<?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: Viorel PETCU</title>
    <description>The latest articles on DEV Community by Viorel PETCU (@realvorl).</description>
    <link>https://dev.to/realvorl</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%2F214207%2Fa3a3fd66-abb7-4419-b7b5-c965ce41411c.png</url>
      <title>DEV Community: Viorel PETCU</title>
      <link>https://dev.to/realvorl</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/realvorl"/>
    <language>en</language>
    <item>
      <title>So, I Vibe Coded A Way Out, Again.</title>
      <dc:creator>Viorel PETCU</dc:creator>
      <pubDate>Sun, 16 Aug 2026 22:18:46 +0000</pubDate>
      <link>https://dev.to/realvorl/so-i-vibe-coded-a-way-out-again-heb</link>
      <guid>https://dev.to/realvorl/so-i-vibe-coded-a-way-out-again-heb</guid>
      <description>&lt;p&gt;A while ago I wrote an article asking &lt;strong&gt;&lt;a href="https://dev.to/realvorl/when-is-100-vibe-coding-ok--38p"&gt;&lt;code&gt;When Is 100% Vibe Coding OK?&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;My conclusion was that it works when the human already owns the invariants. If you know the problem is solvable, understand the constraints, and have a reliable way to verify the output, AI can be a fantastic accelerator.&lt;/p&gt;

&lt;p&gt;Apparently I decided to test that theory again.&lt;/p&gt;

&lt;p&gt;This time with my banking app.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;If you are savvy enough in the Go ecosystem and you just want to produce QR codes that &lt;em&gt;most&lt;/em&gt; banking apps can consume here's the "TL;DR just show me the code" version:&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/unmanageable-software/lazy-epc" rel="noopener noreferrer"&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%2Fcqidw6vv9lvwl7yo5dhx.png" alt="Social Media Template from GitHub" width="800" height="400"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The rest of this article is for those who are interested in how the sausage was made.&lt;/p&gt;

&lt;h2&gt;
  
  
  The "paper" problem
&lt;/h2&gt;

&lt;p&gt;I live in Germany. Germany loves paper. I don't. Well, I do, but in its original form, as trees &lt;code&gt;🌳🌳🌳&lt;/code&gt; &lt;code&gt;🌳🌳🌳&lt;/code&gt; &lt;code&gt;🌳🌳🌳&lt;/code&gt; &lt;/p&gt;

&lt;p&gt;Here, lots of invoices arrive in envelopes. Notices arrive in envelopes. Things that absolutely could have been an email arrive in envelopes, and because I am terrible at filing them, they pile up.&lt;/p&gt;

&lt;p&gt;Eventually I perform the ancient German ritual of sitting down next to the pile and dealing with it. This is when the second annoyance begins.&lt;/p&gt;

&lt;p&gt;Somewhere on each invoice are the recipient, IBAN, amount and payment reference. I open my banking app and start copying them (nowadays the app can photograph the page and you get a decent prefill - but I still check each field). IBAN, check it. Amount, check it. Reference, check it. Look back at the paper because I no longer trust myself. Check everything again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;There has to be a lazier way.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Wait, isn't this what QR codes are for?
&lt;/h2&gt;

&lt;p&gt;My first thought was extremely sophisticated: &lt;strong&gt;Can I somehow shove all of this into a QR code?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Turns out others in Europe had the same idea &lt;code&gt;14&lt;/code&gt; years ago &lt;code&gt;😀&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;There is an &lt;a href="https://www.europeanpaymentscouncil.eu/document-library/guidance-documents/quick-response-code-guidelines-enable-data-capture-initiation" rel="noopener noreferrer"&gt;EPC QR code standard&lt;/a&gt; for &lt;code&gt;SEPA&lt;/code&gt; credit transfers, commonly encountered as &lt;code&gt;GiroCode&lt;/code&gt;. Underneath the banking terminology is something wonderfully boring (just how &lt;code&gt;I&lt;/code&gt; like it): structured text.&lt;/p&gt;

&lt;p&gt;Something along these lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BCD
002
1
SCT

Example Recipient
DE69670400440672013000
EUR12.34

Invoice 2026-001
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Encode a correctly structured EPC payload as a QR code, point a compatible banking app at it, and the payment form gets populated. Recipient, IBAN, amount, reference. Exactly the boring automation I wanted.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;- note that you can only transfer a maximum of &lt;strong&gt;&lt;code&gt;999.999.999,99 EUR&lt;/code&gt;&lt;/strong&gt; so if you need to transfer more than a &lt;code&gt;billion&lt;/code&gt; you can't use this method.&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%2F5odhpat3iz8n8u1enr84.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%2F5odhpat3iz8n8u1enr84.gif" alt="Heath Ledger's Joker Laughing MEME" width="400" height="250"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Linux already had most of my application
&lt;/h2&gt;

&lt;p&gt;I could have started looking for QR libraries. Instead, Linux already had &lt;code&gt;qrencode&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Give it text. Get a QR code.&lt;/p&gt;

&lt;p&gt;So the first architecture was basically:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payment fields
    ↓
EPC payload
    ↓
qrencode
    ↓
PNG
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is my favorite kind of software architecture: &lt;strong&gt;&lt;code&gt;boring&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The EPC specification defines the payload. &lt;code&gt;qrencode&lt;/code&gt; already knows how to make QR codes. Neither problem needed me to invent anything. What I actually needed was some glue around them.&lt;/p&gt;

&lt;p&gt;And this is where &lt;code&gt;Codex&lt;/code&gt; shines.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enter Codex
&lt;/h2&gt;

&lt;p&gt;I didn't really want to spend my free time typing &lt;code&gt;GO&lt;/code&gt; boilerplate. I knew what I wanted the program to do, I knew the boundaries, and, most importantly, I had an extremely good test oracle sitting in my pocket: my banking app.&lt;/p&gt;

&lt;p&gt;To be clear I didn't ask Codex to just &lt;em&gt;"build the thing"&lt;/em&gt;. I started with the project structure. Once that was good, implement the EPC payload and tests. Good. Add the &lt;code&gt;qrencode&lt;/code&gt; boundary. Good. Now generate a self-contained HTML document. Good. Now make the CLI accept a real payment.&lt;/p&gt;

&lt;p&gt;Small step &lt;code&gt;-&amp;gt;&lt;/code&gt; test &lt;code&gt;-&amp;gt;&lt;/code&gt; commit &lt;code&gt;-&amp;gt;&lt;/code&gt; iterate.&lt;/p&gt;

&lt;p&gt;At some point in this cycle I felt less like someone programming in the traditional sense and more like &lt;code&gt;Tony Stark&lt;/code&gt; talking to &lt;code&gt;Jarvis&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Good. Now throw a database in there."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Except with far less holographic UI and considerably more:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;test&lt;/span&gt; ./...
go vet ./...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The database was &lt;code&gt;SQLite&lt;/code&gt;, of course, because of the architectural principle mentioned before: &lt;strong&gt;pick the most boring thing that solves the problem.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;HTML&lt;/code&gt; followed the same philosophy. The &lt;code&gt;QR&lt;/code&gt; &lt;code&gt;PNG&lt;/code&gt; is embedded directly into the document as &lt;code&gt;Base64&lt;/code&gt; encoded text. One file contains the payment details and its &lt;code&gt;QR&lt;/code&gt; code. No server, no framework and no external assets required to display it.&lt;/p&gt;

&lt;p&gt;At this point in time the pipeline looked like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;payment fields
    ↓
EPC payload
    ↓
qrencode
    ↓
PNG
    ↓
self-contained HTML
    ↓
SQLite
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then came the important test.&lt;/p&gt;

&lt;h2&gt;
  
  
  And then I made a donation with it
&lt;/h2&gt;

&lt;p&gt;Eventually I could run something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go run ./cmd/giro create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--recipient&lt;/span&gt; &lt;span class="s2"&gt;"Wikimedia e. V."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--iban&lt;/span&gt; DE09370205000003287300 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--amount&lt;/span&gt; 6.79 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--reference&lt;/span&gt; &lt;span class="s2"&gt;"Via LAZY-EPC over GitHub"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application validates the payment, creates the EPC payload, asks &lt;code&gt;qrencode&lt;/code&gt; for the &lt;code&gt;PNG&lt;/code&gt;, embeds it into the &lt;code&gt;HTML&lt;/code&gt; document and archives the result in &lt;code&gt;SQLite&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;generated ~/Documents/payments/payment-20260816-184212.html
stored payment &lt;span class="c"&gt;#7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I opened the generated document, pointed my banking app at the QR code, and everything appeared in the correct fields.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;xdg-open ~/Documents/payments/payment-20260816-184212.html
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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%2Frebkwsi5yi2nipcfpmdh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Frebkwsi5yi2nipcfpmdh.png" alt="Brave Browser window rendering the HTML mentioned above" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then, because apparently I have more faith in my software development process than is healthy, &lt;strong&gt;I actually made a real transfer with it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It worked.&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%2Fgj0s9dr90bnx9r3acgnw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fgj0s9dr90bnx9r3acgnw.png" alt="android phone HVB banking app displaying the infromation encoded in the EPC" width="692" height="1496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That probably should have been the end of the project. &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%2Fzrqti8mti1c39mn3qlrc.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%2Fzrqti8mti1c39mn3qlrc.gif" alt="Bugs Bunny " width="498" height="498"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Once I had used it for a real payment, another obvious annoyance appeared. A lot of these payments are similar. Same recipient, same IBAN, perhaps a different amount or reference.&lt;/p&gt;

&lt;p&gt;I already had SQLite. Why should I type all of that again?&lt;/p&gt;

&lt;p&gt;So the database gained search and notes. Previous payments could become the starting point for new ones. Find the old payment, change what changed, generate another QR.&lt;/p&gt;

&lt;p&gt;This solved the data problem, but now I needed some way to browse it and that's when a roughly 30-year-old memory came back.&lt;/p&gt;

&lt;h2&gt;
  
  
  A Romanian post office, sometime in the late 1990s
&lt;/h2&gt;

&lt;p&gt;I grew up in Romania, and sometime in the 1990s I remember seeing computer terminals at the post office.&lt;/p&gt;

&lt;p&gt;I couldn't tell you what computers they were. I don't know what operating system they ran, and I for sure couldn't tell you anything about the software behind them. But I remember the interface.&lt;/p&gt;

&lt;p&gt;Those wonderful old forms. Boxes, rows, fields, keyboard navigation. No mouse required. No animation. No hamburger menu. It looked incredibly serious to me as a kid.&lt;/p&gt;

&lt;p&gt;It looked like a &lt;strong&gt;computer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Thirty years later, while building software specifically because I don't want to deal with paperwork coming through the post, I realized exactly what interface I wanted. &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"A post-office terminal, from the 90s"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Apparently some circles take thirty years to come around.&lt;/p&gt;

&lt;p&gt;So I went back to Codex.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Give it a TUI."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The result looks exactly as dated as I wanted it to.&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%2Fkamdxfzmoq10kc0se9z7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fkamdxfzmoq10kc0se9z7.png" alt="List View of the TUI" width="799" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The SQLite archive becomes a terminal table. I can filter it by recipient, reference, notes or IBAN, navigate through previous payments and press Enter on one.&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%2Fsnuvhl8sycs7j3cfz7uq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fsnuvhl8sycs7j3cfz7uq.png" alt="example of the filter function" width="799" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That opens a form containing the complete payment. &lt;code&gt;Tab&lt;/code&gt; through the fields, change the amount or reference, and create a new one.&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%2F7udpzpv40rhaklsmp51z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F7udpzpv40rhaklsmp51z.png" alt="example of possible CRUD operations" width="799" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are no recurring-payment templates because, after using the application, I realized I didn't actually need them. The database itself is the template system. Search for the last similar payment and clone it.&lt;/p&gt;

&lt;p&gt;The notes field is deliberately local metadata. It never enters the EPC payload. It's there so Future Me has some chance of understanding what Past Me was doing.&lt;/p&gt;

&lt;p&gt;And yes, the TUI has &lt;code&gt;Create&lt;/code&gt;, &lt;code&gt;Update&lt;/code&gt;, &lt;code&gt;Delete&lt;/code&gt; and &lt;code&gt;Open&lt;/code&gt;. Delete asks first, because even my commitment to 1990s user-interface design has limits.&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%2Fqy16s6gcty55z8cx9jye.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fqy16s6gcty55z8cx9jye.png" alt="dialog box asking to validate delete action" width="799" height="463"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Boring is a feature
&lt;/h2&gt;

&lt;p&gt;There are already plenty of things I could add.&lt;/p&gt;

&lt;p&gt;The external &lt;code&gt;qrencode&lt;/code&gt; dependency could become a small &lt;code&gt;Go&lt;/code&gt; library so the application can eventually be distributed as one binary. The TUI could gain &lt;strong&gt;mouse support&lt;/strong&gt;, &lt;strong&gt;more keybindings&lt;/strong&gt; and &lt;strong&gt;other views&lt;/strong&gt;. Prebuilt packages would make installation less dependent on knowing your way around the &lt;code&gt;Go&lt;/code&gt; ecosystem.&lt;/p&gt;

&lt;p&gt;I also discovered another rabbit hole that I am deliberately not entering yet: custom URI handlers.&lt;/p&gt;

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

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;lazyepc://payment/...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;could eventually let a service hand a payment to the locally installed application through the operating system.&lt;/p&gt;

&lt;p&gt;That sounds fun.&lt;/p&gt;

&lt;p&gt;Which is precisely why I'm not building it right now.&lt;/p&gt;

&lt;p&gt;The current application solves the problem that annoyed me. It generates the QR, remembers previous payments and gets out of the way. Good is better than perfect, and useful software is allowed to stop before it is mirror polished.&lt;/p&gt;

&lt;h2&gt;
  
  
  So, was this 100% vibe coded?
&lt;/h2&gt;

&lt;p&gt;Pretty much.&lt;/p&gt;

&lt;p&gt;Codex wrote essentially all of the implementation. I made the architectural decisions, tested what came back, rejected complexity I didn't want and decided what the next increment should be.&lt;/p&gt;

&lt;p&gt;That distinction became particularly obvious when the TUI developed a wonderfully stupid bug: pressing Enter did absolutely nothing.&lt;/p&gt;

&lt;p&gt;At first this looked like a terminal focus problem. It wasn't. The callback was firing, but the code had returned a Go slice by value while another closure later reassigned its captured slice. The selection callback was therefore still looking at the original empty state.&lt;/p&gt;

&lt;p&gt;AI wrote the bug. I still had to understand why the system couldn't possibly be behaving correctly.&lt;/p&gt;

&lt;p&gt;Then Codex fixed it.&lt;/p&gt;

&lt;p&gt;That experience reaffirmed my conclusion from the previous article. Vibe coding didn't remove engineering from this project. It removed an extraordinary amount of typing.&lt;/p&gt;

&lt;p&gt;And, unexpectedly, sitting.&lt;/p&gt;

&lt;p&gt;I'm in my 40s now and I've been typing essentially nonstop since university. My hands occasionally remind me of that. What I didn't expect from working this way was that while Codex was implementing something or running the test suite, I didn't have to sit there staring at it. I could get up, walk around the house, stretch, do something else, then come back and review the result.&lt;/p&gt;

&lt;p&gt;I'm not going to pretend that AI-assisted programming is some kind of ergonomic treatment, but reducing the amount of time I spend glued to a keyboard turned out to be a surprisingly nice side effect.  &lt;/p&gt;

&lt;p&gt;I still decided where the boundaries belonged. I decided that calling &lt;code&gt;qrencode&lt;/code&gt; was preferable to adding another abstraction just because it was possible. I decided HTML was enough. I decided SQLite was enough. I decided what needed tests, what constituted success, and when a feature wasn't worth building.&lt;/p&gt;

&lt;p&gt;Most importantly, I decided whether I trusted the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  The invariant was sitting in my pocket
&lt;/h2&gt;

&lt;p&gt;This project had unusually strong invariants.&lt;/p&gt;

&lt;p&gt;EPC is a specification. The payload is inspectable text. The QR code is machine-readable. At the end of the chain is an independently developed banking application that parses the result and shows me exactly what payment it thinks I am about to authorize.&lt;/p&gt;

&lt;p&gt;Codex could hallucinate an implementation detail. It could choose a bad abstraction. It could misunderstand what I wanted the TUI to do.&lt;/p&gt;

&lt;p&gt;But it couldn't negotiate with my banking app.&lt;/p&gt;

&lt;p&gt;Either the QR code represented the payment I intended, or it didn't.&lt;/p&gt;

&lt;p&gt;That is what I meant when I wrote that &lt;strong&gt;the human needs to own the invariants&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This time I just happened to own them while repeatedly saying the software-engineering equivalent of:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;"Jarvis, throw in a little hot rod red in there."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The result is &lt;a href="https://github.com/unmanageable-software/lazy-epc" rel="noopener noreferrer"&gt;&lt;code&gt;lazy-epc&lt;/code&gt;&lt;/a&gt;, and the code is now public. It's a workable MVP rather than a polished consumer application. If you're reasonably comfortable around Go and Linux, you can build it, use it, break it, or make it better.&lt;/p&gt;

&lt;p&gt;For now, it does exactly what I wanted:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Turn annoying payment details into a QR code, remember them for next time, and get out of the way.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>vibecoding</category>
      <category>cschallenge</category>
    </item>
    <item>
      <title>When Is 100% Vibe Coding OK ?</title>
      <dc:creator>Viorel PETCU</dc:creator>
      <pubDate>Sun, 31 May 2026 22:20:09 +0000</pubDate>
      <link>https://dev.to/realvorl/when-is-100-vibe-coding-ok--38p</link>
      <guid>https://dev.to/realvorl/when-is-100-vibe-coding-ok--38p</guid>
      <description>&lt;p&gt;Recently, I lost a lot of time reviewing and would have lost even more implementing, what I can only describe as &lt;code&gt;vibe-written&lt;/code&gt; specs.&lt;/p&gt;

&lt;p&gt;Requirements that &lt;em&gt;felt&lt;/em&gt; coherent while saying almost nothing concrete. The kind of document where every paragraph sounds reasonable until you scratch the surface a bit and realize the thing is not applicable in the existing ecosystem. &lt;/p&gt;

&lt;p&gt;To be clear: I am not against &lt;code&gt;vibe coding&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I use it myself all the time.&lt;/p&gt;

&lt;p&gt;If I need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a one-off script,&lt;/li&gt;
&lt;li&gt;a throwaway visualization,&lt;/li&gt;
&lt;li&gt;a temporary parser,&lt;/li&gt;
&lt;li&gt;a document nobody else will maintain,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then speed matters more than rigor.&lt;/p&gt;

&lt;p&gt;But maintenance changes the equation completely.&lt;/p&gt;




&lt;p&gt;This weekend accidentally gave me a good example of where &lt;code&gt;vibe coding&lt;/code&gt; actually shines. During my usual Saturday clean-up, I knocked over one of those wooden cube puzzles: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;25 identical pentacubes that are supposed to fill a perfect &lt;code&gt;5×5×5&lt;/code&gt; cube. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Specifically this one: &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%2F6dsmgatdq25oljfpii95.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%2F6dsmgatdq25oljfpii95.png" alt="wooden cube puzzle" width="800" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I tried reconstructing it manually for a while and failed ( &lt;em&gt;blaming the fact that I was tired and there was &lt;strong&gt;heat dome&lt;/strong&gt; over EUROPE messing with the comfort index&lt;/em&gt; 🥵 )&lt;/p&gt;

&lt;p&gt;Needless to say this really bugged the engineer in me and on Sunday morning I was ready to throw AI at this. I realized that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the rules are precise,&lt;/li&gt;
&lt;li&gt;the state space is finite,&lt;/li&gt;
&lt;li&gt;the problem is absolutely solvable,&lt;/li&gt;
&lt;li&gt;and computers are very good at brute force.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I opened Go and started &lt;code&gt;vibe coding&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The single piece shape is simple:&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%2Fnr9qmepraqrylmcle1m4.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%2Fnr9qmepraqrylmcle1m4.png" alt="pentacube puzzle piece" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A planar pentacube of which there are 25 instances.&lt;/p&gt;

&lt;p&gt;At first the challenge was not the algorithm. It was the representation or the domain specific language (&lt;code&gt;DSL&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Even explaining the puzzle to &lt;code&gt;ChatGPT&lt;/code&gt; took multiple iterations. We had to establish:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;coordinate conventions,&lt;/li&gt;
&lt;li&gt;slice notation,&lt;/li&gt;
&lt;li&gt;legal piece states,&lt;/li&gt;
&lt;li&gt;rotation semantics,&lt;/li&gt;
&lt;li&gt;and eventually a labeling system for pieces.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the model stabilized, the rest followed naturally:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;generate transformations,&lt;/li&gt;
&lt;li&gt;normalize orientations,&lt;/li&gt;
&lt;li&gt;recursively place pieces,&lt;/li&gt;
&lt;li&gt;backtrack on collision.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Classic brute force.&lt;/p&gt;

&lt;p&gt;I fully expected to need serious optimization eventually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pruning,&lt;/li&gt;
&lt;li&gt;bitboards,&lt;/li&gt;
&lt;li&gt;placement precomputation,&lt;/li&gt;
&lt;li&gt;maybe even Algorithm X.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But modern hardware is absurdly efficient. &lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;naive recursive solver&lt;/strong&gt; found solutions fast enough that optimization stopped being necessary and became optional engineering curiosity instead.&lt;/p&gt;

&lt;p&gt;Somewhere in the middle of this, the project accidentally became a generic mono-pentacube solver.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
computing unique orientations &lt;span class="k"&gt;for &lt;/span&gt;default piece...
unique orientations: 24
solution found

z &lt;span class="o"&gt;=&lt;/span&gt; 0
1 1 1 1 2 
3 1 4 2 2 
3 3 5 6 2 
3 7 8 9 2 
3 8 8 8 8 

z &lt;span class="o"&gt;=&lt;/span&gt; 1
A B C D E 
A 4 4 4 4 
A A 5 6 6 
A 7 F 9 9 
G H H H H 

z &lt;span class="o"&gt;=&lt;/span&gt; 2
I B C D E 
I I C D J 
I 5 5 6 J 
I 7 F 9 J 
G 7 F H J 

z &lt;span class="o"&gt;=&lt;/span&gt; 3
B B C D E 
K K K K E 
L M 5 6 J 
G 7 F 9 N 
G O O O O 

z &lt;span class="o"&gt;=&lt;/span&gt; 4
L B C D E 
L M K P N 
L M P P N 
L M F P N 
G M O P N 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then I wanted &lt;strong&gt;visual output&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So we added &lt;code&gt;OBJ/MTL&lt;/code&gt; export and grouped the generated geometry by piece so the solutions could be inspected directly in Blender.&lt;/p&gt;

&lt;p&gt;At that point I left the chat entirely, opened Codex, cleaned the implementation up, packaged it properly, and turned it into something reusable for solving this entire family of problems.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Solution&lt;/th&gt;
&lt;th&gt;Exploded View&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&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%2F68k5esb3pl8jox1hlg3b.png" alt="Solution" width="720" height="720"&gt;&lt;/td&gt;
&lt;td&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%2Fw4tokqm2t8yq5qyihwgh.png" alt="Exploded Solution" width="800" height="726"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Not bad for roughly two hours of &lt;code&gt;vibe coding&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But I think the important part is this:&lt;/p&gt;

&lt;p&gt;My attempt only worked because &lt;strong&gt;I already knew&lt;/strong&gt; the problem itself was &lt;strong&gt;solvable&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;That certainty let me push through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;incorrect assumptions,&lt;/li&gt;
&lt;li&gt;hallucinated constraints,&lt;/li&gt;
&lt;li&gt;broken code generation,&lt;/li&gt;
&lt;li&gt;malformed OBJ output,&lt;/li&gt;
&lt;li&gt;and multiple rounds of &lt;code&gt;ChatGPT&lt;/code&gt; &lt;strong&gt;&lt;em&gt;confidently misunderstanding&lt;/em&gt;&lt;/strong&gt; the puzzle.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;That is where I think &lt;code&gt;vibe coding&lt;/code&gt; genuinely works well:&lt;/p&gt;

&lt;p&gt;When the human already owns the invariants.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;the problem is solvable,&lt;/li&gt;
&lt;li&gt;the constraints are real,&lt;/li&gt;
&lt;li&gt;and the output is verifiable,&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;then AI becomes an accelerator and &lt;code&gt;vibe coding&lt;/code&gt; is &lt;strong&gt;100% OK&lt;/strong&gt; in my book.&lt;/p&gt;

&lt;p&gt;But if the requirements themselves are "vibes", then the system has nothing solid to converge toward.&lt;/p&gt;

&lt;p&gt;You do not get speed.&lt;/p&gt;

&lt;p&gt;You get, well, nonsense. &lt;/p&gt;

&lt;p&gt;PS:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;should you knock over your puzzle, or ever need to fill a 5x5x5 cube with a planar pentacube, while also needing to visualise the solution... &lt;br&gt;
I got you: &lt;a href="https://github.com/realvorl/vibe-code-t-puzzle" rel="noopener noreferrer"&gt;vibe coded &lt;code&gt;go&lt;/code&gt; implementation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>ai</category>
      <category>go</category>
      <category>cschallenge</category>
      <category>productivity</category>
    </item>
    <item>
      <title>No One Has This Problem? Just Me? OK.</title>
      <dc:creator>Viorel PETCU</dc:creator>
      <pubDate>Sun, 02 Nov 2025 09:51:08 +0000</pubDate>
      <link>https://dev.to/realvorl/no-one-has-this-problem-just-me-ok-3oon</link>
      <guid>https://dev.to/realvorl/no-one-has-this-problem-just-me-ok-3oon</guid>
      <description>&lt;p&gt;I think I just solved a problem nobody else cared to fix.&lt;/p&gt;

&lt;p&gt;I’ve been using a Mac for years and got used to everything just working the way I like it. But at work, I have to use Windows. Add to that the fact that I live in Germany, so the provided hardware has a &lt;code&gt;QWERTZ&lt;/code&gt; layout plus all the umlauts: &lt;code&gt;Ä&lt;/code&gt;, &lt;code&gt;Ö&lt;/code&gt;, &lt;code&gt;Ü&lt;/code&gt;, and the Sharp &lt;code&gt;S&lt;/code&gt; or &lt;code&gt;ß&lt;/code&gt;. Add to that the fact that I have to write a lot in Romanian, with its own set of diacritics: &lt;code&gt;Ă&lt;/code&gt;, &lt;code&gt;Â&lt;/code&gt;, &lt;code&gt;Î&lt;/code&gt;, &lt;code&gt;Ș&lt;/code&gt;, &lt;code&gt;Ț&lt;/code&gt;, and you can imagine how messy keyboard shortcuts and special characters can get. &lt;/p&gt;

&lt;p&gt;After 12 years of doing this balancing act, as a software developer and technical writer, I finally found a setup that fixes it all. It’s the perfect mix of &lt;strong&gt;AutoHotkey&lt;/strong&gt; and the &lt;strong&gt;Logitech MX Keys Mini (German layout)&lt;/strong&gt;, &lt;strong&gt;Mac&lt;/strong&gt;, and &lt;strong&gt;PC&lt;/strong&gt; special keys.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logitech MX Keys Mini is the best.
&lt;/h3&gt;

&lt;p&gt;Before we go any further, let’s settle one thing. If you don’t think the &lt;strong&gt;Logitech MX Keys Mini&lt;/strong&gt; is the best keyboard out there, you’re wrong. Stop reading, go to a brick-and-mortar store that carries it (all of the technology-oriented ones will have it), and test it. After you &lt;strong&gt;inevitably&lt;/strong&gt; discover this fact, come back and finish the article. I could write an entire fanboy article about this piece of exceptional hardware because it's just that good.&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%2Fsgg0mumgftfcosonzezx.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%2Fsgg0mumgftfcosonzezx.png" alt="Logitech MX MINI keyboard" width="800" height="800"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;But we are here for a different reason... so let's get back to that.&lt;/p&gt;

&lt;h3&gt;
  
  
  QWERTZ Isn’t a Problem.
&lt;/h3&gt;

&lt;p&gt;No, wait — one more detour while we’re at it. Yes, I’m using the &lt;strong&gt;German layout&lt;/strong&gt; for programming. I know, I know... everyone thinks their layout is the best. Go ahead, grab your pitchforks, and tell me how I’m “doing it wrong.” But after 22 years of professional coding, I can tell you: &lt;strong&gt;&lt;em&gt;it is all in your head&lt;/em&gt;&lt;/strong&gt;! No layout is &lt;strong&gt;superior&lt;/strong&gt; — you build muscle memory and become efficient regardless of character location. So the best layout is the one that makes you fastest and doesn’t give you Carpal Tunnel issues after a day of coding and writing. It just so happens that QWERTZ is more useful for both &lt;strong&gt;German&lt;/strong&gt; and &lt;strong&gt;Romanian&lt;/strong&gt;, and I would also argue for programming in general. &lt;/p&gt;

&lt;p&gt;Here are the stats:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Keywords Total&lt;/th&gt;
&lt;th&gt;Keywords with &lt;strong&gt;Y&lt;/strong&gt; or &lt;strong&gt;Z&lt;/strong&gt;
&lt;/th&gt;
&lt;th&gt;"Y"&lt;/th&gt;
&lt;th&gt;%-Y&lt;/th&gt;
&lt;th&gt;"Z"&lt;/th&gt;
&lt;th&gt;%-Z&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;C (32)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;—&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Java (≈67)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;byte&lt;/code&gt;, &lt;code&gt;finally&lt;/code&gt;, &lt;code&gt;synchronized&lt;/code&gt;, &lt;code&gt;try&lt;/code&gt;, &lt;code&gt;yield&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;~7.5%&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;~1.5%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Python (35)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;async&lt;/code&gt;, &lt;code&gt;yield&lt;/code&gt;, &lt;code&gt;finally&lt;/code&gt;, &lt;code&gt;try&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;~11.4%&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Go (25)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4%&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0%&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;OK, now for real — getting back to the topic...&lt;/p&gt;

&lt;h3&gt;
  
  
  macOS Got It Right
&lt;/h3&gt;

&lt;p&gt;One thing macOS absolutely nailed is how &lt;strong&gt;Command + C&lt;/strong&gt; and &lt;strong&gt;Control + C&lt;/strong&gt; just make sense everywhere. Copy in the terminal? Works. Copy in a GUI? Works. No weird exceptions. On Windows, for the longest time, you had to remember that in the terminal it’s &lt;strong&gt;Ctrl + Shift + C&lt;/strong&gt; — because we all know you don't want to execute &lt;strong&gt;Ctrl + C&lt;/strong&gt; mid-command. It always felt wrong to me. I know Microsoft finally fixed this when text is already selected, but after years of fighting with it, I wanted something better — something that just works all the time and does so regardless of the OS.&lt;/p&gt;

&lt;h3&gt;
  
  
  Fixing Windows With AutoHotkey
&lt;/h3&gt;

&lt;p&gt;This is where &lt;strong&gt;AutoHotkey&lt;/strong&gt; enters the picture and solves this issue sublimely. This software is the:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;VIM of Windows hotkeying.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Insane amounts of possible customization, including interacting/polling with open windows. &lt;strong&gt;WikiHow&lt;/strong&gt; does a great &lt;a href="https://www.wikihow.com/Use-AutoHotkey" rel="noopener noreferrer"&gt;TL;DR setup&lt;/a&gt; for getting up and running with AutoHotkey. But the authors also maintain the &lt;a href="https://www.autohotkey.com/docs/v2/howto/Install.htm" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;, so you can fully take advantage of this unique software and its capabilities.&lt;/p&gt;

&lt;p&gt;All right, after all that reading, several rants, and what I think is my longest article yet, here is your reward — enjoy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="p"&gt;;&lt;/span&gt; Romanian diacritics layer on German keyboard using Right Ctrl as modifier
&lt;span class="p"&gt;;&lt;/span&gt; AutoHotkey v2 — Explicit combo version, reliable uppercase handling

&lt;span class="c"&gt;#SingleInstance Force&lt;/span&gt;
&lt;span class="c"&gt;#Requires AutoHotkey v2.0&lt;/span&gt;

&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;---&lt;/span&gt; Lowercase layer &lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^a::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"â"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^ä::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ă"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^s::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ș"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^t::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"ț"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^i::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"î"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;---&lt;/span&gt; Uppercase layer &lt;span class="o"&gt;(&lt;/span&gt;Right Ctrl + Shift + key&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^+a::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Â"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^+ä::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Ă"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^+s::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Ș"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^+t::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Ț"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^+i::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Î"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;---&lt;/span&gt; ALT&lt;span class="o"&gt;(&lt;/span&gt;CMD&lt;span class="o"&gt;)&lt;/span&gt; + C / X / V remaps to COPY / CUT / PASTE system-wide &lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="o"&gt;!&lt;/span&gt;c::Send&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^c"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="p"&gt;;&lt;/span&gt; Alt + C → Ctrl + C
&lt;span class="o"&gt;!&lt;/span&gt;x::Send&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^x"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="p"&gt;;&lt;/span&gt; Alt + X → Ctrl + X
&lt;span class="o"&gt;!&lt;/span&gt;v::Send&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^v"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="p"&gt;;&lt;/span&gt; Alt + V → Ctrl + V
&lt;span class="o"&gt;!&lt;/span&gt;r::Send&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^r"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="p"&gt;;&lt;/span&gt; Alt + R → Ctrl + r
&lt;span class="o"&gt;!&lt;/span&gt;z::Send&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^z"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="p"&gt;;&lt;/span&gt; Alt + R → Ctrl + z
&lt;span class="o"&gt;!&lt;/span&gt;+z::Send&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^y"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="p"&gt;;&lt;/span&gt; Alt + SHIFT + R → Ctrl + y

&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nt"&gt;---&lt;/span&gt; ALT&lt;span class="o"&gt;(&lt;/span&gt;CMD&lt;span class="o"&gt;)&lt;/span&gt; + A / S remaps to SELECT ALL / SAVE system-wide &lt;span class="nt"&gt;---&lt;/span&gt;
&lt;span class="o"&gt;!&lt;/span&gt;a::Send&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^a"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;      &lt;span class="p"&gt;;&lt;/span&gt; Alt + A → Ctrl + A  &lt;span class="o"&gt;(&lt;/span&gt;Select All&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;!&lt;/span&gt;s::Send&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"^s"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;      &lt;span class="p"&gt;;&lt;/span&gt; Alt + S → Ctrl + S  &lt;span class="o"&gt;(&lt;/span&gt;Save&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^q::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"@"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;;&lt;/span&gt; Right CTRL + q &lt;span class="o"&gt;(&lt;/span&gt;produce @ &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^+::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"~"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;;&lt;/span&gt; Right CTRL + &lt;span class="s2"&gt;"+"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;produce ~ &lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^-::Return         &lt;span class="p"&gt;;&lt;/span&gt; Right CTRL + - &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;do &lt;/span&gt;nothing / RETURN&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^&amp;lt;::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"|"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;;&lt;/span&gt; Right CTRL + &lt;span class="s2"&gt;"&amp;lt;"&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;produce | &lt;span class="o"&gt;)&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^e::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"€"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^2::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"²"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^3::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"³"&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="nt"&gt;---&lt;/span&gt; CODING NEEDS
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^7::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"{"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^8::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"["&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^9::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"]"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^0::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"}"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;  &lt;span class="p"&gt;;&lt;/span&gt; 
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;^ß::SendText&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;)  ; 
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Really customizing the MX Keys Mini
&lt;/h3&gt;

&lt;p&gt;Now, if you also happen to be a total &lt;code&gt;NERD&lt;/code&gt; and &lt;code&gt;perfectionist&lt;/code&gt;, you can really take this all the way up to &lt;code&gt;ELEVEN&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here's how I customized the crap out of the &lt;strong&gt;MX Keys Mini&lt;/strong&gt; keycaps too. The stock German layout doesn’t show any Romanian letters, obviously, so I added the missing diacritics right onto the keys. It’s a small detail, but it makes the keyboard feel bespoke!&lt;/p&gt;

&lt;p&gt;For that, I used a permanent marker that you can find on Amazon (oil- and water-proof and able to adhere to plastic) and a simple template:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;TRACE&lt;/th&gt;
&lt;th&gt;GUIDE&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&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%2Fc7bhiynaihjgkf052hrk.png" alt="oil and water proof permanent white marker writes on plastic" width="800" height="873"&gt;&lt;/td&gt;
&lt;td&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%2F2tnsi5gkhiljdzvglv6z.png" alt="ruler template for lettering" width="800" height="788"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Final result
&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%2Fkrznb3cejse9ppm34xyi.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%2Fkrznb3cejse9ppm34xyi.png" alt="Logitech MX MINI keyboard upgraded with diacritics" width="800" height="358"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So, if you made it all the way here, &lt;strong&gt;I am impressed&lt;/strong&gt;. I really thought this was a niche thing, but apparently, you are also in need of a solution for typing Romanian while using a German layout and also switching between Mac and PC on a daily basis for years. I think this makes you officially my:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;BROTHER (or sister) FROM ANOTHER MOTHER&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&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%2Fe6aiaq2fbhmxxzmmhwbe.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%2Fe6aiaq2fbhmxxzmmhwbe.png" alt="gumball and darwin best friends and brother from another mother" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;PEACE!&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>multiplatform</category>
      <category>writing</category>
      <category>programming</category>
    </item>
    <item>
      <title>Patreon Doesn’t Show Your Total Spending. So I Did.</title>
      <dc:creator>Viorel PETCU</dc:creator>
      <pubDate>Sun, 27 Jul 2025 00:04:36 +0000</pubDate>
      <link>https://dev.to/realvorl/patreon-doesnt-show-your-total-spending-so-i-did-1bo5</link>
      <guid>https://dev.to/realvorl/patreon-doesnt-show-your-total-spending-so-i-did-1bo5</guid>
      <description>&lt;p&gt;I like supporting small creators on Patreon. A few euros here, a few bucks there — no problem. But one day, I looked at my Memberships page and thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“Wait… how much is this costing me per year?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Patreon doesn't tell you.&lt;br&gt;
No total. No filters. No export. No API. It’s like &lt;strong&gt;they don’t want you to know&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Here's a Challenge for You
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://patreon.com/settings/memberships" rel="noopener noreferrer"&gt;https://patreon.com/settings/memberships&lt;/a&gt; and check how much you’re spending yearly on creator support. Go ahead, I’ll wait.&lt;/p&gt;

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

&lt;p&gt;Back? Was it easy?&lt;/p&gt;

&lt;p&gt;If you only support a couple of people, maybe.&lt;br&gt;
But if you’re like me — supporting multiple creators in different currencies and on different &lt;strong&gt;billing periods&lt;/strong&gt; — it turns into a bit of a chore. Right?&lt;/p&gt;

&lt;p&gt;Since I want a clear view of all my subscriptions (and Patreon doesn’t offer it), I had to build it myself.&lt;/p&gt;

&lt;h3&gt;
  
  
  This is Part 3 in My "Why Doesn’t This Site Show a Total?" Series.
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;id&lt;/th&gt;
&lt;th&gt;name&lt;/th&gt;
&lt;th&gt;follow&lt;/th&gt;
&lt;th&gt;code&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Total cost of an Amazon wishlist&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/realvorl/if-you-don-t-like-it-change-it-ag5"&gt;article&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://gist.github.com/realvorl/476177819f413ad2a4d2f04730d5f7c5#file-amazon-wishlist-total-js" rel="noopener noreferrer"&gt;gist&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Total time of a YouTube playlist&lt;/td&gt;
&lt;td&gt;&lt;a href="https://viorel-petcu.com/blog/yt-playlist-time" rel="noopener noreferrer"&gt;article&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://gist.github.com/realvorl/cbe29c931f11890ffbf365bd46729b84#file-yt-playlist-runtime-js" rel="noopener noreferrer"&gt;gist&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Total cost of my Patreon memberships&lt;/td&gt;
&lt;td&gt;this one&lt;/td&gt;
&lt;td&gt;&lt;a href="https://gist.github.com/realvorl/e72b6d01adbace038bb24971b23f4677#file-patreon-membership-anual-js" rel="noopener noreferrer"&gt;gist&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each one is a quick userscript using vanilla JavaScript and the User &lt;a href="https://tenrabbits.github.io/user-js-css-docs/" rel="noopener noreferrer"&gt;JavaScript and CSS browser&lt;/a&gt; extension. Drop the code in, refresh the page, and boom — feature unlocked.&lt;/p&gt;

&lt;h2&gt;
  
  
  What This One Does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Scans the Patreon Memberships page&lt;/li&gt;
&lt;li&gt;Finds prices like &lt;strong&gt;$4.99&lt;/strong&gt; per month or &lt;strong&gt;€50&lt;/strong&gt; per year&lt;/li&gt;
&lt;li&gt;Groups them by:

&lt;ul&gt;
&lt;li&gt;Currency (&lt;code&gt;USD&lt;/code&gt;, &lt;code&gt;EUR&lt;/code&gt;, etc.)&lt;/li&gt;
&lt;li&gt;Billing cycle (&lt;code&gt;monthly&lt;/code&gt;, &lt;code&gt;yearly&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Adds everything up&lt;/li&gt;

&lt;li&gt;Shows the result in a popup with:

&lt;ul&gt;
&lt;li&gt;Monthly and yearly breakdowns&lt;/li&gt;
&lt;li&gt;A bold total per year, in green for dollars and blue for euros&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

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

&lt;p&gt;The script waits for the page to load fully, then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Injects a styled modal&lt;/li&gt;
&lt;li&gt;Hooks into the second &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; on the page and &lt;em&gt;makes it clickable&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;When clicked, it scrapes all relevant &lt;code&gt;&amp;lt;span&amp;gt;&lt;/code&gt; elements&lt;/li&gt;
&lt;li&gt;Extracts price, frequency, and currency&lt;/li&gt;
&lt;li&gt;Totals everything and shows it in a popup&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Before vs. After
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&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%2F5cmuijjr0mvpgdw8hou7.png" alt="Before screenshot" width="720" height="860"&gt;&lt;/td&gt;
&lt;td&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%2Fqxm2iuodl13bkpj7x7xy.png" alt="After screenshot" width="720" height="860"&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;So there's my answer: &lt;code&gt;90.24 $&lt;/code&gt; and &lt;code&gt;252 €&lt;/code&gt; per year going to creators I like and like supporting! - &lt;code&gt;less than 1€ per day&lt;/code&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Script Exists
&lt;/h2&gt;

&lt;p&gt;I like being generous — but I don’t like being blind.&lt;/p&gt;

&lt;p&gt;If a big platform like Patreon hides something as basic as "how much am I spending?", then I’ll just write it myself.&lt;/p&gt;

&lt;p&gt;This script gave me back visibility. No tracking, no login, no API — just a plain overview.&lt;/p&gt;

&lt;h2&gt;
  
  
  No Plugin? No Problem
&lt;/h2&gt;

&lt;p&gt;If you don't like to install extensions (even if these are extremely useful) you still can use this script as such:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Open the browser &lt;code&gt;dev&lt;/code&gt; tools → Console tab&lt;/li&gt;
&lt;li&gt;Paste the full code and hit Enter&lt;/li&gt;
&lt;li&gt;Totals will appear when you click the second &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt; on the page&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Or turn it into a &lt;a href="https://caiorss.github.io/bookmarklet-maker/" rel="noopener noreferrer"&gt;bookmarklet&lt;/a&gt; if you want a fun little challenge and another alternative to installing extensions.&lt;/p&gt;

&lt;p&gt;Enjoy! &lt;br&gt;
Vio, Out! &lt;/p&gt;

</description>
      <category>automation</category>
      <category>webdev</category>
      <category>extensions</category>
      <category>patreon</category>
    </item>
    <item>
      <title>PDF Scan File Size: What To Do About It.</title>
      <dc:creator>Viorel PETCU</dc:creator>
      <pubDate>Sat, 14 Sep 2024 22:25:03 +0000</pubDate>
      <link>https://dev.to/realvorl/pdf-scan-file-size-what-to-do-about-it-4d9j</link>
      <guid>https://dev.to/realvorl/pdf-scan-file-size-what-to-do-about-it-4d9j</guid>
      <description>&lt;p&gt;&lt;a href="https://realvorl.github.io/BadgeSVGen/?data=eyJ0ZXh0MSI6IvCfm6DvuI8iLCJ0ZXh0MiI6InVidW5Ub29scyIsIndpZHRoIjoxMTAsImhlaWdodCI6MjAsImZvbnRTaXplIjoxNCwiaGludFNpemUiOjE1LCJmaWxsQ29sb3IxIjoiIzAwMDAwMCIsImZpbGxDb2xvcjIiOiIjNTAzMjUwIiwidGV4dENvbG9yMSI6IiNmZmZmZmYiLCJ0ZXh0Q29sb3IyIjoiI2ZmZmZmZiIsImRpdmlkZXJMaW5lIjozMH0=" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Frealvorl%2Fubuntools%2Fmain%2Fdocs%2Fbranding%2Fbadge.svg" alt="badge"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In today's digital age, we're constantly creating, sharing, and storing documents and media. While the cost of storage has dropped and internet speeds have skyrocketed, there's a hidden cost we often overlook—our &lt;strong&gt;environmental impact&lt;/strong&gt;. A wonderful resource for insights on how our digital activities affect the environment is &lt;a href="https://www.thegreenwebfoundation.org/news/data-sources-for-calculating-digital-emissions/#calculators" rel="noopener noreferrer"&gt;&lt;strong&gt;The Green Web Foundation&lt;/strong&gt;&lt;/a&gt;, particularly their calculators.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Environmental Cost of Large Files
&lt;/h3&gt;

&lt;p&gt;It might not be immediately obvious, but the size of the files we share and store contributes to global energy consumption. Data centers, which store everything from photos to PDFs, use massive amounts of electricity. Smaller files mean less data transferred, processed, and stored—leading to a direct reduction in energy usage and, ultimately, CO₂ emissions.&lt;/p&gt;

&lt;h3&gt;
  
  
  The 'Ubuntools' Docker Image
&lt;/h3&gt;

&lt;p&gt;This is where my project, the &lt;strong&gt;ubuntools&lt;/strong&gt; Docker image, comes in handy. Initially created as a basic toolkit for probing APIs and aggregating data, I expanded the project to include tools for compressing PDFs and media files. Why? Because reducing file size doesn’t just save space—it helps reduce the environmental impact of our digital lives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Document and Media Compression Matters
&lt;/h3&gt;

&lt;p&gt;Even though cloud storage seems limitless and fiber internet offers instant downloads, the carbon footprint of transferring large files still matters. Every megabyte of data requires energy to transmit and store. By compressing documents and media, we can make a small but meaningful contribution to minimizing our environmental impact. Here's an example of how I incorporated this idea into &lt;a href="https://github.com/realvorl/ubuntools/" rel="noopener noreferrer"&gt;&lt;strong&gt;ubuntools&lt;/strong&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;I planned to send an article from a magazine to a contact of mine as an email attachment since there was no online version to simply share the link. So, I fired up my flatbed scanner and scanned the three pages:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia3.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExcGt4eTUwZDV5bWszcjY3c3IwYTMxdzlhZTFlbXRoanhvaHdsMG51ZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2F37Fsl1eFxbhtu%2F200.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia3.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExcGt4eTUwZDV5bWszcjY3c3IwYTMxdzlhZTFlbXRoanhvaHdsMG51ZSZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2F37Fsl1eFxbhtu%2F200.webp" alt="can't believe my eyes"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To my surprise, for a &lt;strong&gt;300 dpi&lt;/strong&gt; resolution, the result was a &lt;strong&gt;1.77 MB&lt;/strong&gt; PDF file. That would not even fit on a standard floppy disk back in the day—&lt;strong&gt;unacceptable!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I planned to do some editing of the files anyway, using GIMP (for image corrections, cropping, fine rotation, etc.), so I told myself, "Once the shading is gone and the colors are uniform, the PDF size will surely reduce." &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;BTW&lt;/strong&gt;, if you're interested in a tutorial on editing PDFs with GIMP (all free and open source software), leave a comment. If there's enough interest, I'll write up a tutorial on the top 10 things you need and how to accomplish them using GIMP.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;But then I got an even bigger surprise. After rotation, color correction, and exporting the layers as pages of the PDF, I felt like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia2.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExamI4MmZxMXU4MThuaWwwaWdpZnFvaWp2MTV6OGd6a2t2cG9ob3VsNyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2F6eqGj0ouvUUUw%2Fgiphy.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmedia2.giphy.com%2Fmedia%2Fv1.Y2lkPTc5MGI3NjExamI4MmZxMXU4MThuaWwwaWdpZnFvaWp2MTV6OGd6a2t2cG9ob3VsNyZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw%2F6eqGj0ouvUUUw%2Fgiphy.webp" alt="how I felt"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Well... 💩 The file was now &lt;strong&gt;11.4 MB&lt;/strong&gt;—kind of going in the wrong direction!&lt;/p&gt;

&lt;p&gt;So, I taught &lt;strong&gt;ubuntools&lt;/strong&gt; some new tricks. Under the &lt;code&gt;pdf-processing&lt;/code&gt; tag, you'll find a base &lt;code&gt;Ubuntu&lt;/code&gt; Docker image with the following tools:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;ghostscript&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;pdftk-java&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;poppler-utils&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# start ubuntools in the directory where your big PDF file are&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/work &lt;span class="nt"&gt;--workdir&lt;/span&gt; /work viorelpe/ubuntools:pdf-processing /bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# execute the following command&lt;/span&gt;
gs &lt;span class="nt"&gt;-sDEVICE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;pdfwrite &lt;span class="nt"&gt;-dCompatibilityLevel&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1.4 &lt;span class="nt"&gt;-dPDFSETTINGS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/ebook &lt;span class="nt"&gt;-dNOPAUSE&lt;/span&gt; &lt;span class="nt"&gt;-dQUIET&lt;/span&gt; &lt;span class="nt"&gt;-dBATCH&lt;/span&gt; &lt;span class="nt"&gt;-sOutputFile&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;compressed.pdf original.pdf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command reduces the scanned document's size while maintaining high quality—perfect for email attachments or archiving. How much did it reduce the size? It came down to &lt;strong&gt;0.71 MB&lt;/strong&gt;, which is a considerable improvement.&lt;/p&gt;

&lt;p&gt;Here’s the finished product and the original side-by-side:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fpq7i49fh2cpn1avoxa7m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fpq7i49fh2cpn1avoxa7m.png" alt="compare uncompressed and compressed"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Check the difference for yourself on &lt;a href="https://github.com/realvorl/ubuntools/commit/3df902730c2166aa973c320f256856ae2b7526af" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Expanding with Media Compression
&lt;/h3&gt;

&lt;p&gt;From here, it's easy to integrate other media compression utilities, such as &lt;code&gt;FFmpeg&lt;/code&gt;, to reduce the size of videos and images. These tools, combined with &lt;strong&gt;ubuntools&lt;/strong&gt;, make it real easy for you, because all you have to do, is to run two commands:&lt;/p&gt;

&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Start &lt;strong&gt;ubuntools&lt;/strong&gt; with the appropriate tools (via tag).&lt;/li&gt;
&lt;li&gt;Run a command and feed it your files.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Conclusion: Think Small, Act Big
&lt;/h3&gt;

&lt;p&gt;File size might seem trivial in an era of "unlimited" storage and bandwidth, but it's the small, cumulative actions that matter. Compress your files, shrink your media, and contribute to a greener future.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://realvorl.github.io/BadgeSVGen/?data=eyJ0ZXh0MSI6IvCfm6DvuI8iLCJ0ZXh0MiI6InVidW5Ub29scyIsIndpZHRoIjoxMTAsImhlaWdodCI6MjAsImZvbnRTaXplIjoxNCwiaGludFNpemUiOjE1LCJmaWxsQ29sb3IxIjoiIzAwMDAwMCIsImZpbGxDb2xvcjIiOiIjNTAzMjUwIiwidGV4dENvbG9yMSI6IiNmZmZmZmYiLCJ0ZXh0Q29sb3IyIjoiI2ZmZmZmZiIsImRpdmlkZXJMaW5lIjozMH0=" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Frealvorl%2Fubuntools%2Fmain%2Fdocs%2Fbranding%2Fbadge.svg" alt="badge"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>docker</category>
      <category>environment</category>
      <category>efficiency</category>
    </item>
    <item>
      <title>I Built ubun🔨ools, So You Don’t Have To</title>
      <dc:creator>Viorel PETCU</dc:creator>
      <pubDate>Sun, 08 Sep 2024 09:59:21 +0000</pubDate>
      <link>https://dev.to/realvorl/i-built-ubunools-so-you-dont-have-to-ahk</link>
      <guid>https://dev.to/realvorl/i-built-ubunools-so-you-dont-have-to-ahk</guid>
      <description>&lt;p&gt;Not sure how often this happens to others, but whenever I start a new project, I like to start with a blank slate. I do this to avoid unconsciously dragging in tools or methods from other projects that might not fit into the current tech stack.&lt;/p&gt;

&lt;p&gt;So, typically, I check out the project and mount the directory into a fresh Ubuntu container like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/realvorl/ubuntools.git
&lt;span class="nb"&gt;cd &lt;/span&gt;ubuntools
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/work &lt;span class="nt"&gt;--workdir&lt;/span&gt; /work ubuntu:24.04 /bin/bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All set, right? Well, not quite. Let’s say I want to check the weather quickly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;root@20fb3c8cf2eb:/work# curl wttr.in
bash: curl: &lt;span class="nb"&gt;command &lt;/span&gt;not found &lt;span class="c"&gt;# &amp;lt;--- and this is what I am talking about&lt;/span&gt;
root@20fb3c8cf2eb:/work# 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I don’t always check the weather, but you get the point. I’ve already set everything up, but now I’m in a container where tools like &lt;code&gt;curl&lt;/code&gt;, &lt;code&gt;wget&lt;/code&gt;, &lt;code&gt;jq&lt;/code&gt;, or even a simple editor like &lt;code&gt;nano&lt;/code&gt; are missing. And if the container crashes, whatever I install now is gone. Frustrating, right?&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;The Solution: Ubun🔨ools&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;So, I built &lt;strong&gt;ubunTools&lt;/strong&gt;—a lightweight Docker image generator that packages the tools I need in these situations. No more fumbling with missing dependencies or reinstalling them every time. With ubunTools, you pull the image, and boom—you’re ready to go.&lt;/p&gt;

&lt;p&gt;Let me show you how this works:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;1&lt;/code&gt; add your list of tools to the workflow input form:  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fal3k95zeuc66n5lqgpe3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fal3k95zeuc66n5lqgpe3.png" alt="github workflow input form" width="320" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;2&lt;/code&gt; put that list into an ENV variable: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr9nex6pi6gqrk09rat5r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fr9nex6pi6gqrk09rat5r.png" alt="adding tool list to env variables" width="800" height="95"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbe82zyiyuizt69sr0269.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbe82zyiyuizt69sr0269.png" alt="adding tag to env variables" width="800" height="93"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;3&lt;/code&gt; build the parameterized image:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjyv67qsf0oafk7jbofja.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjyv67qsf0oafk7jbofja.png" alt="parameterized docker build" width="800" height="77"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;4&lt;/code&gt; push to docker hub:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyt4pthujscbylbr3p4ni.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyt4pthujscbylbr3p4ni.png" alt="using docker action to upload image" width="800" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here’s the same workflow, but this time using an image generated by ubunTools:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Swapping ubuntu:24.04 with viorelpe/ubuntools:useful_REST_tools&lt;/span&gt;
docker run &lt;span class="nt"&gt;-it&lt;/span&gt; &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;pwd&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;:/work &lt;span class="nt"&gt;--workdir&lt;/span&gt; /work viorelpe/ubuntools:useful_REST_tools /bin/bash

root@0325311145e2:/work# curl wttr.in
Weather report: 

      &lt;span class="se"&gt;\ &lt;/span&gt;  /     Clear 
       .-.      11 °C          
    ― &lt;span class="o"&gt;(&lt;/span&gt;   &lt;span class="o"&gt;)&lt;/span&gt; ―   ↑ 6 km/h       
       &lt;span class="sb"&gt;`&lt;/span&gt;-’      10 km          
      /   &lt;span class="se"&gt;\ &lt;/span&gt;    0.0 mm          ...
                                ...
┌────────────────────────────── ...
│            Morning            ...
├────────────────────────────── ...
│    &lt;span class="se"&gt;\ &lt;/span&gt; /       Partly Cloudy   ...
│  _ /&lt;span class="s2"&gt;""&lt;/span&gt;.-.     +22&lt;span class="o"&gt;(&lt;/span&gt;21&lt;span class="o"&gt;)&lt;/span&gt; °C      ...
│    &lt;span class="se"&gt;\_&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;   &lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nb"&gt;.&lt;/span&gt;   ↖ 5 km/h        ...
│    /&lt;span class="o"&gt;(&lt;/span&gt;___&lt;span class="o"&gt;(&lt;/span&gt;__&lt;span class="o"&gt;)&lt;/span&gt;  10 km           ...
│               0.0 mm | 0%     ...
└────────────────────────────── ...

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Easy, right?&lt;/strong&gt; Now, I’m good to go with all the tools I need.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Principle 1: Adding More Tools Shouldn’t Be Hard&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;One of my favorite things about ubunTools is how simple it is to expand. Need a new tool for a specific project? No problem! Just name it in the GitHub workflow inputs, trigger it, and the image will automatically update with the new tool(s). Minimal effort, maximum flexibility. It’s like a toolkit that grows with you as your needs evolve.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Principle 2: Keeping It Secure and Up-to-Date&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Security matters, especially when dealing with Docker images. That’s why ubunTools includes a mechanism that &lt;a href="https://github.com/realvorl/ubuntools/blob/main/.github/workflows/docker-image.yml#L54-L63" rel="noopener noreferrer"&gt;keeps it in sync&lt;/a&gt; with the latest Ubuntu image. If the Ubuntu image changes (like when there’s a security fix), ubunTools rebuilds the image (at midnight) and pushes it to Docker Hub under the appropriate tag - overriding the previous one. You don’t have to worry about manual updates—it keeps everything secure and fresh.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Conclusion: A Toolkit That Works for You&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Ubun🔨ools started as a personal solution to a common onboarding headache, and it’s already grown beyond what I imagined. Whether you’re probing APIs, aggregating data, or just needing a basic toolkit, ubunTools has you covered—and it’s easy to build on.&lt;/p&gt;

&lt;p&gt;Check out ubunTools on &lt;a href="https://github.com/realvorl/ubuntools" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; 🚀. Don’t want to deal with creating your own Docker Hub account? No worries! Just create an issue on GitHub, and I’ll build the image for you. It’s just a few text fields and a click—no biggie.&lt;/p&gt;




&lt;h3&gt;
  
  
  &lt;strong&gt;Final Thoughts&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;I wanted to make my life easier, and now, with ubunTools, I hope it makes yours easier too. Whether you’re a developer who likes starting projects with a clean slate, or you just need a reliable, customizable toolkit, ubunTools is there for you. Let me know if you give it a spin! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://realvorl.github.io/BadgeSVGen/?data=eyJ0ZXh0MSI6IvCfm6DvuI8iLCJ0ZXh0MiI6InVidW5Ub29scyIsIndpZHRoIjoxMTAsImhlaWdodCI6MjAsImZvbnRTaXplIjoxNCwiaGludFNpemUiOjE1LCJmaWxsQ29sb3IxIjoiIzAwMDAwMCIsImZpbGxDb2xvcjIiOiIjNTAzMjUwIiwidGV4dENvbG9yMSI6IiNmZmZmZmYiLCJ0ZXh0Q29sb3IyIjoiI2ZmZmZmZiIsImRpdmlkZXJMaW5lIjozMH0=" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CZNR0Tf6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/realvorl/ubuntools/main/docs/branding/badge.svg" alt="badge" width="110" height="20"&gt;&lt;/a&gt;&lt;/p&gt;




</description>
      <category>docker</category>
      <category>productivity</category>
      <category>automation</category>
    </item>
    <item>
      <title>I built a Free Badge Generator for Everyone</title>
      <dc:creator>Viorel PETCU</dc:creator>
      <pubDate>Sat, 31 Aug 2024 21:27:02 +0000</pubDate>
      <link>https://dev.to/realvorl/i-built-a-free-badge-generator-for-everyone-42k5</link>
      <guid>https://dev.to/realvorl/i-built-a-free-badge-generator-for-everyone-42k5</guid>
      <description>&lt;p&gt;It all started with a simple sticker. On a business trip to Hamburg, my colleagues and I came across a promotional sticker from the German county of Baden-Württemberg, which humorously boasted, &lt;strong&gt;&lt;em&gt;"nett hier, aber waren Sie schon mal in Baden-Württemberg?"&lt;/em&gt;&lt;/strong&gt; ("Nice here, but have you ever been to Baden-Württemberg?").&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.thelaend.de/en/" rel="noopener noreferrer"&gt;Have a look at the promotion&lt;/a&gt; if you ever taught about moving to or within Germany, Baden-Württemberg is a great destination.&lt;/p&gt;

&lt;p&gt;Since my team is based in Stuttgart, this little joke hit home, and we all had a good laugh. But it also sparked an idea—what if I could create similar badges or "stickers" digitally and easily share them in README files, CI/CD pipelines, or anywhere else they might bring a bit of humor or information?&lt;/p&gt;

&lt;p&gt;That idea grew into the Badge Maker Lite, a simple yet powerful tool that anyone can use to create custom badges with personalized text, colors, and layouts.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Concept: A Tool for Everyone
&lt;/h3&gt;

&lt;p&gt;The Badge Maker Lite is designed to be a free and accessible service. It’s hosted on &lt;a href="https://realvorl.github.io/BadgeSVGen/" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt;, ensuring that anyone can use it without cost or the need for a personal account. The service is straightforward: you customize your badge, preview it in real-time, and get the SVG code to use wherever you need it. The goal was to make it as easy as possible to create these badges, without requiring any complicated setup or installation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fb79rif958nqkaey3hnp4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fb79rif958nqkaey3hnp4.png" alt="Screenshot of the Badge Maker Lite on GitHub"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, for those who want a bit more control, the app has some special features. When run locally with a GitHub Personal Access Token (PAT) that has the appropriate scope, you can save and load your badges using GitHub Gists. This feature adds a layer of persistence to your badges, allowing you to easily share or reuse them later.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Badge Gallery
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;the joke&lt;/th&gt;
&lt;th&gt;other work link&lt;/th&gt;
&lt;th&gt;approved link&lt;/th&gt;
&lt;th&gt;nice touch link&lt;/th&gt;
&lt;th&gt;future ideas&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://shop.thelaend.de/sticker-set-nett-hier.html" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Frealvorl%2Frealvorl%2Fmain%2Fbadges%2Fnett-hier.svg" alt="cusotm-badge"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/dashboard"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbit.ly%2Fv-here-badge" alt="cusotm-badge"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://realvorl.github.io/BadgeSVGen/" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fbit.ly%2Fv-app-badge" alt="cusotm-badge"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.amazon.de/AhfuLife-Halloween-Decoration-Honeycomb-Decorations/dp/B0CFXHT9HK" rel="noopener noreferrer"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fraw.githubusercontent.com%2Frealvorl%2Frealvorl%2Fmain%2Fbadges%2Fboo.svg" alt="cusotm-badge"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Special Features: Gists and Short URLs
&lt;/h3&gt;

&lt;p&gt;One of the standout features of the Badge Maker Lite is its ability to save badge configurations as GitHub Gists. This is only enabled when the tool is run locally with a PAT, making it both secure and optional. By saving a badge as a Gist, you receive a unique UUID that you can later use to reload your configuration.&lt;/p&gt;

&lt;p&gt;But the fun doesn’t stop there. I’ve also been using Bitly to create short, memorable URLs that link directly to these SVGs. This makes it incredibly easy to embed badges in Markdown documents, project READMEs, or even CI/CD pipeline statuses. Here’s how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Generate Your Badge&lt;/strong&gt;: Customize your badge using the Badge Maker Lite.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save It as a Gist&lt;/strong&gt;: If running locally with a PAT, save the badge and get a UUID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a Bitly Link&lt;/strong&gt;: Use Bitly to shorten the URL of your public raw SVG hosted on GitHub. Now, you have a neat, memorable URL that can be shared or embedded wherever you like.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This process not only makes it easier to share your badges but also adds a layer of professionalism to your documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Tech Test for Something Bigger
&lt;/h3&gt;

&lt;p&gt;While the Badge Maker Lite is a fun and useful tool, it’s also a tech test for a larger project I’m planning. This project has allowed me to experiment with different technologies, workflows, and user interactions, all of which will inform the development of my next big idea. Stay tuned for more on that front—exciting things are coming!&lt;/p&gt;

&lt;h3&gt;
  
  
  Call for Contributions
&lt;/h3&gt;

&lt;p&gt;The Badge Maker Lite is open to contributions, and I’d love to see what the community can bring to the table. While I’m content with the HTML, DIVs, and CSS approach for the UI, I’m fully aware that there’s room for improvement, especially when it comes to responsiveness and modern design. If you have ideas for enhancing the UI, or if you want to contribute new features or improvements, I encourage you to jump in.&lt;/p&gt;

&lt;p&gt;I’ve intentionally kept the project open-ended, so there’s plenty of scope for innovation. And if there’s something you’d like to see but don’t have the time or skills to implement, feel free to open an issue. I’ll prioritize the ones that get the most upvotes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;What started as a joke sticker on a business trip has evolved into a fully-fledged tool that anyone can use to create custom SVG badges. Whether you’re adding a bit of humor to your documentation or displaying important status updates in your CI/CD pipelines, the Badge Maker Lite makes it easy and fun.&lt;/p&gt;

&lt;p&gt;I’m excited to see how others use this tool and where the community takes it. If you’re interested in trying it out or contributing, check out the &lt;a href="https://github.com/realvorl/BadgeSVGen" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;. Let’s build something awesome together!&lt;/p&gt;

</description>
      <category>node</category>
      <category>typescript</category>
      <category>productivity</category>
      <category>jokes</category>
    </item>
    <item>
      <title>I built a Free Badge Generator for Everyone</title>
      <dc:creator>Viorel PETCU</dc:creator>
      <pubDate>Sat, 31 Aug 2024 21:27:01 +0000</pubDate>
      <link>https://dev.to/realvorl/i-built-a-free-badge-generator-for-everyone-2bnp</link>
      <guid>https://dev.to/realvorl/i-built-a-free-badge-generator-for-everyone-2bnp</guid>
      <description>&lt;p&gt;It all started with a simple sticker. On a business trip to Hamburg, my colleagues and I came across a promotional sticker from the German county of Baden-Württemberg, which humorously boasted, &lt;strong&gt;&lt;em&gt;"nett hier, aber waren Sie schon mal in Baden-Württemberg?"&lt;/em&gt;&lt;/strong&gt; ("Nice here, but have you ever been to Baden-Württemberg?").&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.thelaend.de/en/" rel="noopener noreferrer"&gt;Have a look at the promotion&lt;/a&gt; if you ever taught about moving to or within Germany, Baden-Württemberg is a great destination.&lt;/p&gt;

&lt;p&gt;Since my team is based in Stuttgart, this little joke hit home, and we all had a good laugh. But it also sparked an idea—what if I could create similar badges or "stickers" digitally and easily share them in README files, CI/CD pipelines, or anywhere else they might bring a bit of humor or information?&lt;/p&gt;

&lt;p&gt;That idea grew into the SVG Badge Generator, a simple yet powerful tool that anyone can use to create custom badges with personalized text, colors, and layouts.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Concept: A Tool for Everyone
&lt;/h3&gt;

&lt;p&gt;The SVG Badge Generator is designed to be a free and accessible service. It’s hosted on &lt;a href="https://realvorl.github.io/BadgeSVGen/" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt;, ensuring that anyone can use it without cost or the need for a personal account. The service is straightforward: you customize your badge, preview it in real-time, and get the SVG code to use wherever you need it. The goal was to make it as easy as possible to create these badges, without requiring any complicated setup or installation.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb79rif958nqkaey3hnp4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fb79rif958nqkaey3hnp4.png" alt="Screenshot of the SVG Badge generator on GitHub" width="800" height="626"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;However, for those who want a bit more control, the app has some special features. When run locally with a GitHub Personal Access Token (PAT) that has the appropriate scope, you can save and load your badges using GitHub Gists. This feature adds a layer of persistence to your badges, allowing you to easily share or reuse them later.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Badge Gallery
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;the joke&lt;/th&gt;
&lt;th&gt;other work link&lt;/th&gt;
&lt;th&gt;approved link&lt;/th&gt;
&lt;th&gt;nice touch link&lt;/th&gt;
&lt;th&gt;future ideas&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://shop.thelaend.de/sticker-set-nett-hier.html" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--9-O3QME---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/realvorl/realvorl/main/badges/nett-hier.svg" alt="cusotm-badge" width="110" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://dev.to/dashboard"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--R1iA9UwO--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bit.ly/v-here-badge" alt="cusotm-badge" width="110" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://realvorl.github.io/BadgeSVGen/" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VdICHjk9--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://bit.ly/v-app-badge" alt="cusotm-badge" width="110" height="20"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://www.amazon.de/AhfuLife-Halloween-Decoration-Honeycomb-Decorations/dp/B0CFXHT9HK" rel="noopener noreferrer"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Z6lSbaEt--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://raw.githubusercontent.com/realvorl/realvorl/main/badges/boo.svg" alt="cusotm-badge" width="133" height="24"&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;...&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Special Features: Gists and Short URLs
&lt;/h3&gt;

&lt;p&gt;One of the standout features of the SVG Badge Generator is its ability to save badge configurations as GitHub Gists. This is only enabled when the tool is run locally with a PAT, making it both secure and optional. By saving a badge as a Gist, you receive a unique UUID that you can later use to reload your configuration.&lt;/p&gt;

&lt;p&gt;But the fun doesn’t stop there. I’ve also been using Bitly to create short, memorable URLs that link directly to these SVGs. This makes it incredibly easy to embed badges in Markdown documents, project READMEs, or even CI/CD pipeline statuses. Here’s how it works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Generate Your Badge&lt;/strong&gt;: Customize your badge using the SVG Badge Generator.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Save It as a Gist&lt;/strong&gt;: If running locally with a PAT, save the badge and get a UUID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Create a Bitly Link&lt;/strong&gt;: Use Bitly to shorten the URL of your public raw SVG hosted on GitHub. Now, you have a neat, memorable URL that can be shared or embedded wherever you like.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This process not only makes it easier to share your badges but also adds a layer of professionalism to your documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  A Tech Test for Something Bigger
&lt;/h3&gt;

&lt;p&gt;While the SVG Badge Generator is a fun and useful tool, it’s also a tech test for a larger project I’m planning. This project has allowed me to experiment with different technologies, workflows, and user interactions, all of which will inform the development of my next big idea. Stay tuned for more on that front—exciting things are coming!&lt;/p&gt;

&lt;h3&gt;
  
  
  Call for Contributions
&lt;/h3&gt;

&lt;p&gt;The SVG Badge Generator is open to contributions, and I’d love to see what the community can bring to the table. While I’m content with the HTML, DIVs, and CSS approach for the UI, I’m fully aware that there’s room for improvement, especially when it comes to responsiveness and modern design. If you have ideas for enhancing the UI, or if you want to contribute new features or improvements, I encourage you to jump in.&lt;/p&gt;

&lt;p&gt;I’ve intentionally kept the project open-ended, so there’s plenty of scope for innovation. And if there’s something you’d like to see but don’t have the time or skills to implement, feel free to open an issue. I’ll prioritize the ones that get the most upvotes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;What started as a joke sticker on a business trip has evolved into a fully-fledged tool that anyone can use to create custom SVG badges. Whether you’re adding a bit of humor to your documentation or displaying important status updates in your CI/CD pipelines, the SVG Badge Generator makes it easy and fun.&lt;/p&gt;

&lt;p&gt;I’m excited to see how others use this tool and where the community takes it. If you’re interested in trying it out or contributing, check out the &lt;a href="https://github.com/realvorl/BadgeSVGen" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt;. Let’s build something awesome together!&lt;/p&gt;

</description>
      <category>node</category>
      <category>typescript</category>
      <category>productivity</category>
      <category>jokes</category>
    </item>
    <item>
      <title>GitHub Actions for Secret Management</title>
      <dc:creator>Viorel PETCU</dc:creator>
      <pubDate>Sun, 10 Mar 2024 12:54:25 +0000</pubDate>
      <link>https://dev.to/realvorl/github-actions-for-secret-management-3p9e</link>
      <guid>https://dev.to/realvorl/github-actions-for-secret-management-3p9e</guid>
      <description>&lt;p&gt;Navigating the complexities of managing projects, especially those inherited with little documentation on API keys and secrets, presents a formidable challenge. My journey began with an urgent need to identify and securely document the secrets dispersed across repositories—a challenge compounded by frequent personnel changes and a lack of previous accountability. To tackle these issues and avert potential downtime risks associated with indiscriminately replacing tokens, I devised two GitHub Actions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://github.com/vosos/download-secret-text" rel="noopener noreferrer"&gt;&lt;strong&gt;Download Secret Text&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/vosos/mystery-token-explorer" rel="noopener noreferrer"&gt;&lt;strong&gt;Mystery Token Explorer&lt;/strong&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Initially born out of necessity, these tools have evolved to assist developers facing similar challenges more broadly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Download Secret Text
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;(Encrypted Documentation &amp;amp; Secure Sharing)&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Download Secret Text&lt;/strong&gt; offers a secure method for encrypting and documenting sensitive information. By using a GPG public key for encryption, it enables the safe sharing of secrets among team members or for archival purposes. This action stands as a testament to the critical importance of maintaining the confidentiality and integrity of project secrets.&lt;/p&gt;

&lt;p&gt;Using the action is quite intuitive:&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;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vosos/download-secret-text@v1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;gpg_public_key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ vars.GPG_PUBLIC_KEY }}&lt;/span&gt;
      &lt;span class="c1"&gt;# gpg_public_key: ${{ secrets.GPG_PUBLIC_KEY }}&lt;/span&gt;
        &lt;span class="na"&gt;plain_text&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
            &lt;span class="s"&gt;SOME_API_KEY=${{ secrets.SOME_API_KEY }}&lt;/span&gt;
            &lt;span class="s"&gt;FYE_SECRET_ONE=${{ secrets.FYE_SECRET_ONE }}&lt;/span&gt;
            &lt;span class="s"&gt;FYE_SECRET_TWO=${{ secrets.FYE_SECRET_TWO }}&lt;/span&gt;
            &lt;span class="s"&gt;SOME_TOKEN=${{ secrets.SOME_TOKEN }}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;such a step in your &lt;code&gt;workflow&lt;/code&gt; will produce this kind of result in your run &lt;code&gt;summary&lt;/code&gt;: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjb6qfm7a3dz39byo0xwd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjb6qfm7a3dz39byo0xwd.png" alt="artifact download example" width="648" height="192"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;For setting up a GPG key pair, GitHub's own &lt;a href="https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key#generating-a-gpg-key" rel="noopener noreferrer"&gt;&lt;strong&gt;&lt;em&gt;TUTORIAL&lt;/em&gt;&lt;/strong&gt;&lt;/a&gt; provides an excellent starting point. The tutorial walks you through generating the necessary public and private keys—the former for this action and the latter, combined with your passphrase, for decryption.&lt;/p&gt;

&lt;h2&gt;
  
  
  Mystery Token Explorer
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;(Shedding Light on the Shadows)&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mystery Token Explorer&lt;/strong&gt; illuminates the obscured tokens within a project by leveraging the GitHub API to fetch information about their ownership. This insight is invaluable for developers tasked with navigating projects riddled with undocumented secrets. Plans for future enhancements include expanding the use of the GitHub API to uncover additional token details (I'll get around to this soon—meanwhile, contributions via PRs and issues are encouraged).&lt;/p&gt;

&lt;p&gt;Using this action is also intuitive:&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="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Identify GitHub User&lt;/span&gt;
      &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;vosos/mystery-token-explorer@v1&lt;/span&gt;
      &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
        &lt;span class="na"&gt;jq_filter&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{name:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;.name,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;login:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;.login,&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;id:&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;.id}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;such a step in your &lt;code&gt;workflow&lt;/code&gt; will produce this kind of result in your run &lt;code&gt;summary&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo6ex3j657vp4qyrw3oj9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo6ex3j657vp4qyrw3oj9.png" alt="results are visible on summary" width="698" height="308"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Bridging GitHub's Secret Management Gap
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwp5azrpvjy16l3sgijbk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwp5azrpvjy16l3sgijbk.png" alt="Missing something?" width="800" height="695"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;GitHub's "write-only" policy for secrets safeguards security by preventing direct viewing or retrieval post-addition. &lt;strong&gt;Download Secret Text&lt;/strong&gt; cleverly circumvents this limitation, providing a "read" capability akin to GitLab's more flexible secret management approach. This functionality is particularly beneficial for teams migrating to GitHub or managing projects across different platforms, offering a unified and secure secret management solution.&lt;/p&gt;

&lt;h2&gt;
  
  
  Expanding Utility Beyond Initial Needs
&lt;/h2&gt;

&lt;p&gt;Though these GitHub Actions were created to ease project transitions, their utility extends into broader contexts:&lt;/p&gt;

&lt;h3&gt;
  
  
  Streamlining Security Audits &amp;amp; CI/CD Pipelines
&lt;/h3&gt;

&lt;p&gt;Incorporating &lt;strong&gt;Mystery Token Explorer&lt;/strong&gt; into security audits or CI/CD pipelines facilitates the automated identification and cataloging of tokens. Used alongside &lt;strong&gt;Download Secret Text&lt;/strong&gt;, it ensures secure updates to secrets, minimizing exposure risks and maintaining uninterrupted operations.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advancing Secure Documentation Practices
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Download Secret Text&lt;/strong&gt; is invaluable for teams committed to securely documenting their infrastructure, ensuring sensitive details remain accessible only to those authorized.&lt;/p&gt;

&lt;h2&gt;
  
  
  In Closing
&lt;/h2&gt;

&lt;p&gt;Inheriting projects laden with undocumented secrets need not be overwhelming. &lt;strong&gt;Mystery Token Explorer&lt;/strong&gt; and &lt;strong&gt;Download Secret Text&lt;/strong&gt; equip developers with robust tools for discovering, understanding, and securely managing digital secrets. These GitHub Actions smooth project transitions and bolster security and operational efficiency, demonstrating a proactive secret management strategy that maximizes GitHub's strengths while mitigating its constraints. As these tools progress, they highlight the potential for community-driven enhancements to forge more secure and efficient development workflows.&lt;/p&gt;

</description>
      <category>security</category>
      <category>github</category>
      <category>cicd</category>
      <category>productivity</category>
    </item>
    <item>
      <title>A Zero-Cost Monitoring Solution</title>
      <dc:creator>Viorel PETCU</dc:creator>
      <pubDate>Tue, 26 Dec 2023 23:31:58 +0000</pubDate>
      <link>https://dev.to/realvorl/a-zero-cost-monitoring-solution-12na</link>
      <guid>https://dev.to/realvorl/a-zero-cost-monitoring-solution-12na</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In the data-driven domain of content creation, having a real-time pulse on audience engagement is invaluable. My quest was to construct a tool that epitomizes both efficiency and elegance—a solution where a simple URL would serve as a portal to the metrics of my Dev.to articles. &lt;/p&gt;

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

&lt;p&gt;Manual monitoring was out of the question. The goal was to create an automated system that provides live updates on comments and reactions without the hassle of repeatedly checking each article. And importantly, this system had to be cost-free.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution
&lt;/h2&gt;

&lt;p&gt;My resolution took the form of a memorable URL: &lt;a href="//bit.ly/dev-mon"&gt;bit.ly/dev-mon&lt;/a&gt;, a nod to both its daemon-like operation and ease of recall. The choice of hosting fell on the sturdy shoulders of &lt;a href="https://pages.github.com/" rel="noopener noreferrer"&gt;GitHub Pages&lt;/a&gt;, a free platform that became the stage for my engagement dashboard.&lt;/p&gt;

&lt;h2&gt;
  
  
  TL;DR: Quick Start Guide
&lt;/h2&gt;

&lt;p&gt;Eager to see a dashboard like this?&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fririgva8da65dce90722.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fririgva8da65dce90722.png" alt="Visual overview of article interactions" width="800" height="444"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Clone the Repository&lt;/strong&gt;: Visit &lt;a href="https://github.com/realvorl/InteractoGraph" rel="noopener noreferrer"&gt;InteractoGraph&lt;/a&gt; and clone the repo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Customize for Your Profile&lt;/strong&gt;: Navigate to &lt;code&gt;./cypress/e2e/0-mine/all_in_one.cy.js#L4&lt;/code&gt; in your cloned repo and replace &lt;code&gt;realvorl&lt;/code&gt; in &lt;code&gt;https://dev.to/realvorl&lt;/code&gt; with your DEV.TO username.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Commit and Push&lt;/strong&gt;: Commit the changes with a message like "Update username in Cypress test" and push to &lt;code&gt;main&lt;/code&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Voilà! Post workflow, visit:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;https://&amp;lt;YOUR-GITHUB-USERNAME&amp;gt;.github.io/InteractoGraph/&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;for your very own engagement dashboard. Of course, you can now extend it to include anything that you can think of and are able to scrape using the Cypress API.&lt;/p&gt;

&lt;p&gt;For a deeper understanding of the setup, let's dive into the subsequent sections.&lt;/p&gt;

&lt;h3&gt;
  
  
  Automating Data Collection with Cypress
&lt;/h3&gt;

&lt;p&gt;The Cypress test below scrapes engagement data from my Dev.to profile, illustrating the ease of automation:&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="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Article Metrics on dev.to&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;captures comments and reactions for multiple articles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Visit the user's main page&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;visit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://dev.to/realvorl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Define an array to hold our article metrics&lt;/span&gt;
    &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;articleMetrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;

    &lt;span class="c1"&gt;// Wait for the articles to load&lt;/span&gt;
    &lt;span class="nx"&gt;cy&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.crayons-story__body&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;each&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;$bodies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="c1"&gt;// Extract the article title, comments, and reactions&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;h2&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&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;textContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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;links&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;$body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;getElementsByTagName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;a&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;reactionsCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&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;commentsCount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;links&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="nx"&gt;innerText&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

      &lt;span class="c1"&gt;// Add the metrics to our array&lt;/span&gt;
      &lt;span class="nx"&gt;articleMetrics&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;commentNo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;commentsCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reactionNo&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;reactionsCount&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="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="c1"&gt;// After the last article, write the metrics to a JSON file&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;index&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;$bodies&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&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="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="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./cypress/e2e/0-mine/data.json&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="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dateISO&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
          &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;articles&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;articleMetrics&lt;/span&gt;
        &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;w+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;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;It captures titles, comments, and reactions and consolidates them into a JSON file, timestamped for traceability.&lt;/p&gt;

&lt;h3&gt;
  
  
  Integrating Data with Visualization
&lt;/h3&gt;

&lt;p&gt;Using a Node.js script, &lt;code&gt;updateHtml.js&lt;/code&gt;, the data from Cypress is dynamically injected into a Google Chart within an HTML template, &lt;code&gt;chart.tmpl&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;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tmplFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chart.tmpl&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Template file path&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;htmlFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;index.html&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// The final HTML file path&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonFilePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;data.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// The JSON data file path&lt;/span&gt;

&lt;span class="c1"&gt;// Read the template HTML content and the JSON data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;htmlContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmplFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// Replace the placeholder in the template with the actual JSON data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;updatedHtmlContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;htmlContent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;// JSON_PLACEHOLDER&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="s2"&gt;`var json = &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;jsonData&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="c1"&gt;// Write the updated HTML content to the final file&lt;/span&gt;
&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;htmlFilePath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updatedHtmlContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This transforms the JSON data into an interactive chart, ready for publishing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Continuous Integration and Deployment with GitHub Actions
&lt;/h3&gt;

&lt;p&gt;The workflow I've established with GitHub Actions automates data scraping, chart updating, and deployment to GitHub Pages. Here's the workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;CI&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;branches&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;main"&lt;/span&gt; &lt;span class="pi"&gt;]&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Runs at minute 0 past every 6th hour&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*/6&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*'&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v3&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install and Run Cypress&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt; 
          &lt;span class="s"&gt;# Set up Git credentials&lt;/span&gt;
          &lt;span class="s"&gt;git config --global user.email "6973263+realvorl@users.noreply.github.com"&lt;/span&gt;
          &lt;span class="s"&gt;git config --global user.name "(Vorl)-Bot"&lt;/span&gt;
          &lt;span class="s"&gt;# Install dependencies and run tests&lt;/span&gt;
          &lt;span class="s"&gt;yarn &lt;/span&gt;
          &lt;span class="s"&gt;yarn cy-all&lt;/span&gt;
          &lt;span class="s"&gt;# Commit and push the results&lt;/span&gt;
          &lt;span class="s"&gt;git add .&lt;/span&gt;
          &lt;span class="s"&gt;git commit -am "chore (ci): update e2e test results $(date +%s)"&lt;/span&gt;
          &lt;span class="s"&gt;git push&lt;/span&gt;
          &lt;span class="s"&gt;# Branching strategy for GitHub Pages&lt;/span&gt;
          &lt;span class="s"&gt;git checkout -b gh-pages&lt;/span&gt;
          &lt;span class="s"&gt;git push --set-upstream origin gh-pages --force&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deploy to GitHub Pages&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;peaceiris/actions-gh-pages@v3&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;github_token&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.GITHUB_TOKEN }}&lt;/span&gt;
          &lt;span class="na"&gt;publish_branch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gh-pages&lt;/span&gt;
          &lt;span class="na"&gt;publish_dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./cypress/e2e/0-mine&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Upload a Build Artifact&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/upload-artifact@v4.0.0&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;chart&lt;/span&gt;
          &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./cypress/e2e/0-mine/index.html&lt;/span&gt;
          &lt;span class="na"&gt;retention-days&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The branching strategy for GitHub Pages involves force-pushing to a &lt;code&gt;gh-pages&lt;/code&gt; branch, using the &lt;a href="https://github.com/peaceiris/actions-gh-pages" rel="noopener noreferrer"&gt;&lt;code&gt;peaceiris/actions-gh-pages&lt;/code&gt;&lt;/a&gt; action to deploy the updated chart, and uploading the chart as a build artifact for quick access.&lt;/p&gt;

&lt;p&gt;The first unintended consequence, but actually my favorite feature of this implementation, is the &lt;code&gt;diff&lt;/code&gt; that you can get when you look at the git history: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbdbqy01tbei2sbx96ppw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fbdbqy01tbei2sbx96ppw.png" alt="my favorite feature" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can just click on the &lt;strong&gt;&lt;code&gt;parent&lt;/code&gt;&lt;/strong&gt; link and you can navigate, in this view, through the history of the changes. &lt;/p&gt;

&lt;p&gt;Another &lt;strong&gt;&lt;em&gt;unintended&lt;/em&gt;&lt;/strong&gt; feature is the possibility to &lt;em&gt;declutter the notifications on your phone&lt;/em&gt;. Because this can be used for virtually anything that you want to monitor, you can disable the annoying push notifications, and just do an hourly / daily digest of the engagement you get online. &lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;This automated system scrapes new data and refreshes the dashboard every 6 hours, eliminating manual effort and providing a continuously updated view of article engagement. Of course, it is not limited to DEV.TO, basically anything that you are interested in (LinkedIn), you can set up a Cypress Test, enrich the JSON with that information and follow the pattern laid out in this article.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>automation</category>
      <category>productivity</category>
    </item>
    <item>
      <title>The "Censorship Brush" - GIMP enabled Invoice Redaction</title>
      <dc:creator>Viorel PETCU</dc:creator>
      <pubDate>Wed, 20 Dec 2023 17:59:38 +0000</pubDate>
      <link>https://dev.to/realvorl/the-censorship-brush-gimp-enabled-invoice-redaction-42mh</link>
      <guid>https://dev.to/realvorl/the-censorship-brush-gimp-enabled-invoice-redaction-42mh</guid>
      <description>&lt;h2&gt;
  
  
  &lt;strong&gt;Introduction: A Holiday Challenge Turned Innovation&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;This holiday season brought more than just cheer to my doorstep; it brought a mountain of delivery invoices. As I sorted through this paper avalanche, I realized that I needed to redact some sensitive information before recycling. But there was a catch – my trusty redaction roller had run out of ink. "No problem," I thought, and hopped online to order a replacement. However, the estimated delivery time was way past the city's pre-Christmas recycling pickup schedule. &lt;/p&gt;

&lt;p&gt;In a moment of what I thought was brilliance, I decided to use my printer. Why not print something over the sensitive parts? And what better than the seemingly endless digits of Pi? After all, &lt;strong&gt;6766&lt;/strong&gt; &lt;a href="https://www.piday.org/million" rel="noopener noreferrer"&gt;decimal places of Pi&lt;/a&gt; on an invoice seemed like a foolproof plan. It was a joyous moment of geeky triumph until I proudly presented my creation to my wife. She was less than impressed. "Isn't that a bit... wasteful?" she pointed out, both in terms of toner and recycling efficiency.&lt;/p&gt;

&lt;p&gt;Her comment got me thinking. There had to be a smarter way to do this, a way that was economical yet effective. Because this is a lot of toner that you just waste, when in fact you only want a few lines or sections redacted &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fyz8i3drbwars17149ilb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fyz8i3drbwars17149ilb.png" alt="6766 digits of pi"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then it hit me – GIMP, the versatile image manipulation program, had all the tools I needed. I recalled a YouTube video about creating custom brushes in GIMP. With a spark of inspiration, I scanned an old, redacted page to capture the pattern left by the roller. From there, it was a matter of putting those GIMP skills to work and crafting a custom censorship brush.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fs6j9df2mi1vkol0io161.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fs6j9df2mi1vkol0io161.png" alt="Scanned image of the redacted paper to show the original pattern"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;What followed was an interesting journey of digital DIY and a testament to the power of open-source software. In this article, I'll guide you through the steps to create your own censorship brush in GIMP, turning a simple software tool into a powerful ally for maintaining your privacy.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F2k4fkoum4iwlmay0lrup.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F2k4fkoum4iwlmay0lrup.png" alt="GIMP logo or opening screen"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Getting Started: GIMP Setup and Initial Preparations&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;⚠️ Disclaimer ⚠️&lt;/strong&gt; While this method has worked well for me, please be aware that using crumpled or improperly aligned invoices in your printer could cause damage. I share my experience for informational purposes and cannot guarantee the same results for everyone. Always use caution and at your own risk.&lt;/p&gt;

&lt;p&gt;First things first, you will need &lt;a href="https://www.gimp.org/downloads/" rel="noopener noreferrer"&gt;GIMP&lt;/a&gt;, a scanner, or a very good lighting setup for your smartphone. I used my Brother DCP-1610 wireless printer and scanner. &lt;/p&gt;

&lt;p&gt;The only other thing you will need is an image of random letters (here you can customize to your liking) &lt;/p&gt;

&lt;p&gt;For example: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

KVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQ
VAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQK
AMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKV
MQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVA
QKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAM
KVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQKVAMQ


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;OR: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

REDACTEDREDACTEDREDACTEDREDACTEDREDACTEDREDACTEDRE
EREDACTEDREDACTEDREDACTEDREDACTEDREDACTEDREDACTEDR
TEREDACTEDREDACTEDREDACTEDREDACTEDREDACTEDREDACTED
CTEREDACTEDREDACTEDREDACTEDREDACTEDREDACTEDREDACTE
ACTEREDACTEDREDACTEDREDACTEDREDACTEDREDACTEDREDACT
DACTEREDACTEDREDACTEDREDACTEDREDACTEDREDACTEDREDAC


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This you can screenshot for later use in the creation of the censorship brush.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Creating the Tool: Crafting a Custom Censorship Brush in GIMP&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;For anything, you might ever need to implement using GIMP, there is no better resource than &lt;a href="https://www.youtube.com/@LogosByNick" rel="noopener noreferrer"&gt;Logos by Nick&lt;/a&gt; on YouTube.&lt;/p&gt;

&lt;p&gt;The following is the exact tutorial I used to implement the brush&lt;/p&gt;

&lt;p&gt;&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/TAP56mhF-rQ"&gt;
&lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;Towards the end of the tutorial, you are shown how to install the brush, but Nick is using Windows, if you are on Linux (like me) you would need to install the new brush at a location similar to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;/usr/share/gimp/2.0/brushes&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;The Redaction Process: Preparing and Censoring Your Invoice&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;Now that your GIMP has a new superpower, you are only a couple of steps away from perfectly censoring any invoice that is compatible with your printer! &lt;/p&gt;

&lt;p&gt;Now you can scan the invoice(s) and get to work. &lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;&lt;em&gt;Step 1: Scan the invoice&lt;/em&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;This step is self-explanatory.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2F8fiqt2cwbjvl947x2pnw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2F8fiqt2cwbjvl947x2pnw.png" alt="the scanned invoice"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;&lt;em&gt;Step 2: Mark the places you want to redact&lt;/em&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Start by &lt;code&gt;adding a new layer&lt;/code&gt; fill it with &lt;code&gt;white&lt;/code&gt; and &lt;code&gt;reduce the opacity&lt;/code&gt; to &lt;strong&gt;50%&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fdyw0zagvjafvaukdom9q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fdyw0zagvjafvaukdom9q.png" alt="named layers and opacity setting"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Make sure the new top layer is selected, choose the rectangle select tool, and, with the &lt;code&gt;shift&lt;/code&gt; key pressed start selecting areas you would like to redact. &lt;/p&gt;

&lt;p&gt;Now that you have multiple areas selected, you can choose your new censorship brush, select the appropriate size and start stamping (do not click and drag) click, reposition and click again. &lt;/p&gt;

&lt;p&gt;When all is done, you will end up with something like this&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fon2s0yfci2dxwb7a3ott.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fon2s0yfci2dxwb7a3ott.png" alt="layers half transparent"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  &lt;strong&gt;&lt;em&gt;Step 3: The Finish&lt;/em&gt;&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;All that is now left to do is to &lt;strong&gt;delete the invoice layer&lt;/strong&gt; and &lt;strong&gt;restore the opacity to 100%&lt;/strong&gt; for the redaction layer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fsjy5ewcnmmdkhrva62c4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fsjy5ewcnmmdkhrva62c4.png" alt="the censoring template"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now put the invoice in your printer on top of your paper stack (facing the right way) and &lt;code&gt;PRINT&lt;/code&gt;, make sure to &lt;code&gt;Ignore Page Margins&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.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%2Fet6a0tnsov5yy95zkquh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.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%2Fet6a0tnsov5yy95zkquh.png" alt="print dialog"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations 🎉 you have now perfectly censored an invoice, so you can throw it in the recycle bin free of any remorse, both from a privacy point of view and also for the amount of wasteful toner. &lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;strong&gt;Conclusion: Towards a More Automated and Efficient Future&lt;/strong&gt;
&lt;/h2&gt;

&lt;p&gt;As we've seen, GIMP is not just an image manipulation tool; it's also a powerful ally in maintaining privacy and reducing waste. This DIY approach to invoice censorship showcases the versatility and potential of open-source software in everyday tasks.&lt;/p&gt;

&lt;p&gt;Looking ahead, there are exciting possibilities to further streamline and automate this process. Imagine leveraging &lt;a href="https://www.gimp.org/tutorials/Automate_Editing_in_GIMP/" rel="noopener noreferrer"&gt;GIMP's API&lt;/a&gt; to integrate with an automated system that detects and redacts sensitive information on invoices. This could be integrated with hardware solutions, enabling a seamless scan, censor, and print workflow.&lt;/p&gt;

&lt;p&gt;I'm also considering creating a repository of censorship masks tailored for different types of invoices, making it even easier for others to adopt this method without starting from scratch.&lt;/p&gt;

&lt;p&gt;WOW you are still reading !? &lt;/p&gt;

&lt;h2&gt;
  
  
  👀
&lt;/h2&gt;

&lt;p&gt;Respect!  &lt;/p&gt;

&lt;p&gt;Did this guide help you create your own censorship brush in GIMP? Do you have other innovative ideas for hacking one thing to do another unintended thing? Share your thoughts, experiences, or any questions in the comments below. &lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>opensource</category>
      <category>productivity</category>
    </item>
    <item>
      <title>🌱 Introducing the Climate Positive Public License! 🌱</title>
      <dc:creator>Viorel PETCU</dc:creator>
      <pubDate>Mon, 16 Oct 2023 23:09:52 +0000</pubDate>
      <link>https://dev.to/realvorl/introducing-the-climate-positive-public-license-b2b</link>
      <guid>https://dev.to/realvorl/introducing-the-climate-positive-public-license-b2b</guid>
      <description>&lt;h2&gt;
  
  
  Hello everyone out there using open-source licenses,
&lt;/h2&gt;

&lt;p&gt;I'm working on a new kind of license I'd like to present: the &lt;strong&gt;Climate Positive Public License (&lt;code&gt;CPPL&lt;/code&gt;)&lt;/strong&gt;. It's still in its early stages, much like the weather outside, unpredictable and ever-changing. However, I believe it holds a lot of promise.&lt;/p&gt;

&lt;p&gt;Why another license, you might ask? Well, the goal isn't just to create software, but to do so with a climate-positive footprint. The &lt;code&gt;CPPL&lt;/code&gt; is designed to promote climate-friendly software practices, CO2 neutrality, carbon capturing, and more. It's an endeavor that's not just for me; it's for our planet.&lt;/p&gt;

&lt;p&gt;The license is very much influenced by the GPL, and the core idea is simple: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Use this license for your software, and in return, make sure your software or project actively contributes to the betterment of the environment.&lt;br&gt;&lt;br&gt;
If not, support the cause in another manner, through environmental funds or other means.&lt;br&gt;&lt;br&gt;
In essence, &lt;strong&gt;it's a license with a conscience&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I won't claim the &lt;code&gt;CPPL&lt;/code&gt; to be the solution to all our environmental concerns. I'm not even sure if it will gain traction, but I'm hopeful. What I do know is that it's another tool in the toolbox for developers who are conscious about the environment and wish to make a positive impact through their work.&lt;/p&gt;

&lt;p&gt;I'd appreciate any feedback, ideas, or contributions. The project is open-source and is very much a collaborative effort. If you're curious or want to contribute, the repo is available on GitHub.&lt;/p&gt;

&lt;p&gt;Would love to hear what you think!&lt;br&gt;&lt;br&gt;
Maybe &lt;a href="https://github.com/realvorl/ClimatePositivePublicLicense" rel="noopener noreferrer"&gt;take a look on GitHub&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>software</category>
      <category>decarbonization</category>
      <category>opensource</category>
      <category>license</category>
    </item>
  </channel>
</rss>
