<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Daniel Doblado</title><description>Personal site of Daniel Doblado</description><link>https://dobla.do/</link><language>en</language><managingEditor>danieldoblado@gmail.com (Daniel Doblado)</managingEditor><webMaster>danieldoblado@gmail.com (Daniel Doblado)</webMaster><lastBuildDate>Wed, 06 May 2026 11:58:18 GMT</lastBuildDate><generator>Astro</generator><item><title>Fixing a Yamaha DGX-620 grand piano screen</title><link>https://dobla.do/blog/fixing-a-yamaha-dgx-620-screen/</link><guid isPermaLink="true">https://dobla.do/blog/fixing-a-yamaha-dgx-620-screen/</guid><description>How I got my piano and the problems of fixing it</description><pubDate>Sun, 01 Jan 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import YoutubeVideo from &quot;#astro/components/YoutubeVideo.astro&quot;;&lt;/p&gt;
&lt;h2&gt;Introduction about the piano&lt;/h2&gt;
&lt;p&gt;I bought a second hand &lt;a href=&quot;https://en.wikipedia.org/wiki/Yamaha_DGX-620&quot;&gt;Yamaha DGX-620 Grand Piano&lt;/a&gt; also known as the &amp;lt;i&amp;gt;YPG-625&amp;lt;/i&amp;gt;, the &amp;lt;abbr title=&quot;Yamaha Portable Grand&quot;&amp;gt;YPG&amp;lt;/abbr&amp;gt; stands for Yamaha Portable Grand, sure portable..., after carying it for 10 minutes by myself my back hurt for 3 days.&lt;/p&gt;
&lt;h2&gt;Opinions and problems&lt;/h2&gt;
&lt;p&gt;Overall I&apos;m very happy with it, while I still don&apos;t know much about playing paino, it does look and sounds great, but it came with problem that bothered me a lot, that the seller did not mention it until I went to buy it.&lt;/p&gt;
&lt;p&gt;The screen was broken, it&apos;s a common known manufacturing problem for these models that looking online affects a lot of people, fortunately due to this reason I got a huge discount, only 70€ to buy the full piano, there&apos;s people selling it with a broken screen for around 200 € minimum, the cost new was of ~700€ so a 90% discount.&lt;/p&gt;
&lt;p&gt;The problem is very simple, the ribbon connector for he display loses connection overtime, due to a very thin solder that&apos;s in tension.&lt;/p&gt;
&lt;h2&gt;Possible first solution&lt;/h2&gt;
&lt;p&gt;This video shows a very simple solution which seems to work for most who tried, simply add foldback clips to increase the preassure of the connection.&lt;/p&gt;
&lt;p&gt;&amp;lt;YoutubeVideo
loading=&quot;eager&quot;
videoId=&quot;JNsmoBEO4nI&quot;
videoTitle=&quot;Yamaha YPG / DGX LCD Screen Fix, Easy &amp;amp; Fast&quot;
/&amp;gt;&lt;/p&gt;
&lt;p&gt;This solution sounds great, low repair cost (only 2 foldback clips), without much risk besides open it, so I tried.&lt;/p&gt;
&lt;p&gt;After removing around 100 screws (that was painful), the case can be removed and the board can be accesed.&lt;/p&gt;
&lt;p&gt;Now that&apos;s open the process it&apos;s easy, after remvoving the ribbons and the 4 screws around the board, the main board will go out and the display can be accessed.
The display has two ribbons that each control half of the screen, I had to fix both in my case, so this requires to remove 2 additional screws to acces the second ribbon.&lt;/p&gt;
&lt;p&gt;I isolated the clips with masking tape, to make sure the circuits will not short with the metalic clips.&lt;/p&gt;
&lt;p&gt;After this I checked the screen and unfortunately it only fixed half of it, I tried adding more pressure, but nothing worked, so I gave up after a while.&lt;/p&gt;
&lt;h2&gt;Second option&lt;/h2&gt;
&lt;p&gt;At this poin I thought my only solution was to replace the screen, this was problematic for two reasons, number one, a new screen cost a minimum of &lt;a href=&quot;https://de.aliexpress.com/item/1005004712449602.html&quot;&gt;60€ on aliexprss&lt;/a&gt; sometimes as much as I paid for the whole piano, reson two is that it requires an iron solder.&lt;/p&gt;
&lt;p&gt;I did want an excuse to buy a &lt;a href=&quot;https://pine64.com/product/pinecil-smart-mini-portable-soldering-iron/&quot;&gt;PINECIL&lt;/a&gt;, an amazing tool, but this will add to the cost, and I decided it was not worth it, besides the piano works great, and the MIDI input can be used without a screen.&lt;/p&gt;
&lt;p&gt;Here it&apos;s when I thought of a differnet solution that so far I didn&apos;t see online, if the solder had desolder why not try to melt it, I do not have a heat gun but I do have a hair dryer so I gave it a try.&lt;/p&gt;
&lt;p&gt;The process is almost the same but instead of adding pins for preassure, I just heated both screen connectors for around 5 minutes using the hair dryer&apos;s max temperature, somtimes adding preassure with the plasticl nozzle.&lt;/p&gt;
&lt;p&gt;After mounting it again I got a gorgeous working screen.&lt;/p&gt;
&lt;p&gt;&amp;lt;YoutubeVideo
videoId=&quot;fPGboN25uWo&quot;
videoTitle=&quot;Yamaha DGX-620 Grand Piano Screen Repair&quot;
/&amp;gt;&lt;/p&gt;
</content:encoded><category>piano</category><category>repair</category><category>Yamaha DGX-620</category><category>screen</category><category>DIY</category><category>hardware</category><author>Daniel Doblado</author></item><item><title>Why newsletters are not ideal</title><link>https://dobla.do/blog/why-newsletters-are-not-ideal/</link><guid isPermaLink="true">https://dobla.do/blog/why-newsletters-are-not-ideal/</guid><description>I don&apos;t like newsletters, and why I prefer RSS</description><pubDate>Tue, 10 May 2022 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Problems&lt;/h2&gt;
&lt;p&gt;You enter a site, and the first thing you see is a popup asking for your email, that&apos;s plain rude, or even worse, you start reading the article, and it pops up asking and interrupting your reading.&lt;/p&gt;
&lt;p&gt;Please add the banner at the end of the article; if your article is good, I might give it a try.&lt;/p&gt;
&lt;p&gt;Let&apos;s say that I agree and write my email; now I have to trust that the site won&apos;t be hacked, or directly sell my email to a third party.&lt;/p&gt;
&lt;p&gt;I might get your new information in a neat and nice email, but again, it&apos;s your choice to send spam, and let&apos;s say you do, this could happen with RSS, but there&apos;s a main difference, that&apos;s unsubscribing, and here is where the problem begins.&lt;/p&gt;
&lt;p&gt;There are so many sites that have broken unsubscribe pages, it&apos;s a link that devs don&apos;t usually visit and rarely gets any maintenance, so if it does not work now, I have to contact the company directly, and if I&apos;m lucky, it will be sorted out, if not, I have to completely block your email address, which might be a problem if I want other updates.&lt;/p&gt;
&lt;h2&gt;Why RSS is better&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://de.wikipedia.org/wiki/RSS_(Web-Feed)&quot;&gt;RSS&lt;/a&gt; completely avoids all those problems.&lt;/p&gt;
&lt;p&gt;It does not ask you to send your email; you gather the data whenever you want, and your email is kept private, there&apos;s no need to trust the site with privacy.&lt;/p&gt;
&lt;p&gt;Unsubscribe is always on your side; just remove the URL from your client and you&apos;re done, no need to contact anyone, no need to trust the site to do it for you.&lt;/p&gt;
&lt;p&gt;RSS can be tailored; some sites separate their feeds by category, so you can choose what you want to read, yes there are some newsletters with preferences that require an account again more work for both, but with RSS, you just choose a URL and be done with it.&lt;/p&gt;
&lt;h2&gt;Advantages of newsletters&lt;/h2&gt;
&lt;p&gt;Being fair, probably all people on the internet have an email, which makes it easier to reach, and most people don&apos;t know what RSS is, so it&apos;s easier to explain to someone to subscribe to a newsletter than to an RSS feed.&lt;/p&gt;
&lt;p&gt;There are more email providers giving completely free service than RSS readers, the moment you want to sync between devices, you have to pay for a service, or self-host, while email is mostly free.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;RSS declined with social platforms removing support, especially when Google Reader decided to close, and when browsers removed integrated support for it (yes Firefox I&apos;m looking at you).&lt;/p&gt;
&lt;p&gt;If you care about privacy, RSS is the way to go, and it&apos;s sad that the tech that respects users the most is dying.&lt;/p&gt;
&lt;p&gt;I&apos;m extending this section now with other articles that promote RSS:
- &lt;a href=&quot;https://reedybear.bearblog.dev/ive-been-advocating-for-rss-support-and-you-should-too/&quot;&gt;I&apos;ve been advocating for RSS support, and you should too&lt;/a&gt;
- &lt;a href=&quot;https://joeyehand.com/blog/2025/01/15/i-ditched-the-algorithm-for-rssand-you-should-too/&quot;&gt;I Ditched the Algorithm for RSS—and You Should Too&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>opinion</category><category>newsletter</category><category>email</category><category>rss</category><author>Daniel Doblado</author></item><item><title>Writing browser extensions without compiling</title><link>https://dobla.do/blog/writing-browser-extensions-without-compiling/</link><guid isPermaLink="true">https://dobla.do/blog/writing-browser-extensions-without-compiling/</guid><description>Positive development changes for one of my extensions.</description><pubDate>Thu, 24 Jun 2021 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Complexity is the enemy&lt;/h2&gt;
&lt;p&gt;Recently one of my most popular &lt;a href=&quot;https://github.com/dobladov/youtube2Anki&quot;&gt;extensions&lt;/a&gt; received a full refactor, previously it was made with just pure JavaScript; this was fine since at the beginning it was a proof of concept, it means no complex bundles, frameworks, compilers, ...&lt;/p&gt;
&lt;p&gt;But with complexity comes necessity, managing the state of the connection between the extension and Anki and editing the sentences got complicated using imperative logic; It&apos;s amazing how used I became to declarative logic with &lt;a href=&quot;https://reactjs.org/&quot;&gt;React&lt;/a&gt; and this is something that I was missing for this extension.&lt;/p&gt;
&lt;p&gt;I could use React, the problem is that if the extension needs to be compiled, the review in the extensions stores will be more exhaustive, taking more time and making it longer to fix simple stuff like bugs.&lt;/p&gt;
&lt;p&gt;So in order to build trust and avoid wasting reviewer&apos;s time, all the code needed to stay just as it was written.&lt;/p&gt;
&lt;p&gt;Tools like &lt;a href=&quot;https://www.snowpack.dev/&quot;&gt;snowpack&lt;/a&gt; made me realize that with the advance of JavaScript modules, we might not need to compile what it&apos;s an interpreted language, seriously how did we reach this point.&lt;/p&gt;
&lt;h2&gt;Coming with simpler solutions&lt;/h2&gt;
&lt;p&gt;Let&apos;s look at the requirements, I wanted to use components, separate my logic from styles, use declarative logic, not set up any bundler (webpack, parcel, etc...) for this React would not make the cut since JSX needs some tooling and care.&lt;/p&gt;
&lt;p&gt;So the decision went to &lt;a href=&quot;https://skruv.io/&quot;&gt;Skruv&lt;/a&gt;, this framework is amazing, not only is tiny, modular and simple to learn, the docs are amazing well organized and clear, covering all functionality without extras.&lt;/p&gt;
&lt;p&gt;You can tell how nice the framework is by looking at this framework&apos;s goals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;No build time or runtime dependencies, no parsers&lt;/li&gt;
&lt;li&gt;Pretty small:
&lt;ul&gt;
&lt;li&gt;~350 LOC vDOM&lt;/li&gt;
&lt;li&gt;~100 LOC State management&lt;/li&gt;
&lt;li&gt;~300 LOC HTML/SVG helpers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Useable without bundling/compilation/transpilation&lt;/li&gt;
&lt;li&gt;Fast enough for most normal use cases: &lt;a href=&quot;https://krausest.github.io/js-framework-benchmark/2023/table_chrome_116.0.5845.82.html&quot;&gt;benchmark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Supports async components like &lt;code&gt;import()&lt;/code&gt; and &lt;code&gt;async&lt;/code&gt; generators&lt;/li&gt;
&lt;li&gt;CSS scoping via shadow DOM&lt;/li&gt;
&lt;li&gt;Hopefully grokable/understandable code&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The best part is that to use it I just have to import the files, that&apos;s it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { div } from &quot;./skruv/html.js&quot;;
import { renderNode } from &quot;./skruv/vDOM.js&quot;;

let root = document.querySelector(&quot;#root&quot;);

root = renderNode(div({}, &quot;Hello world!&quot;), root);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;On top of those advantages, I love the nested syntax, far shorter than JSX, now that I think about it feels weird that we moved to write HTML in JS.&lt;/p&gt;
&lt;p&gt;You might be also wondering, &quot;what if I need typescript, do you have to transpile?&quot; the answer is no, you can use &lt;a href=&quot;https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html&quot;&gt;JSDocs with Typescript&lt;/a&gt; without having to create a single &lt;code&gt;ts&lt;/code&gt; file.&lt;/p&gt;
&lt;h2&gt;How it ended&lt;/h2&gt;
&lt;p&gt;I feel this change was a success, new code can be iterated easily without having to worry much about state or building, since the extension runs on modern browsers I don&apos;t have to care about babel, etc&lt;/p&gt;
&lt;p&gt;Right now the only concern I have about the extension is &lt;em&gt;manifest v3&lt;/em&gt; which is not supported in Firefox yet, but this is a story for another post.&lt;/p&gt;
&lt;p&gt;I have to say that I&apos;m glad about the feedback people gave, this refactor was motivated because changes on Youtube made it fail and people reported issues with a lot of support, not only that, but I&apos;ve seen articles written in Vietnamese, Japanese and German in how to use the extension and how they find it useful, it really makes me want to keep contributing free, anti-tracking open-source code.&lt;/p&gt;
</content:encoded><category>javascript</category><category>extensions</category><category>tooling</category><category>framework</category><category>firefox</category><category>chrome</category><category>manifest</category><author>Daniel Doblado</author></item><item><title>How to configure a 4k screen in Linux</title><link>https://dobla.do/blog/how-to-configure-a-4k-screen-in-linux/</link><guid isPermaLink="true">https://dobla.do/blog/how-to-configure-a-4k-screen-in-linux/</guid><description>Troubles with your screen?, here is how to fix it.</description><pubDate>Sat, 18 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;You just got a new cool 4K monitor, you opened it, connected it to your Linux device and the first thing you face is that you can&apos;t select the maximum resolution for the screen.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;My disappointment is immeasurable and my day is ruined. --Reviewbrah&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Why is this problem happening?&lt;/h2&gt;
&lt;p&gt;Apparently some screens might not give the correct &lt;a href=&quot;https://en.wikipedia.org/wiki/Extended_Display_Identification_Data&quot;&gt;EDID&lt;/a&gt; information, in my case a &lt;a href=&quot;https://www.samsung.com/us/business/support/owners/product/ue850-series-u28e850r/&quot;&gt;SAMSUNG LU28E85KRS&lt;/a&gt;, this makes it impossible to select all supported modes for our screen, showing some default ones, without including resolutions greater than 1920x1080.&lt;/p&gt;
&lt;h2&gt;How to get the correct EDID and set it using xrandr&lt;/h2&gt;
&lt;h3&gt;Getting the correct EDID&lt;/h3&gt;
&lt;p&gt;Thanks to this wonderful answer &lt;a href=&quot;https://unix.stackexchange.com/a/323121&quot;&gt;How to get EDID for a single monitor?&lt;/a&gt; by &lt;a href=&quot;https://unix.stackexchange.com/users/32896/malat&quot;&gt;Malat&lt;/a&gt;, obtaining the correct information is easy.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install read-edid
sudo get-edid | parse-edid
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cool, we get all supported modes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Modeline    &quot;Mode 1&quot; 148.500 1920 2008 2052 2200 1080 1084 1089 1125 +hsync +vsync
Modeline    &quot;Mode 0&quot; 297.00 3840 4016 4104 4400 2160 2168 2178 2250 +hsync +vsync
Modeline    &quot;Mode 2&quot; 74.250 1280 1390 1420 1650 720 725 730 750 +hsync +vsync
Modeline    &quot;Mode 3&quot; 148.500 1920 2448 2492 2640 1080 1084 1089 1125 +hsync +vsync
Modeline    &quot;Mode 4&quot; 74.250 1280 1720 1760 1980 720 725 730 750 +hsync +vsync
Modeline    &quot;Mode 5&quot; 27.027 720 736 798 858 480 489 495 525 -hsync -vsync
Modeline    &quot;Mode 6&quot; 27.000 720 732 796 864 576 581 586 625 -hsync -vsync
Modeline    &quot;Mode 7&quot; 74.250 1920 2558 2602 2750 1080 1084 1089 1125 +hsync +vsync
Modeline    &quot;Mode 8&quot; 74.250 1920 2008 2052 2200 1080 1084 1089 1125 +hsync +vsync
Modeline    &quot;Mode 9&quot; 148.50 1920 2008 2052 2200 1080 1084 1089 1125 +hsync +vsync
Modeline    &quot;Mode 10&quot; 148.50 1920 2448 2492 2640 1080 1084 1089 1125 +hsync +vsync
Modeline    &quot;Mode 11&quot; 74.25 1280 1390 1430 1650 720 725 730 750 +hsync +vsync
Modeline    &quot;Mode 12&quot; 241.50 2560 2608 2640 2720 1440 1443 1448 1481 +hsync -vsync
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Adding the new modes with xrandr&lt;/h3&gt;
&lt;p&gt;The first value after the mode number tell us the horizontal resolution, in my case I&apos;m interested in &lt;code&gt;Mode 0&lt;/code&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Modeline    &quot;Mode 0&quot; 297.00 3840 4016 4104 4400 2160 2168 2178 2250 +hsync +vsync
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let&apos;s create a new mode, we can name it anything we want, but I prefer to name it after the output resolution.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xrandr --newmode &quot;3840x2160&quot; 297.00 3840 4016 4104 4400 2160 2168 2178 2250 +hsync +
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we add the mode to the output, if you are not sure which one is the output, run &lt;code&gt;xrandr&lt;/code&gt; and it will show something like &lt;code&gt;HDMI-2 connected&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;We use the name of the output.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;xrandr --addmode HDMI-2 3840x2160
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now the new resolution should be available in the Display settings.&lt;/p&gt;
&lt;p&gt;Don&apos;t forget to set font DPI and screen scale to your taste.&lt;/p&gt;
&lt;h2&gt;Useful information&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://wiki.archlinux.org/index.php/Xrandr&quot;&gt;Arch wiki xrandr&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/datagrok/410d26e24ec51159cdfd2a400b809705&quot;&gt;Automate setting the screen&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>4k</category><category>screen</category><category>Xrandr</category><category>KDE</category><author>Daniel Doblado</author></item><item><title>Crawling data from dynamically generated sites</title><link>https://dobla.do/blog/crawling-data-from-dynamically-generated-sites/</link><guid isPermaLink="true">https://dobla.do/blog/crawling-data-from-dynamically-generated-sites/</guid><description>How to get data from other sites using free online services</description><pubDate>Fri, 14 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Let&apos;s start with the need, why I had the necessity to do this,
Recently I created this &lt;a href=&quot;https://observablehq.com/@dobladov/coronavirus-2019-ncov&quot;&gt;Corona Virus Map&lt;/a&gt;, I wanted to be the fastest getting the data from the source site in Chinese, most of the other sites get the data periodical using a cron task and dumping their data locally.&lt;/p&gt;
&lt;p&gt;In my case I wanted to get their data every time the map is loaded, ensuring more accurate results and completely avoiding any manual update of data.&lt;/p&gt;
&lt;h2&gt;Parsing the site&lt;/h2&gt;
&lt;p&gt;So my first try was to curl the page and sadly the results for this &lt;a href=&quot;https://ncov.dxy.cn/ncovh5/view/pneumonia&quot;&gt;site&lt;/a&gt; will not load unless you use Javascript since the tables are dynamically generated.&lt;/p&gt;
&lt;p&gt;This leave us with not many options but to use a browser, so in my case I decided to use Puppeteer, a headless Chrome browser to get the data.&lt;/p&gt;
&lt;p&gt;So after a while playing with it I got the code to do it, it will download and log the data in the format that I need.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const fs = require(&quot;fs&quot;);
const puppeteer = require(&quot;puppeteer&quot;);

(async () =&amp;gt; {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.goto(&quot;https://ncov.dxy.cn/ncovh5/view/pneumonia&quot;);
  const data = await page.evaluate((body) =&amp;gt; {
    [...document.querySelectorAll(&quot;.close___2yTiY:not(.open___3it7L)&quot;)].forEach(
      (e) =&amp;gt; e.click(),
    );
    return [
      ...document
        .querySelectorAll(&quot;.areaBox___3jZkr&quot;)[1]
        .querySelectorAll(&quot;.areaBlock2___27vn7&quot;),
    ].map((row) =&amp;gt; {
      const [area, , confirmed, dead, cured] = [...row.querySelectorAll(&quot;p&quot;)];
      return {
        area: area.innerText,
        confirmed: +confirmed.innerText || 0,
        dead: +dead.innerText || 0,
        cured: +cured.innerText || 0,
      };
    });
  });
  console.log(data);
  fs.writeFileSync(
    `2020-02-16T21:21:26.466Z.json`,
    JSON.stringify(data, null, 2),
  );
  await browser.close();
})();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After launching this script with &lt;code&gt;node crawler.js&lt;/code&gt; I get a file that I can use as the source data of my visualization.&lt;/p&gt;
&lt;p&gt;All fine but now I have another need, how to get this data every time I load the map?&lt;/p&gt;
&lt;p&gt;For this I definitely need a server running but for this case I wanted to see what can I do with free services.&lt;/p&gt;
&lt;h2&gt;Using Glitch&lt;/h2&gt;
&lt;p&gt;My first approach was to use &lt;a href=&quot;glitch.com&quot;&gt;Glitch&lt;/a&gt;, after setting a express server and a few tries later I got my data every time I curl this url: https://coronacrawler.glitch.me&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const express = require(&quot;express&quot;);
const app = express();
const puppeteer = require(&quot;puppeteer&quot;);

app.use(express.static(&quot;public&quot;));

app.get(&quot;/&quot;, async function (request, response) {
  try {
    const browser = await puppeteer.launch({
      args: [&quot;--no-sandbox&quot;],
    });

    // Same code to get the data before

    res.json(data);
  } catch (error) {
    console.log(error);
  }
});

const listener = app.listen(process.env.PORT, function () {
  console.log(&quot;Your app is listening on port &quot; + listener.address().port);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cool, but this creates another problem.&lt;/p&gt;
&lt;h3&gt;Cross-origin&lt;/h3&gt;
&lt;p&gt;The map running in ObservableHQ needs to crawl this data and unfortunately if you curl a different domain from a website it needs to have Cross-origin headers enabled or for security reasons it will not load, after adding this to our express server it works.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;app.use(function (req, res, next) {
  res.header(&quot;Access-Control-Allow-Origin&quot;, &quot;*&quot;);
  res.header(
    &quot;Access-Control-Allow-Headers&quot;,
    &quot;Origin, X-Requested-With, Content-Type, Accept&quot;,
  );
  next();
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Alternative to Cross-origin&lt;/h4&gt;
&lt;p&gt;Another solution if adding the headers is impossible (For example in a server we can&apos;t control), we can use a service like &lt;a href=&quot;https://cors-anywhere.herokuapp.com/&quot;&gt;cors-anywhere&lt;/a&gt;, just changing the URL to be:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://cors-anywhere.herokuapp.com/https://coronacrawler.glitch.me
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Problems with Glitch&lt;/h4&gt;
&lt;p&gt;Nice, now I have a server running and I can query the data, what else can I ask for? Well while this works there are two main problems, Glitch is more of a place for playing with code and experimenting so it will not be fast.&lt;/p&gt;
&lt;p&gt;Glitch servers go to sleep, it&apos;s perfectly understandable, it makes no sense for Glitch to give computing power for free running all the time, after some period of inactivity the machine will go to sleep and it needs to be awaken the next time, this makes the map unresponsive if there are not many queries and it gives the sensation of being more slow than it is.&lt;/p&gt;
&lt;p&gt;At this point I moved to zeit.co&lt;/p&gt;
&lt;h2&gt;Using Zeit.co&lt;/h2&gt;
&lt;p&gt;Why use zeit.co when Glitch is working?&lt;/p&gt;
&lt;p&gt;This service offers quite a nice free &quot;Hobby&quot; service allowing to use serverless functions.&lt;/p&gt;
&lt;h3&gt;Creating a serverless function&lt;/h3&gt;
&lt;p&gt;We need to modify the previous script a little since we have other constrains.&lt;/p&gt;
&lt;p&gt;First we need a start point for the api query in this case we need a file in &lt;code&gt;/api/index.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const getData = require(&quot;./getData&quot;);

module.exports = async (req, res) =&amp;gt; {
  const data = await getData();
  res.json(data);
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The first problem I encountered is that using puppeteer is a little bit different, luckily this guide &lt;a href=&quot;https://zeit.co/blog/serverless-chrome&quot;&gt;Serverless Chrome via Puppeteer with Now 2.0&lt;/a&gt; explains how to do it well.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const chrome = require(&quot;chrome-aws-lambda&quot;);
const puppeteer = require(&quot;puppeteer-core&quot;);

const getData = async () =&amp;gt; {
  const browser = await puppeteer.launch({
    args: chrome.args,
    executablePath: await chrome.executablePath,
    headless: chrome.headless,
  });

  const page = await browser.newPage();
  await page.goto(&quot;https://ncov.dxy.cn/ncovh5/view/pneumonia&quot;);

  // Same code to get the data before

  return data;
};

module.exports = getData;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Supporting Cross-origin&lt;/h4&gt;
&lt;p&gt;To enable cors we need a file &lt;code&gt;/api/200.js&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = async (req, res) =&amp;gt; {
  res.status(200).send({ message: &quot;cors ok&quot; });
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Also our &lt;code&gt;now.js&lt;/code&gt; file should look like this.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;version&quot;: 2,
  &quot;routes&quot;: [
    {
      &quot;src&quot;: &quot;/(.*)&quot;,
      &quot;dest&quot;: &quot;/api/index.js&quot;,
      &quot;headers&quot;: {
        &quot;Access-Control-Allow-Origin&quot;: &quot;*&quot;,
        &quot;Access-Control-Allow-Methods&quot;: &quot;OPTIONS&quot;,
        &quot;Access-Control-Allow-Headers&quot;: &quot;Origin, X-Requested-With, Content-Type, Accept, Authorization&quot;
      }
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After this using the command &lt;code&gt;now&lt;/code&gt; should upload the project to zeit.co and the data is available on the project url https://cvirus.dobladov.now.sh/&lt;/p&gt;
&lt;h2&gt;Notes&lt;/h2&gt;
&lt;p&gt;As an alternative to Glitch and Zeit we can use &lt;a href=&quot;https://docs.netlify.com/functions/overview/#manage-your-serverless-functions&quot;&gt;Netlify&lt;/a&gt;, but for this project it was not necessary.&lt;/p&gt;
&lt;p&gt;I&apos;m not affiliated to Glitch of Zeit.co in any way, I just think both are really nice services to run my apps.&lt;/p&gt;
</content:encoded><category>javascript</category><category>crawler</category><author>Daniel Doblado</author></item><item><title>Automatically deploy to GitHub Pages using Actions</title><link>https://dobla.do/blog/automatically-deploy-to-github-pages-using-actions/</link><guid isPermaLink="true">https://dobla.do/blog/automatically-deploy-to-github-pages-using-actions/</guid><description>How to automate your workflows increasing your productivity and reducing manual errors.</description><pubDate>Fri, 07 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&lt;/p&gt;
&lt;p&gt;This has become the main way to deploy most of my projects and it has greatly reduced the time invested doing it by hand, also avoiding headaches that you might be familiar with like, &quot;ohhh! I forgot to checkout the proper branch&quot;, &quot;I used the wrong script.&quot;, &quot;I accidentally deleted the database and all the backups while updating a CSS color&quot;.&lt;/p&gt;
&lt;p&gt;After setting up this workflow your code will be automatically deployed after each commit to master without human interaction and without having to use other service than GitHub.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“There should be two tasks for a human being to perform to deploy software into a development, test, or production environment: to pick the version and environment and to press the “deploy” button.”
― David Farley, Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Advantages&lt;/h2&gt;
&lt;p&gt;If just mentioning the world &lt;em&gt;&quot;Automatically&quot;&lt;/em&gt; does not catch your attention, these are some of the main reasons why is worth to write some tiny workflows.&lt;/p&gt;
&lt;h3&gt;Single source of truth&lt;/h3&gt;
&lt;p&gt;Your code and and scrips for deployment are on same repository ensuring that all the information to create a build is on the repository and no manual process or tool is missing.&lt;/p&gt;
&lt;h3&gt;Documentation of deployments&lt;/h3&gt;
&lt;p&gt;The &lt;code&gt;.yml&lt;/code&gt; workflow shows how to do a proper deployment, other developers should know how it can be replicated locally; Even if there are no comments it might be easy to reverse engineer this files.&lt;/p&gt;
&lt;h3&gt;Automated testing&lt;/h3&gt;
&lt;p&gt;Before each build tests can be enforce to avoid putting code on production that might contain errors or not follow the code style guide.&lt;/p&gt;
&lt;h3&gt;No more manual tasks&lt;/h3&gt;
&lt;p&gt;This is clear, automating a process is always faster and in general less error prone than putting your code manually in production on a Friday before vacations.&lt;/p&gt;
&lt;h3&gt;Multi-platform&lt;/h3&gt;
&lt;p&gt;Multiple platforms can be tested while doing the deployment without the need of the developers of installing virtual machines.&lt;/p&gt;
&lt;h3&gt;Cost&lt;/h3&gt;
&lt;p&gt;Manual deployment will cost you at the very least time, GitHub actions are for free both in price and your time, why not use them?&lt;/p&gt;
&lt;h2&gt;Creating a new Action&lt;/h2&gt;
&lt;p&gt;Choose a repository, go to Actions and add new one.&lt;/p&gt;
&lt;p&gt;We need to create a new file, in this case is called &lt;code&gt;deploy.yml&lt;/code&gt;, here we will define the process for the deployment, make sure to &lt;strong&gt;never put any secrets in here&lt;/strong&gt; since it will be committed to the repository.&lt;/p&gt;
&lt;p&gt;The structure goes this way, we need a &lt;code&gt;name&lt;/code&gt;, &lt;code&gt;on&lt;/code&gt; which circumstances we need to run this action, and which &lt;code&gt;jobs&lt;/code&gt; the action will execute.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Deploy to GitHub Pages

on:
  push:
    branches:
      - master

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Build
        uses: actions/setup-node@v4
        with:
          node-version: &quot;22.x&quot;
      - run: |
          npm install
          npm test
          npm run build
          touch dist/.nojekyll
          echo &quot;snoo.odyssey.codes&quot; &amp;gt; dist/CNAME
      - name: Deploy
        uses: JamesIves/github-pages-deploy-action@releases/v3
        with:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          BRANCH: gh-pages
          FOLDER: dist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;After saving this new workflow we should have a file called &lt;code&gt;deploy.yml&lt;/code&gt; inside &lt;code&gt;.github/workflows&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Immediately a new action will be triggered executing the workflow, making a new build and pushing it to the &lt;code&gt;gh-pages&lt;/code&gt; branch.&lt;/p&gt;
&lt;p&gt;After is finish our app should be available at the repository public URL, the URL is show in Setting -&amp;gt; GitHub Pages, here you can also set a custom one.&lt;/p&gt;
&lt;h3&gt;Functionality of each block&lt;/h3&gt;
&lt;p&gt;Let&apos;s go trough each part of the code.&lt;/p&gt;
&lt;p&gt;Here we are indicating push code to master should trigger this Action.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;on:
  push:
    branches:
      - master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Inside of jobs we have to define the machine that will run the code, in this case Ubuntu.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;build-and-deploy:
	runs-on: ubuntu-latest
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Steps of the job&lt;/h4&gt;
&lt;p&gt;The Action is now divided in different &lt;code&gt;steps&lt;/code&gt; that will be executed sequentially.&lt;/p&gt;
&lt;p&gt;This step will checkout your repository to make the code available.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Checkout
  uses: actions/checkout@v4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Now we set node.js and we use version 13 to run the code, I would recommend to read the docs to do some other interesting stuff like running multiple node versions.&lt;/p&gt;
&lt;p&gt;This step will be similar for other code like go or rust&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Build
  uses: actions/setup-node@v4
  with:
    node-version: &quot;22.x&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The interesting part, here we have to define the process of actually building the site in this case something similar to what we would execute on our local machine to do it.&lt;/p&gt;
&lt;p&gt;In this case I install the missing node packages, test the code and compile the site.&lt;/p&gt;
&lt;p&gt;After this I added some special stuff for this repository, for example I make sure to create a file indicating that is not a Jekyll repository but this is actually not required in most cases.&lt;/p&gt;
&lt;p&gt;After that I define the CNAME since I want this code to run on a custom domain instead of &lt;code&gt;user.github.io/repository&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- run: |
  npm install
  npm test
  npm run build
  touch dist/.nojekyll
  echo &quot;snoo.odyssey.codes&quot; &amp;gt; dist/CNAME
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With this last step we push the code to the &lt;code&gt;gh-pages&lt;/code&gt; branch of the repository&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Deploy
      uses: JamesIves/github-pages-deploy-action@releases/v3
      with:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        BRANCH: gh-pages
        FOLDER: dist
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If any of this steps fails the process will stop ensuring no &quot;bad build&quot; will be out on production.&lt;/p&gt;
&lt;h2&gt;More about it&lt;/h2&gt;
&lt;h3&gt;Committing the build to a different repository&lt;/h3&gt;
&lt;p&gt;I had the use case of having to push the build to a different repository from the one where the sources are.&lt;/p&gt;
&lt;p&gt;In this case I wanted to have the code of my blog on a repository and deploy this build to a GitHubs&apos;s special repository named name.github.io, to serve my personal page from here since &apos;gh-pages&apos; does not work for this repository and my blog does not use Jekyll meaning the source code has to be on a separated repository and the build on master.&lt;/p&gt;
&lt;p&gt;To create this token go to &lt;code&gt;Settings&lt;/code&gt; -&amp;gt; &lt;code&gt;Developer settings&lt;/code&gt; -&amp;gt; &lt;code&gt;Personal access tokens&lt;/code&gt; and create a new one.&lt;/p&gt;
&lt;p&gt;With this configuration, we can use a personal token that needs to be set on Settings -&amp;gt; Secrets, since the &lt;code&gt;secrets.GITHUB_TOKEN&lt;/code&gt; only allows you to commit to your current repository.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Deploy
      uses: peaceiris/actions-gh-pages@v4
      with:
        personal_token: ${{ secrets.PERSONAL_TOKEN }}
        external_repository: dobladov/dobladov.github.io
        publish_branch: master
        publish_dir: ./dist

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Testing multiple platforms&lt;/h3&gt;
&lt;p&gt;Different platforms might use different commands.&lt;/p&gt;
&lt;p&gt;For example let&apos;s say there&apos;s a &lt;code&gt;rm&lt;/code&gt; in the scripts of the package.js while this build should work on MacOS and Linux, unfortunately it will fail on Windows since the &lt;code&gt;rm&lt;/code&gt; command is not available, with a multiple platform test we will realize that using a solution like &lt;code&gt;rimraf&lt;/code&gt; instead of &lt;code&gt;rm&lt;/code&gt; will solve the problem for Windows.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;strategy:
      matrix:
        platform: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.platform }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Writing a CNAME for custom domains&lt;/h3&gt;
&lt;p&gt;If your deployments needs a custom domain don&apos;t forget to write this file on each build.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;echo &quot;snoo.odyssey.codes&quot; &amp;gt; dist/CNAME
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Running actions on other events&lt;/h3&gt;
&lt;p&gt;Actions are not limited to push to master, is possible to also execute test or do other task on pull request or any kind of supported action, also to schedule this tasks using cron.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;on:
  schedule:
    - cron: &quot;0 * * * *&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Not only for deployments&lt;/h3&gt;
&lt;p&gt;Actions can be used for creating new release packages, also notifications, emails can be send on each event, the limit is your imagination.&lt;/p&gt;
&lt;h3&gt;Disadvantages&lt;/h3&gt;
&lt;p&gt;One of the main disadvantages, is that like any other service if is down you will not be able of deploying using these workflows, It does not limit you to still do manual deployments for your application.&lt;/p&gt;
&lt;p&gt;If you wonder why your perfectly working action does not work don&apos;t forget to check: https://status.github.com/&lt;/p&gt;
&lt;p&gt;Actions are not a 100% reliable if the infrastructure of GitHub fails, I had a problem where GitHub did not trigger one of my builds when I merged new code, a way to force a new commit without changes is to execute &lt;code&gt;git commit --allow-empty&lt;/code&gt;, this will cause the action to be executed.&lt;/p&gt;
&lt;h2&gt;Documentation&lt;/h2&gt;
&lt;p&gt;Documentation worth reading:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/actions/setup-node&quot;&gt;Node Action documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/JamesIves/github-pages-deploy-action&quot;&gt;Deploy Action documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://help.github.com/en/actions/automating-your-workflow-with-github-actions&quot;&gt;GitHub Actions documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/actions/checkout&quot;&gt;Checkout Action documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Some examples of my GitHub Actions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dobladov/snoo/blob/master/.github/workflows/deploy.yml&quot;&gt;Snoo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dobladov/last-time/blob/master/.github/workflows/deploy.yml&quot;&gt;Last-Time&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dobladov/mdx-blog/blob/master/.github/workflows/deploy.yml&quot;&gt;This blog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>GitHub</category><category>devops</category><category>production</category><category>gh-pages</category><author>Daniel Doblado</author></item><item><title>Become your company&apos;s JavaScript janitor</title><link>https://dobla.do/blog/become-your-companys-javascript-janitor/</link><guid isPermaLink="true">https://dobla.do/blog/become-your-companys-javascript-janitor/</guid><description>How to keep your JavaScript dependencies up-to-date and learn new things in the process</description><pubDate>Wed, 16 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;How many times do you work on a project and many dependencies are outdated, and neither you nor your colleagues want to take care of them because it&apos;s a huge tech debt task, especially with major versions?&lt;/p&gt;
&lt;p&gt;This might not happen if you take care of your dependencies regularly; even better, it can be an opportunity to improve your codebase and learn new things that new versions bring.&lt;/p&gt;
&lt;h2&gt;Basic solutions with npm&lt;/h2&gt;
&lt;p&gt;The simplest way to see if there are new versions of your dependencies is to run &lt;code&gt;npm outdated&lt;/code&gt;, this command will show you the current version of your dependencies and the latest version available, pretty useful to get an overview.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;From here we can do &lt;code&gt;npm update&lt;/code&gt;, it will update all the dependencies to the latest version, but this is not always the best option because it can break your codebase, so you need to be careful, it&apos;s much better to update packages one by one and check if everything is working as expected by running your tests, especially with major versions.&lt;/p&gt;
&lt;p&gt;Another task to do is to check how many vulnerabilities you have in your dependencies, easy to do by running &lt;code&gt;npm audit&lt;/code&gt;, this will show you the vulnerabilities and the severity of outdated packages.&lt;/p&gt;
&lt;p&gt;And while this tools are good &lt;em&gt;npm-check&lt;/em&gt; does a better job combining and improving them.&lt;/p&gt;
&lt;h2&gt;A better way&lt;/h2&gt;
&lt;p&gt;There are some tools that can help you with this task, one of them is &lt;a href=&quot;https://www.npmjs.com/package/npm-check&quot;&gt;npm-check&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The command &lt;code&gt;npx npm-check -u -E&lt;/code&gt; will show an interactive list indicating the scope (&lt;em&gt;patch&lt;/em&gt;, &lt;em&gt;minor&lt;/em&gt;, &lt;em&gt;major&lt;/em&gt;) of a newer version and a link to the changelog, so you can see what has changed and if it&apos;s worth updating it.&lt;/p&gt;
&lt;p&gt;It makes incredibly easy to install each dependency once at a time to avoid breaking changes. My advice here is to always run the tests after installing any major version.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Workflow&lt;/h2&gt;
&lt;p&gt;For maintenance tasks, I like to reserve some time on Fridays to do this kind of work, so I can end the week with a clean slate, and I can also use this time to learn new things.&lt;/p&gt;
&lt;p&gt;I usually go through all the new dependencies and read the changelog, if I see something interesting, I update it, or make notes for future improvements based on the new features.&lt;/p&gt;
&lt;h2&gt;Problems with alternatives&lt;/h2&gt;
&lt;p&gt;In my opinion, Dependabot feels more like spam than a useful tool; it create&apos;s a lot of PRs that are most often ignored, creating a lot of noise in your repository while not helping you understand what&apos;s going on with the new versions.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;Both your colleagues and future self will be thankful for your work on this task and the new knowledge you&apos;ll gain.&lt;/p&gt;
&lt;p&gt;Take the time to appreciate and maintain the foundation that your projects are built upon.&lt;/p&gt;
</content:encoded><category>dependencies</category><category>update</category><category>npm</category><category>javascript</category><category>devops</category><category>maintenance</category><author>Daniel Doblado</author></item><item><title>Choosing a game engine</title><link>https://dobla.do/blog/choosing-a-game-engine/</link><guid isPermaLink="true">https://dobla.do/blog/choosing-a-game-engine/</guid><description>Moving from canvas to game engines like Godot or Defold</description><pubDate>Mon, 18 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;With the recent &lt;a href=&quot;https://news.ycombinator.com/item?id=37486431&quot;&gt;controversy about Unity&lt;/a&gt;, I see more and more posts about &lt;a href=&quot;/tags/gamedev&quot;&gt;gamedev&lt;/a&gt;, which is a very interesting topic that I don&apos;t usually work as much as I want; I only made small games with &lt;a href=&quot;https://phaser.io/&quot;&gt;Phaser.js&lt;/a&gt; long ago and recently some experiments directly in &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API&quot;&gt;canvas&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://cocoamoss.com/&quot;&gt;Cocoa Moss&lt;/a&gt; a team of two people that makes really cozy games, motivated me to give gamedev another try. I have been playing their games with my girlfriend for the past week almost every night, and they always make us happy.&lt;/p&gt;
&lt;h2&gt;Problems with canvas and not using a framework&lt;/h2&gt;
&lt;p&gt;My last game required hundreds of elements on the screen moving and colliding with each other. canvas is very limited in this sense, and while it works, the performance is not the best. I also have to handle quirks like different density screens and other visual glitches.&lt;/p&gt;
&lt;p&gt;I need to port the game to something faster, like &lt;a href=&quot;https://en.wikipedia.org/wiki/WebGL&quot;&gt;WebGL&lt;/a&gt;. The problem is that WebGL by itself is a very low-level API, and I don&apos;t want to write a game engine from scratch (neither I&apos;m smart enough to do so)&lt;/p&gt;
&lt;p&gt;Most people will opt to use a framework to handle the complexity of WebGL, the first option is usually &lt;a href=&quot;https://threejs.org/&quot;&gt;three.js&lt;/a&gt;. In my case, the games are not 3D, so &lt;em&gt;three.js&lt;/em&gt; feels overkill.&lt;/p&gt;
&lt;p&gt;My second option was &lt;a href=&quot;https://pixijs.com/&quot;&gt;pixi.js&lt;/a&gt; while I&apos;m getting somewhere, I don&apos;t like so much how the API is structured. I find myself often searching for solutions online to do basic stuff, most of the time finding code for old versions that is not compatible with the current one. This is the same reason I stopped using &lt;em&gt;Phaser&lt;/em&gt;, all examples have a mix of old and new APIs, which makes it very frustrating to attempt any changes.&lt;/p&gt;
&lt;p&gt;So far, Pixi.js is my best option, but I&apos;m still not happy with it.&lt;/p&gt;
&lt;h2&gt;Using game engines&lt;/h2&gt;
&lt;p&gt;The main reason for using a game engine is that it will handle all the complexity across many platforms, not limiting me to just the web, although that&apos;s my main target. But hey, more performance is always good, and if I do free games, I want people to be able to download them and keep them forever, or for at least longer than the time I host them.&lt;/p&gt;
&lt;p&gt;I never paid much attention to &lt;em&gt;Unity&lt;/em&gt;, and after all the controversy with the fees,  many developers are moving on to other alternatives, the main ones seem to be &lt;a href=&quot;https://godotengine.org/&quot;&gt;Godot&lt;/a&gt; or &lt;a href=&quot;https://www.unrealengine.com/&quot;&gt;Unreal Engine&lt;/a&gt;. Unreal is probably overkill for what I want to do and not suitable for 2D, so my first choice was Godot.&lt;/p&gt;
&lt;h2&gt;Godot&lt;/h2&gt;
&lt;p&gt;This means that Godot is on the table. I avoided it at first because I&apos;m the most comfortable writing &lt;a href=&quot;/tags/javascript&quot;&gt;JavaScript&lt;/a&gt;, and last time I checked, the developers were in the process of releasing version 4 with a huge rewrite of the engine, so I decided to wait.&lt;/p&gt;
&lt;p&gt;There are two options for writing the code: GDScript and C#. After watching many videos about the advantages of one over the other, it seems that GDScript will emerge as the winner, so I will go with that.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=S2tTEPHIS1I&quot;&gt;C# Is Better Than GDScript But&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=xM-HL0RU_ho&quot;&gt;How Much Faster Is C# Compared to GDscript?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=ZF-IunpetMg&quot;&gt;GDScript vs. C# - Which Is Better? &lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=g19dUmKTAfI&quot;&gt;C# or GDScript with Godot ?? Why Bother?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Writing GDScript seems simple enough; it has basically the same syntax as Python, and it brings deep integration with the IDE.&lt;/p&gt;
&lt;p&gt;While Godot seems to be great for making games, the situation of making web games is problematic at the moment because it does not support macOS web export. This is a problem when I&apos;m planning on mainly making web games.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/godotengine/godot/issues/67949&quot;&gt;All HTML5 exports crash on loading&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/godotengine/godot/issues/70691&quot;&gt;HTML5 export for Godot 4.0 takes 1-2 minutes to load on macOS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.reddit.com/r/godot/comments/141l9bm/how_far_is_godot_4_from_getting_stable_web_export/&quot;&gt;How far is Godot 4 from getting stable web export?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Defold&lt;/h2&gt;
&lt;p&gt;When searching for new engines, I stumbled into &lt;a href=&quot;https://defold.com/&quot;&gt;Defold&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;I had never heard of it before, but; I&apos;m very surprised. The engine is open source, free, and the export is very straightforward supporting almost every platform, including macOS, Windows, Linux, Android, iOS, HTML5, consoles, etc.&lt;/p&gt;
&lt;p&gt;It uses &lt;a href=&quot;https://www.lua.org/&quot;&gt;Lua&lt;/a&gt; which I never wrote before, but I kinda love it now, it&apos;s simpler than Python, it&apos;s clear, and the compilation time is incredible fast, if you don&apos;t believe me watch &lt;a href=&quot;https://www.youtube.com/watch?v=jUuqBZwwkQw&quot;&gt;Lua in 100 Seconds
&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The IDE is very well organized, I was able to create most of the logic I need for my game after watching a &lt;a href=&quot;https://www.youtube.com/watch?v=HjJ-oDz-GcI&quot;&gt;couple of tutorials&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;So far, I feel more tempted to start writing my game in &lt;em&gt;Defold&lt;/em&gt; until Godot gets their web export fixed.&lt;/p&gt;
&lt;p&gt;While they promote themselves as a 2D engine, underneath it&apos;s a 3D engine, and it&apos;s &lt;a href=&quot;https://twitter.com/AGulev/status/1702085104422592541&quot;&gt;getting better and better&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;There are fewer tutorials for &lt;em&gt;Defold&lt;/em&gt; on the wild, the official tutorials are high quality, teaching step by step, with clear goals and good results.&lt;/p&gt;
&lt;h2&gt;Alternatives&lt;/h2&gt;
&lt;p&gt;I was tempted by other approaches like &lt;a href=&quot;https://www.raylib.com/&quot;&gt;raylib&lt;/a&gt; specially using &lt;a href=&quot;https://github.com/RobLoach/node-raylib&quot;&gt;node-raylib&lt;/a&gt;, and &lt;a href=&quot;https://bevyengine.org/&quot;&gt;Bevy&lt;/a&gt;, but I guess it&apos;s better to bet on a bigger community and a more complete engine.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;There are many ways to make games, and betting on open source should be the way to go, especially when game developers tend to be very talented people who are willing to collaborate on making a better engine.&lt;/p&gt;
&lt;p&gt;Using a game engine brings many advantages. For me, having a community of people creating learning content is the greatest advantage, I can focus on making the game and not the engine, and I can export to many platforms.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
</content:encoded><category>godot</category><category>gamedev</category><category>defold</category><category>unity</category><category>canvas</category><category>webgl</category><category>lua</category><category>games</category><category>phaser</category><category>javascript</category><category>pixi</category><category>threejs</category><category>unreal</category><category>raylib</category><category>bevy</category><author>Daniel Doblado</author></item><item><title>GeoMask: geolocation spoofing extension</title><link>https://dobla.do/blog/geomask/</link><guid isPermaLink="true">https://dobla.do/blog/geomask/</guid><description>A way to easily spoof your geolocation in Firefox and Chrome for development and privacy purposes.</description><pubDate>Mon, 15 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Earlier this year I switched back to Firefox, I want to move away from Google services as much as possible, and one feature that I used sometimes was the ability to spoof my geolocation, not even for nefarious purposes or privacy, mostly for development.&lt;/p&gt;
&lt;p&gt;This is something really easy to do on Chrome, without even having to resort to extensions, simply open the developer tools, go to the &quot;Sensors&quot; tab and change your location to whatever place you want, I wish Firefox had something similar built-in, but it doesn&apos;t.&lt;/p&gt;
&lt;p&gt;My first attempt was to use an existing extension, I couldn&apos;t find any that worked well, most of them were either outdated, not working at all, or had the most unintuitive user interface imaginable.&lt;/p&gt;
&lt;p&gt;When there are no good options, the best option is to make your own, so I created &lt;a href=&quot;https://github.com/dobladov/geomask&quot;&gt;GeoMask&lt;/a&gt; following the development mindset that I&apos;m going to call &quot;Better Execution, Same Task&quot; (BEST), the idea is to look at existing solutions, see what they do well and what they don&apos;t, and then make something that does it slightly better in each aspect, nothing groundbreaking.&lt;/p&gt;
&lt;h2&gt;Goals&lt;/h2&gt;
&lt;p&gt;Let&apos;s start by understanding the main feature, &lt;strong&gt;changing the location directly in the extension popup&lt;/strong&gt;, this might sound crazy but most of the extensions I tried, required an options page buried in settings or navigating to a hosted webpage where JS is injected to add UI where the location can be set, and most of those sites are offline and not maintained anymore.&lt;/p&gt;
&lt;p&gt;I believe these extensions have weird designs because of how limited and hard to work with the WebExtension APIs are, even more after manifest v3 changes, most of the code has to run isolated from the extension UI, requiring to add weird communication between layers, inject scripts which are very limited, etc, I don&apos;t want to rant much about it, but clearly Google does not like extensions and it&apos;s forcing Mozilla to follow their enshittification practices.&lt;/p&gt;
&lt;p&gt;My extension needs to do the minimum as Google&apos;s devtool, a popup with two text fields for latitude and longitude, and a button to apply the changes, and what can be better than that?, if the popup also shows a map with a pin that can be dragged to change the location visually, and search box to find places quickly.&lt;/p&gt;
&lt;h2&gt;Development&lt;/h2&gt;
&lt;p&gt;With that in mind, I created a simple map with &lt;a href=&quot;https://leafletjs.com/&quot;&gt;Leaflet&lt;/a&gt;, my initial thought was to use &lt;a href=&quot;https://d3js.org/d3-geo&quot;&gt;d3-geo&lt;/a&gt; with a self contained geojson map of the world, but leaflet gives a lot of features out of the box, zoom, panning, and more rich map information, in my opinion is a worthy dependency.&lt;/p&gt;
&lt;p&gt;Now that I have a map, I can set coordinates, change the position easily, I need to do the most annoying part of this project, overriding the browsers&apos;s &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Navigator/geolocation&quot;&gt;geolocation API&lt;/a&gt;, with the selected coordinates.&lt;/p&gt;
&lt;p&gt;For that the &lt;a href=&quot;https://github.com/dobladov/geomask/blob/main/extension/popup/popup.mjs&quot;&gt;popup&lt;/a&gt; needs to be able to store the coordinates using &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Storage&quot;&gt;browserAPI.storage.local.set&lt;/a&gt;, then a &lt;a href=&quot;https://github.com/dobladov/geomask/blob/main/extension/content.js&quot;&gt;content&lt;/a&gt; script listens to storage changes with &lt;code&gt;browserAPI.storage.onChanged.addListener&lt;/code&gt; in order store the coordinates on the page context, but unfortunately a content script cannot override the geolocation API directly, so it needs to &lt;a href=&quot;https://github.com/dobladov/geomask/blob/main/extension/inject.js&quot;&gt;inject&lt;/a&gt; a &lt;code&gt;web_accessible_resources&lt;/code&gt; that overrides the &lt;code&gt;navigator.geolocation.getCurrentPosition&lt;/code&gt; and &lt;code&gt;navigator.geolocation.watchPosition&lt;/code&gt; methods to return the spoofed coordinates, where this data can be read from the &lt;code&gt;root.dataset&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;At this point I had a functional extension that could change the location, to polish it a bit more, I added a search box using &lt;a href=&quot;https://nominatim.org/release-docs/develop/api/Search/&quot;&gt;Nominatim&lt;/a&gt; to find places quickly, a way to reset the location to the real one, dark and light mode, some error handling for invalid coordinates, and a randomize button with distance options.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;For the extension itself I decided to use vanilla JS with &lt;a href=&quot;https://jsdoc.app/&quot;&gt;JSDOC&lt;/a&gt; types validated by typescript to avoid any build step and no UI frameworks, given that there&apos;s not much UI, keeping the extension lightweight and simple, &lt;em&gt;92.1 KB&lt;/em&gt; for &lt;a href=&quot;https://github.com/dobladov/geomask/releases/tag/1.0.0&quot;&gt;1.0.0&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Available now for both &lt;a href=&quot;https://addons.mozilla.org/en-US/firefox/addon/geomask/&quot;&gt;Firefox&lt;/a&gt; and &lt;a href=&quot;https://chromewebstore.google.com/detail/geomask-change-your-locat/onmhohhfidpbfmgkplobnjlfnfnopehm&quot;&gt;Chrome&lt;/a&gt;&lt;/p&gt;
</content:encoded><category>extension</category><category>geolocation</category><category>firefox</category><category>privacy</category><category>chrome</category><category>web-development</category><category>javascript</category><category>typescript</category><category>web-extensions</category><category>addons</category><category>leaflet</category><category>d3-geo</category><category>nominatim</category><category>mapping</category><author>Daniel Doblado</author></item><item><title>Git tricks</title><link>https://dobla.do/blog/git-tricks/</link><guid isPermaLink="true">https://dobla.do/blog/git-tricks/</guid><description>Some of my tweaks for using git more efficiently</description><pubDate>Tue, 22 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://git-scm.com/&quot;&gt;Git&lt;/a&gt; is one of my most used work tools, sharpening our tools helps us be more productive and save a lot of time. These are some of my tweaks to speed up my workflow when interacting with git.&lt;/p&gt;
&lt;h2&gt;ZSH git aliases&lt;/h2&gt;
&lt;p&gt;One of the first things I do after setting up my laptop is to install &lt;a href=&quot;https://www.zsh.org/&quot;&gt;ZSH&lt;/a&gt; and &lt;a href=&quot;https://ohmyz.sh/&quot;&gt;Oh My ZSH&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Their &lt;a href=&quot;https://kapeli.com/cheat_sheets/Oh-My-Zsh_Git.docset/Contents/Resources/Documents/index&quot;&gt;aliases&lt;/a&gt; have become completely natural to me, and I can&apos;t work without them.&lt;/p&gt;
&lt;p&gt;While the list looks a bit overwhelming, it&apos;s actually very easy to remember them; most of the time it&apos;s the initial letter or how you would pronounce them, for example &lt;code&gt;git checkout&lt;/code&gt; becomes &lt;code&gt;gco&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;After a few uses, they become natural, and you will find yourself using them without thinking about it.&lt;/p&gt;
&lt;p&gt;These are my most used shortcuts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;git fetch&lt;/code&gt; -&amp;gt; &lt;code&gt;gf&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git add&lt;/code&gt; -&amp;gt; &lt;code&gt;ga&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git commit -m&lt;/code&gt; -&amp;gt; &lt;code&gt;gcmsg&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git checkout&lt;/code&gt; -&amp;gt; &lt;code&gt;gco&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git checkout -b&lt;/code&gt; -&amp;gt; &lt;code&gt;gcb&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git merge&lt;/code&gt; -&amp;gt; &lt;code&gt;gm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git pull origin &quot;$(git_current_branch)&lt;/code&gt; -&amp;gt; &lt;code&gt;ggpull&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;git push origin &quot;$(git_current_branch)&quot;&lt;/code&gt; -&amp;gt; &lt;code&gt;ggpush&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Commands that include variables like &lt;code&gt;git_current_branch&lt;/code&gt; save a huge amount of time and mental attention.&lt;/p&gt;
&lt;h2&gt;Custom aliases&lt;/h2&gt;
&lt;p&gt;Switching between my latest edited branches is common, and this command helps me quickly visualize my modified branches in order.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alias branchOrder=&apos;git branch --sort=-committerdate&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Similar to the last command, it helps to switch branches in a quick way using &lt;a href=&quot;https://github.com/junegunn/fzf&quot;&gt;fuzzy finder&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alias gcoi=&apos;git checkout $(git branch -a | fzf | xargs)&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;For one of my projects, we include the PR number on the &lt;a href=&quot;https://keepachangelog.com/&quot;&gt;Changelog&lt;/a&gt;, this command gives me the value without having to open the browser.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;alias lastPR=&apos;gh pr list -s all --json number --jq &quot;.|=sort_by(.number)|last|.n&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Global git ignore&lt;/h2&gt;
&lt;p&gt;I like to keep a global gitignore file, this is useful for ignoring files that are not specific to a project, for example, IDE configuration files.&lt;/p&gt;
&lt;p&gt;Adding &lt;code&gt;devNotes*&lt;/code&gt; is especially useful as it helps me to write notes about a project while being sure they won&apos;t be pushed by accident to the repository.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nano .gitignore_global
git config --global core.excludesfile ~/.gitignore_global
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;.DS_Store
.vscode
devNotes*
dev-notes*
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;ZSH theme&lt;/h2&gt;
&lt;p&gt;ZSH themes are very customizable, I like to keep it simple, my current one is basically the default one with a few tweaks, a simpler way to display the &lt;strong&gt;branch name&lt;/strong&gt; on the left side and the &lt;strong&gt;SHA commit&lt;/strong&gt; on the right, content on the &lt;code&gt;RPROMPT&lt;/code&gt; will be automatically hidden if the content goes over it.&lt;/p&gt;
&lt;p&gt;That information helps me understand what I&apos;m working at a glance, avoiding the need to run &lt;code&gt;git status&lt;/code&gt; or &lt;code&gt;git branch&lt;/code&gt; to get that information.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PROMPT=&quot;%(?:%{$fg_bold[green]%}➜ :%{$fg_bold[red]%}➜ )&quot;
PROMPT+=&apos; %{$fg[cyan]%}%c%{$reset_color%} $(git_prompt_info)&apos;
RPROMPT=&apos;%F{cyan}$(git_prompt_short_sha)%F{reset}&apos;
ZSH_THEME_GIT_PROMPT_PREFIX=&quot;%{$fg_bold[blue]%}(%{$fg[red]%}&quot;
ZSH_THEME_GIT_PROMPT_SUFFIX=&quot;%{$reset_color%} &quot;
ZSH_THEME_GIT_PROMPT_DIRTY=&quot;%{$fg[blue]%}) %{$fg[yellow]%}✗&quot;
ZSH_THEME_GIT_PROMPT_CLEAN=&quot;%{$fg[blue]%})&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Gh command&lt;/h2&gt;
&lt;p&gt;This section is more suited to GitHub than git but since my workflow combines both, I think it&apos;s worth mentioning.&lt;/p&gt;
&lt;p&gt;I use &lt;a href=&quot;https://cli.github.com/&quot;&gt;GitHub CLI&lt;/a&gt; mostly for scripting, there are two very simple commands that save me a lot of time, &lt;code&gt;gh repo view -w&lt;/code&gt; and &lt;code&gt;gh pr view -w&lt;/code&gt;, they open the current repository or pull request in the browser.&lt;/p&gt;
&lt;p&gt;Another command that I like to execute is &lt;code&gt;gh run watch&lt;/code&gt; to know when a workflow is finished, for example, when I&apos;m running tests on a PR, it can be pipped to the command &lt;code&gt;say [message]&lt;/code&gt; to get a notification when the workflow is finished.&lt;/p&gt;
&lt;p&gt;There&apos;s more to this command, but these three options are really useful.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
</content:encoded><category>git</category><category>productivity</category><category>tools</category><category>zsh</category><category>oh-my-zsh</category><category>gh</category><category>github</category><category>gitignore</category><category>gitconfig</category><category>git-aliases</category><author>Daniel Doblado</author></item><item><title>How I journal using Foam</title><link>https://dobla.do/blog/how-i-journal-using-foam/</link><guid isPermaLink="true">https://dobla.do/blog/how-i-journal-using-foam/</guid><description>Reasons to use Foam and why I think it&apos;s the best note-taking workflow.</description><pubDate>Tue, 15 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Note-taking helps us focus, remember and organize our thoughts, it&apos;s an incredible tool to improve your work and yourself.&lt;/p&gt;
&lt;p&gt;I&apos;ve been taking notes for a long time, but I never had a good system to keep them organized and more importantly go back and read/refine old notes.&lt;/p&gt;
&lt;h2&gt;Why choosing Foam over alternatives&lt;/h2&gt;
&lt;p&gt;I stared to use &lt;a href=&quot;https://foambubble.github.io/foam/&quot;&gt;Foam&lt;/a&gt; to annotate everything, I had many attempts with other tools, none seemed to stick, my last attempt was &lt;a href=&quot;https://www.notion.so/&quot;&gt;Notion&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;While I find &lt;em&gt;Notion&lt;/em&gt; incredibly useful it feels sluggish and the layout does not make me want to come back or write more, I do have a big amount of topics in there, but even when I can connect the pages, it&apos;s really hard to make mental connections to each one of the other topics.&lt;/p&gt;
&lt;p&gt;There are other issues with &lt;em&gt;Notion&lt;/em&gt;, like privacy concerns, but to me the most important is ownership, it&apos;s possible to export simple notes to Markdown, but takes a lot of time and is not a good experience, I didn&apos;t check their API, in any case I&apos;m sure it&apos;s not as easy as cloning a git repository.&lt;/p&gt;
&lt;p&gt;This is why I love &lt;em&gt;Foam&lt;/em&gt;, it works the same way as as &lt;a href=&quot;https://obsidian.md/&quot;&gt;Obsidian&lt;/a&gt; or other &lt;a href=&quot;https://en.wikipedia.org/wiki/Zettelkasten&quot;&gt;Zettelkasten&lt;/a&gt; apps, but inside of &lt;a href=&quot;https://code.visualstudio.com/&quot;&gt;VSCode&lt;/a&gt;, this is great because I always have &lt;em&gt;VSCode&lt;/em&gt; opened, meaning that I don&apos;t have to switch context, all my shortcuts and plugins work, I can use the same editor for everything.&lt;/p&gt;
&lt;p&gt;The view of the graph is the main selling point to use &lt;em&gt;Obsidian&lt;/em&gt;, no more need to remember visiting old post, all is connected and can be accessed with a click, this is a game changer for me, I can see the connections between topics and ideas, I can see the evolution of my thoughts and how they are connected.&lt;/p&gt;
&lt;p&gt;A graph view is something that I always wanted to have and what makes the difference in my total number of edits, and the view in &lt;em&gt;Foam&lt;/em&gt; works great, it can be positioned like any other tab which I always keep as a pinned tab with &lt;code&gt;cmd + k shift + enter&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;There&apos;s some sense of satisfaction after looking at all the connections on the graph that making me want to write even more.&lt;/p&gt;
&lt;h2&gt;Journaling&lt;/h2&gt;
&lt;p&gt;I make daily notes to keep track of what I did in a short resume format, it&apos;s easy to create a new document using the shortcuts like &lt;code&gt;/today&lt;/code&gt;, &lt;code&gt;/yesterday&lt;/code&gt; or &lt;code&gt;/+1w&lt;/code&gt; all of my notes can be connected using tags to other notes, this makes easy to visualize what I was doing on a particular day with the graph view without having to search or even open the note.&lt;/p&gt;
&lt;h2&gt;Plugins system&lt;/h2&gt;
&lt;p&gt;The integrations work great, multiple plugins doing one thing and doing it well.&lt;/p&gt;
&lt;p&gt;I can integrate dynamic &lt;a href=&quot;https://excalidraw.com/&quot;&gt;Excalidraw&lt;/a&gt; documents, before I used to have a single &lt;em&gt;Excalidraw&lt;/em&gt; page opened in my browser with a bunch of cramped ideas, now I can simply create a new &lt;code&gt;idea.excalidraw.png&lt;/code&gt; and immediately work on separated contexts, I can&apos;t overstate how useful it has become.&lt;/p&gt;
&lt;p&gt;On top of it, I can directly link the &lt;code&gt;excalidraw.png&lt;/code&gt; file inside Markdown and preview it while still being editable.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;There are many other integrations, to &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=mushan.vscode-paste-image&quot;&gt;paste images&lt;/a&gt;, &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=fabiospampinato.vscode-markdown-todo&quot;&gt;create todos&lt;/a&gt;, &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=kisstkondoros.vscode-gutter-preview&quot;&gt;preview images&lt;/a&gt; like shown in the last screenshot or simply have a &lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=yzhang.markdown-all-in-one&quot;&gt;better markdown experience&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Best part is that all of these plugins are just &lt;em&gt;VSCode&lt;/em&gt; plugins that have functionality outside of &lt;em&gt;Foam&lt;/em&gt;, enriching your whole development experience.&lt;/p&gt;
&lt;h2&gt;Backups&lt;/h2&gt;
&lt;p&gt;The output of &lt;em&gt;Foam&lt;/em&gt; is just plain text making trivial to put the content into a repository.&lt;/p&gt;
&lt;p&gt;Git once understood it&apos;s such a powerful tool that changes your way to think, for me the ability to have diffs and restore what I&apos;m working on it&apos;s invaluable.&lt;/p&gt;
&lt;p&gt;For my normal workflow, I write my notes during the day and next day review the changes using git, make corrections and push them to the repository, this helps to refresh my memory and improve the quality of the notes.&lt;/p&gt;
&lt;p&gt;And again all of this without leaving &lt;em&gt;VSCode&lt;/em&gt; and completely open source, I can&apos;t thank enough all the &lt;a href=&quot;https://foambubble.github.io/foam/#thanks-and-attribution&quot;&gt;contributors&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
</content:encoded><category>notes</category><category>productivity</category><category>vscode</category><category>foam</category><category>obsidian</category><category>git</category><category>markdown</category><author>Daniel Doblado</author></item><item><title>Leaving the loop of repetition</title><link>https://dobla.do/blog/leaving-the-loop-of-repetition/</link><guid isPermaLink="true">https://dobla.do/blog/leaving-the-loop-of-repetition/</guid><description>Breaking the loop of addiction by replacing it with something positive</description><pubDate>Wed, 30 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;I never had to deal with drugs, or what&apos;s seen as a &quot;serious&quot; addiction. But I believe I&apos;ve been addicted to other things, like video games, endless scrolling, and other more mundane entertainment.&lt;/p&gt;
&lt;p&gt;There have been periods of time when I was aware of wasting my life in ways that made me feel bad about myself, and I don&apos;t mean feeling bad for not being a workaholic, but for really enjoying my time of reading or simply sitting in the grass looking at trees.&lt;/p&gt;
&lt;h2&gt;The Loop&lt;/h2&gt;
&lt;p&gt;The loop is a cycle of repetition. It&apos;s a habit that you can&apos;t seem to break. It&apos;s a pattern that you can&apos;t seem to escape. It&apos;s a routine that you can&apos;t change.&lt;/p&gt;
&lt;p&gt;The first step is to become aware of the loop. You have to be able to see it, and realize how it&apos;s negatively affecting your life.&lt;/p&gt;
&lt;p&gt;At some point, I came to the realization that breaking the loop can only be done one way: &lt;strong&gt;by replacing the addiction with something positive&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Once you know what you have to get rid of, it&apos;s important to find a replacement. At this point, you have to understand that &lt;strong&gt;there&apos;s no stop of the bad habit by willpower alone&lt;/strong&gt;; the only way to get rid of it is by being busy enough with something else to the point that you don&apos;t have time to think about it.&lt;/p&gt;
&lt;h2&gt;Breaking the loop&lt;/h2&gt;
&lt;p&gt;I used to spend time on places like &lt;a href=&quot;https://www.reddit.com/&quot;&gt;Reddit&lt;/a&gt; doing nothing but scrolling, rarely participating in the conversation.&lt;/p&gt;
&lt;p&gt;At some point, I knew I was getting nothing from it, no fun, no growth, not even real entertainment, what I was watching was gone the moment the next piece of content came, creating nothing of value.&lt;/p&gt;
&lt;p&gt;I took many approaches, some very effective, like blocking the content in tedious ways to use it, like blocking the domain in the hosts file, and while it reduced the consumption, it was still a problem, causing me to go back to the same pattern.&lt;/p&gt;
&lt;p&gt;The only thing that worked was to completely replace it by reading. If I really want to go to &lt;em&gt;Reddit&lt;/em&gt;, I always have better content prepared.&lt;/p&gt;
&lt;p&gt;I store hundreds of articles in &lt;a href=&quot;https://pocket.com/&quot;&gt;Pocket&lt;/a&gt; to read later, I started to &lt;a href=&quot;http://localhost:3000/blog/reading-books-on-my-phone/&quot;&gt;read on my phone&lt;/a&gt;. This way, I have content that really fulfills me, and I can do it anywhere, anytime, with the same convenience of scrolling &lt;em&gt;Reddit&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;That was the key factor: make it as convenient as possible and make it something that I really enjoy, and that&apos;s the only way to break the loop.&lt;/p&gt;
&lt;p&gt;There are other habits that I&apos;ve changed, like exercising. While I was always in shape, or at least thin, I always wanted to do more.&lt;/p&gt;
&lt;p&gt;I was able to have enough determination to run often, but it never felt good for me, it made me lose more weight than I needed, and I didn&apos;t enjoy it. I was doing it because I thought it was the only way to exercise.&lt;/p&gt;
&lt;p&gt;So it became another loop to break. I replaced it with &lt;a href=&quot;https://en.wikipedia.org/wiki/Calisthenics&quot;&gt;calisthenics&lt;/a&gt;, I started to do it at home, and I really enjoy it.&lt;/p&gt;
&lt;p&gt;I can do it anywhere, anytime, and for as long as I want. I can do it in a way that I enjoy and in a way that I can see progress, and that&apos;s the key factor: make it as convenient as possible and make it something enjoyable.&lt;/p&gt;
&lt;p&gt;Find your loop and replace it.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
</content:encoded><category>addiction</category><category>loop</category><category>repetition</category><category>habits</category><category>pattern</category><category>routine</category><category>productivity</category><category>Reddit</category><category>Pocket</category><category>calisthenics</category><category>exercise</category><category>running</category><category>reading</category><category>books</category><category>phone</category><author>Daniel Doblado</author></item><item><title>Making the Orb</title><link>https://dobla.do/blog/making-the-orb/</link><guid isPermaLink="true">https://dobla.do/blog/making-the-orb/</guid><description>How the interactive orb in my site was made.</description><pubDate>Sat, 19 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import orbMeme from &quot;./orb-meme.jpg&quot;;
import YoutubeVideo from &quot;#astro/components/YoutubeVideo.astro&quot;;
import orb from &quot;./orb.webm&quot;;
import noJsOrb from &quot;./orb-no-js.webm&quot;;&lt;/p&gt;
&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Inspired by sites like &lt;a href=&quot;https://oimo.io/&quot;&gt;Oimo&lt;/a&gt; I wanted to have one interactive element, ideally something that doesn&apos;t get in the way and makes the main page standout while not being too resource intense.&lt;/p&gt;
&lt;p&gt;I played with some other ideas like noise backgrounds, generating a map that constantly changes, &lt;a href=&quot;https://en.wikipedia.org/wiki/Rorschach_test&quot;&gt;Rorschach patterns&lt;/a&gt;, floating particles, but none of them looked good enough (I&apos;m not a designer) or felt more special than a background.&lt;/p&gt;
&lt;p&gt;&amp;lt;video width=&quot;400&quot; autoplay loop style={{ maxWidth: &quot;100%&quot;, display: &quot;flex&quot;, margin: &quot;0 auto&quot; }}&amp;gt;
&amp;lt;source src={orb} type=&quot;video/webm&quot; /&amp;gt;
&amp;lt;/video&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;br /&amp;gt;&lt;/p&gt;
&lt;h2&gt;Implementation&lt;/h2&gt;
&lt;p&gt;Then I saw this wonderful video from &lt;a href=&quot;https://www.youtube.com/@BarneyCodes&quot;&gt;Barney Codes&lt;/a&gt; and I loved how the flow field looked; He did a wonderful explanation and re-creating it was a breeze, a channel totally worth &lt;a href=&quot;https://www.youtube.com/@BarneyCodes?sub_confirmation=1&quot;&gt;subscribing&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;YoutubeVideo
videoId=&quot;sZBfLgfsvSk&quot;
videoTitle=&quot;Easy Perlin Noise Flow Fields&quot;
/&amp;gt;&lt;/p&gt;
&lt;h2&gt;Tweaks&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://p5js.org/&quot;&gt;p5&lt;/a&gt; can be a bit of a heavy library and it&apos;s global nature makes it not ideal for a final implementation, I ported the code to be mostly vanilla &amp;lt;abbr title=&quot;JavaScript&quot;&amp;gt;js&amp;lt;/abbr&amp;gt; with the exception of the &lt;a href=&quot;https://github.com/josephg/noisejs&quot;&gt;perlin noise&lt;/a&gt;, the author of the library has a good article of how it works, &lt;a href=&quot;https://joeiddon.github.io/projects/javascript/perlin.html&quot;&gt;&quot;Creating perlin noise in JS&quot;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;p
class=&quot;codepen&quot;
data-height=&quot;300&quot;
data-theme-id=&quot;dark&quot;
data-default-tab=&quot;js,result&quot;
data-slug-hash=&quot;dyQBGMG&quot;
data-preview=&quot;true&quot;
data-user=&quot;dobladov&quot;
style=&quot;height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;&quot;&lt;/p&gt;
&lt;blockquote&gt;&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;span&amp;gt;
See the Pen
&amp;lt;a href=&quot;https://codepen.io/dobladov/pen/dyQBGMG&quot;&amp;gt;
Flow Field with chromatic aberration
&amp;lt;/a&amp;gt;
by DobladoV (&amp;lt;a href=&quot;https://codepen.io/dobladov&quot;&amp;gt;@dobladov&amp;lt;/a&amp;gt;) on
&amp;lt;a href=&quot;https://codepen.io&quot;&amp;gt;CodePen&amp;lt;/a&amp;gt;.
&amp;lt;/span&amp;gt;
&amp;lt;/p&amp;gt;
&amp;lt;script async src=&quot;https://cpwebassets.codepen.io/assets/embed/ei.js&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p&gt;One of the ideas I had in mind for the site theme was using &lt;a href=&quot;https://en.wikipedia.org/wiki/Chromatic_aberration&quot;&gt;chromatic aberration&lt;/a&gt; for some elements, adding it to the flow field really makes it pop just by displacing the particles in red, green and blue; At this point it looked good, but it had some problems, making it such a huge background element requires many particles, this can be an intensive task so if I could make it smaller.&lt;/p&gt;
&lt;h2&gt;Inspiration&lt;/h2&gt;
&lt;p&gt;The idea of making it an orb was inspired by this &lt;a href=&quot;https://twitter.com/wiz_mud/status/1687956508531679232&quot;&gt;meme&lt;/a&gt; and his wonderful game &lt;a href=&quot;http://wiz.zone&quot;&gt;Wiz.Zone&lt;/a&gt;, I thought using the orb and including the flow field inside would look interesting, while reducing the number of particles needed, I&apos;m definitely pleased with the result.&lt;/p&gt;
&lt;p&gt;&amp;lt;img
src={orbMeme.src}
alt=&quot;Meme with two images, one says &apos;Our hands look like this..&apos; representing heavily used keyboards and tobaco stained fingers, the other says &apos;So yours can look like this&apos; with a picture of a hand with a glowing orb in the middle.&quot;
width={orbMeme.width}
height={orbMeme.height}
style={{
width: &quot;27rem&quot;,
display: &quot;flex&quot;,
margin: &quot;0 auto&quot;,
maxWidth: &quot;100%&quot;,
left: 0,
}}
/&amp;gt;&lt;/p&gt;
&lt;p&gt;I also decided to use a photo of my hand and use a dither filter using https://doodad.dev/dither-me-this, personally it would look better with more contrast, but for an initial version it looks good enough, the hand it&apos;s also divided in two different layers to make give the impression to be between the fingers.&lt;/p&gt;
&lt;h2&gt;Final touches&lt;/h2&gt;
&lt;p&gt;I added some other changes like turning the direction of the flow according to the mouse position, and some minor change that probably nobody will ever notice, to do the same using the device orientation on phones.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;window.addEventListener(
  &quot;deviceorientation&quot;,
  (event) =&amp;gt; {
    leftToRight = event.gamma;
  },
  true
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;There are some other minor changes like stacked shadows to make it look more like glass and a shine in the background.&lt;/p&gt;
&lt;p&gt;As an Easter egg, I also made it show some basic animation if JavaScript it&apos;s disabled.&lt;/p&gt;
&lt;p&gt;&amp;lt;video width=&quot;400&quot; autoplay loop style={{ maxWidth: &quot;100%&quot;, display: &quot;flex&quot;, margin: &quot;0 auto&quot; }}&amp;gt;
&amp;lt;source src={noJsOrb} type=&quot;video/webm&quot; /&amp;gt;
&amp;lt;/video&amp;gt;&lt;/p&gt;
</content:encoded><category>javascript</category><category>logo</category><category>orb</category><category>p5</category><category>animation</category><category>flow field</category><category>chromatic-aberration</category><category>noise</category><category>dither</category><author>Daniel Doblado</author></item><item><title>Old media feels new</title><link>https://dobla.do/blog/old-media-feels-new/</link><guid isPermaLink="true">https://dobla.do/blog/old-media-feels-new/</guid><description>Lately, I get the feeling that most of the entertainment I want to consume has already been made.</description><pubDate>Sun, 02 Feb 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;import YoutubeVideo from &quot;#astro/components/YoutubeVideo.astro&quot;;&lt;/p&gt;
&lt;p&gt;Once a coworker jokingly said &lt;em&gt;“Why watch movies if all of them are some variation of Shakespeare&apos;s plays?&quot;&lt;/em&gt;, there’s some truth to that, most movies and shows follow the same arc, and you know what’s coming all along; Even when there are still many ways to say the same in beautiful and slightly different ways, repetition is something you can feel.&lt;/p&gt;
&lt;p&gt;Which made me think &lt;em&gt;“If the content is already made, maybe I can find it in other ways”&lt;/em&gt;, and maybe closer to the source. Most of the time the original is better than the reboot, and with this thought, I started to look for older content on archiving platforms.&lt;/p&gt;
&lt;p&gt;I stumbled upon &lt;a href=&quot;https://de.wikipedia.org/wiki/The_Dick_Van_Dyke_Show&quot;&gt;The Dick Van Dyke Show&lt;/a&gt; thanks to &lt;a href=&quot;https://kottke.org/24/12/0045909-you-can-watch-the-first-1&quot;&gt;this article&lt;/a&gt;, and gave a try to the first episode.&lt;/p&gt;
&lt;p&gt;I have to say I was completely entertained during the whole episode. Besides the “oddity” of being in black and white, the way it flows felt different; The actors complete their sentences, and other actors react within the same scene without any cuts! There’s some higher quality in their acting compared to contemporary shows, probably due to the cost of film and the difficulty of editing at the time, where they had to put more effort into doing their bits in one take than now where every pixel and sound can be edited.&lt;/p&gt;
&lt;p&gt;&amp;lt;YoutubeVideo
loading=&quot;eager&quot;
videoId=&quot;XHXG25zKhCI&quot;
videoTitle=&quot;The Dick Van Dyke Show&quot;
/&amp;gt;&lt;/p&gt;
&lt;p&gt;Somehow a show from the 60s felt modern and connected me more to people that are far away from my generation. I used to think &lt;a href=&quot;https://en.wikipedia.org/wiki/Seinfeld&quot;&gt;Seinfeld&lt;/a&gt; was a more innovative show when it came, creating this kind of sitcom, but turns out it’s not so different from The Dick Van Dyke show. Both have a protagonist with 2 friends that struggle with social situations and are comedians, while having similar locations. Both shows are good, but the similarities really surprised me.&lt;/p&gt;
&lt;p&gt;The same happens with books that have been written even thousands of years ago. &lt;a href=&quot;https://en.wikipedia.org/wiki/Meditations&quot;&gt;Meditations&lt;/a&gt; by &lt;a href=&quot;https://en.wikipedia.org/wiki/Marcus_Aurelius&quot;&gt;Marcus Aurelius&lt;/a&gt; has the same issues we face nowadays. If written with modern language you could not see how ancient this book is. His problems are the same as our problems, we struggle the same way and not much has changed.&lt;/p&gt;
&lt;p&gt;What I want to say is that there’s not so much disconnect between current and older generations, and their content is not so different from what we might find in the present.&lt;/p&gt;
&lt;p&gt;Could be that my feeling of boredom with new content is simply Juvenoia, a term I learned in this great &lt;a href=&quot;https://www.youtube.com/watch?v=LD0x7ho_IYc&quot;&gt;VSauce video&lt;/a&gt;, but in this case, I think my worry with content is justified. As it’s easier to produce content we are more likely to produce garbage, or simply to put less effort, which is basically why I’m not looking forward to consuming any AI-generated content.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://en.wiktionary.org/wiki/juvenoia&quot;&gt;Juvenoia&lt;/a&gt;: The fear or hostility directed by an older generation toward a younger one, or toward youth culture in general.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Right now I can find old media on &lt;a href=&quot;https://archive.org/&quot;&gt;Archive.org&lt;/a&gt;, directly on &lt;a href=&quot;https://www.youtube.com/&quot;&gt;YouTube&lt;/a&gt; or even downloading it (I won’t feel bad for downloading a movie where most of the cast is already dead).&lt;/p&gt;
&lt;p&gt;For reading, &lt;a href=&quot;https://standardebooks.org/&quot;&gt;Standard Ebooks&lt;/a&gt; provides me with many good books. You can even &lt;a href=&quot;https://standardebooks.org/bulk-downloads&quot;&gt;bulk download&lt;/a&gt; their library, which should basically supply a lifetime of books.&lt;/p&gt;
&lt;p&gt;With this feeling, I stopped my Netflix subscription. I&apos;m finding enough good free content to fill out my entertainment. The first issue I thought I would encounter is that there’s rarely people who consume the same content around me, and it’s definitely harder to talk with coworkers about shows, but due to fragmentation in streaming this was rarely happening.&lt;/p&gt;
&lt;p&gt;Even popular shows that I enjoy and have become quite popular, &lt;a href=&quot;https://en.wikipedia.org/wiki/Severance_(TV_series)&quot;&gt;Severance&lt;/a&gt; in this case is mostly unknown to my coworkers because it&apos;s only available on Apple TV. I still enjoy new shows, but there’s a lot of old content already made that I can extract value.&lt;/p&gt;
&lt;p&gt;Somehow taking this route feels more relaxing than going to the next popular thing, and humbles my thoughts about my generation being smarter simply because we have the internet now.&lt;/p&gt;
&lt;p&gt;This Arthur C. Clarke video really says a lot about the imagination of past generations vs ours now.&lt;/p&gt;
&lt;p&gt;&amp;lt;YoutubeVideo
videoId=&quot;YwELr8ir9qM&quot;
videoTitle=&quot;1964: Arthur C Clarke Predicts the Future | Horizon | Past Predictions | BBC Archive&quot;
/&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;That is why the future is so endlessly fascinating because try as we can, we&apos;ll never outguess it.&lt;/p&gt;
&lt;/blockquote&gt;
</content:encoded><category>opinion</category><category>entertainment</category><category>shows</category><category>books</category><category>movies</category><category>content</category><category>internet</category><author>Daniel Doblado</author></item><item><title>Reading books on my phone</title><link>https://dobla.do/blog/reading-books-on-my-phone/</link><guid isPermaLink="true">https://dobla.do/blog/reading-books-on-my-phone/</guid><description>While physical books are great, I prefer reading on my phone</description><pubDate>Thu, 17 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;What started as a test has become a habit, my mentality used to be &quot;reading physical books is better than digital&quot;, because we like to follow the motto:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;reject modernity, 𝖊𝖒𝖇𝖗𝖆𝖈𝖊 𝖙𝖗𝖆𝖉𝖎𝖙𝖎𝖔𝖓&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Kindle experience&lt;/h2&gt;
&lt;p&gt;But after buying my first &lt;a href=&quot;https://www.amazon.com/Kindle-Paperwhite-reader-Previous-Generation/dp/B00QJE3MGU&quot;&gt;Kindle (7st gen)&lt;/a&gt;, modernity had some good points, I preferred physical books, but a lot of the convenience of the Kindle made me continue digital.&lt;/p&gt;
&lt;p&gt;Firstly, the progress bar, the psychological effect that it has on me telling me &quot;&amp;lt;tt&amp;gt;there are only 10 pages left in the chapter&amp;lt;/tt&amp;gt;&quot; is motivation enough to keep me reading until the end of a chapter.&lt;/p&gt;
&lt;p&gt;Immediately starting to read the book with the click of a button or leaving it without having to worry about losing the page, means I can read in short bursts, like when I&apos;m waiting for the bus.&lt;/p&gt;
&lt;p&gt;But still, the Kindle wasn&apos;t as good as a book, the screen was small, the refresh rate was too slow, and reading at night still required a light (I had a model without a backlight).&lt;/p&gt;
&lt;p&gt;Transferring books was annoying, I kept the Kindle almost always on airplane mode to save battery, so I had to connect it to my computer to transfer books with &lt;a href=&quot;https://calibre-ebook.com/&quot;&gt;Calibre&lt;/a&gt; or connect it to Wi-Fi to transfer books with email or the Amazon store, and while it was not a big deal, it was still annoying.&lt;/p&gt;
&lt;p&gt;Although the battery life was amazing, it was still another device to charge and carry around, with its own inconveniences.&lt;/p&gt;
&lt;h2&gt;Phone experience&lt;/h2&gt;
&lt;p&gt;I tried to use my phone as my main reading device after accidentally stepping on my Kindle, which unfortunately didn&apos;t survive. Sometimes misfortune brings new opportunities.&lt;/p&gt;
&lt;p&gt;Turns out I always carry my phone with me; just the mental barrier of having to carry my kindle was enough to make me read less.&lt;/p&gt;
&lt;p&gt;Now I have the reading app very prominent on my phone screen, reminding me to read, meaning I have many short bursts of time to read before a meeting, while commuting, or while shopping, with the advantage that it&apos;s time that otherwise would be wasted with some social media app.&lt;/p&gt;
&lt;p&gt;The screen is way better; yes, it&apos;s not e-ink, yet the backlight makes it easier to read in the dark, and the refresh rate and resolution are miles ahead of any e-ink device. I don&apos;t get much strain from reading on screens like my phone or laptops, so this was not a problem for me, but I understand others might find it inconvenient.&lt;/p&gt;
&lt;p&gt;My current phone might not be lighter than my Kindle device, but it&apos;s definitely smaller; even the smallest Kindle device looks goofy in a pocket.&lt;/p&gt;
&lt;p&gt;One of the best parts was using &lt;a href=&quot;https://play.google.com/store/books?hl=en&amp;amp;gl=US&quot;&gt;Google Play Books&lt;/a&gt; I&apos;m not sure how, but they just let you upload any files you want and sync them over the internet for free. Just by navigating to &lt;a href=&quot;https://play.google.com/books/uploads&quot;&gt;Play Books upload&lt;/a&gt; and dragging the book, it automatically sends the book to all your devices and keeps them in sync, one of those services that just works.&lt;/p&gt;
&lt;p&gt;And the app it&apos;s good, integrating dictionary, notes, good UI, and a must have feature, using the volume buttons to turn pages.&lt;/p&gt;
&lt;h2&gt;Physical book experience&lt;/h2&gt;
&lt;p&gt;While I still buy physical books, most of the time I get the same book digitally just to read at night while my partner sleeps.&lt;/p&gt;
&lt;p&gt;The transport of a huge book, the need for light to see properly, and how annoying is to turn the pages at the start and end of a thick book are problems that I want to let go of.&lt;/p&gt;
&lt;p&gt;I will miss the smell, the sense of physical progress, the uncontested battery life, and the feeling of having a bookshelf, but I think I can live without them.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I try to be pragmatic, and while nostalgia is a huge component here, what made me read more during these past 4 years was my phone and the convenience that brings, so I&apos;ll keep doing it.&lt;/p&gt;
&lt;p&gt;Who knows, maybe in the future we can have screens in our retinas, and I will write this post again.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
</content:encoded><category>opinion</category><category>reading</category><category>books</category><category>phone</category><category>apps</category><category>kindle</category><category>productivity</category><author>Daniel Doblado</author></item><item><title>Rust with a taste of React</title><link>https://dobla.do/blog/rust-with-a-taste-of-react/</link><guid isPermaLink="true">https://dobla.do/blog/rust-with-a-taste-of-react/</guid><description>Learning Rust and finding similar patterns to React with the Dioxus framework</description><pubDate>Fri, 12 Sep 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Learning Rust&lt;/h2&gt;
&lt;p&gt;For the past months I have been more and more interested in &lt;a href=&quot;https://rust-lang.org/&quot;&gt;Rust&lt;/a&gt;, some of my preferred sources for learning are the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://doc.rust-lang.org/book/&quot;&gt;Rustbook&lt;/a&gt;: The most in-depth resource, a must for everyone who aims to learn the language, more dense than the rest of the other resources I will mention, probably the most cared and complete resource made by the community.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://web.mit.edu/rust-lang_v1.25/arch/amd64_ubuntu1404/share/doc/rust/html/book/second-edition/index.html&quot;&gt;MIT’s introduction to Rust&lt;/a&gt;: I loved reading it, the lessons go straight to the point and it rarely feels boring, it made the learning process far easier than the official Rust book.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://rust-lang-nursery.github.io/rust-cookbook/&quot;&gt;Rust Cookbook&lt;/a&gt;: following the philosophy of teaching by example, wonderful to understand real cases with short snippets.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://learnxinyminutes.com/rust/&quot;&gt;Learn X in Y minutes&lt;/a&gt;: a short overview of the language, a good site for any programming language.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://exercism.org/tracks/rust&quot;&gt;Exercism&lt;/a&gt;: I completed some of the exercises here, and while I didn’t use it in depth, I still consider it a good resource.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The value of Rust&lt;/h2&gt;
&lt;p&gt;One of the best improvements in my work life has been TypeScript. The introduction of type safety has improved my confidence when refactoring code so much that I can&apos;t imagine writing plain JavaScript anymore, at least not without &lt;a href=&quot;https://jsdoc.app/&quot;&gt;JSDoc&lt;/a&gt; and TS checking it. So what if I could take that safety even further and catch many issues before the code even compiles?&lt;/p&gt;
&lt;p&gt;Most of the concepts that Rust introduces make a lot of sense, immutable by default, influenced by functional programming, good pattern matching, and the most important or what makes it different, borrow checking entirely avoiding garbage collection.&lt;/p&gt;
&lt;h2&gt;Writing UIs with Rust&lt;/h2&gt;
&lt;p&gt;Now that I’m at a point where all Rust concepts and patterns finally clicked and make sense, I wanted to see what can I do with it and Rust has these wonderful pages &lt;a href=&quot;https://github.com/UgurcanAkkok/AreWeRustYet&quot;&gt;Are we Rust yet?&lt;/a&gt; to showcase what’s the current state of the language on different fields, and as a frontend dev, the page that caught my attention first was &lt;a href=&quot;https://areweguiyet.com/&quot;&gt;Are we GUI yet?&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;First I tried &lt;a href=&quot;https://www.egui.rs/&quot;&gt;EGui&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;An easy-to-use immediate mode GUI that runs on both web and native.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Their &lt;a href=&quot;https://www.egui.rs/#demo&quot;&gt;demo&lt;/a&gt; is incredible, super fast, well-made, with tons of examples, but with a huge problem, using right click reveals that all the UI is drawn to canvas, which is not ideal for accessibility, so I kept looking until I found the topic of this post, &lt;a href=&quot;https://dioxuslabs.com/&quot;&gt;Dioxus&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;The Dioxus framework&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;Elegant &lt;strong&gt;React-like&lt;/strong&gt; library for building user interfaces for desktop, web, mobile, SSR, liveview, and more.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Everything about this framework looks amazing, it targets all the platforms that I work on,with all the nices things about Rust, sub-second hot reloads, not rendering to a canvas, and the most important thing it feels like writing React, the framework I have been using for many years professionally.&lt;/p&gt;
&lt;p&gt;I bet most React developers who never touched Rust can read this component code without much issue.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;use dioxus::prelude::*;

pub fn App() -&amp;gt; Element {
    let mut count = use_signal(|| 0);

    rsx! {
        h1 { &quot;High-Five counter: {count}&quot; }
        button { onclick: move |_| count += 1, &quot;Up high!&quot; }
        button { onclick: move |_| count -= 1, &quot;Down low!&quot; }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Yes, &lt;code&gt;use_signal&lt;/code&gt; is not named &lt;code&gt;useState&lt;/code&gt; and the syntax is slightly off from JS with &lt;code&gt;let mut&lt;/code&gt;  and &lt;code&gt;pub fn&lt;/code&gt; and of course is not &lt;code&gt;jsx&lt;/code&gt; but &lt;code&gt;rsx&lt;/code&gt;, anyway, the structure and overall feel of a React component is there.&lt;/p&gt;
&lt;p&gt;And for me this is wonderful, not only it brings those advantages, but simplifies all the development experience to just Rust, something that an alternative like react-native does not, in general react-native apps are very complex and weirdly put together.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;So far I only used Dioxus for test projects, it&apos;s just so simple to set and run with &lt;code&gt;dx new [project-name]&lt;/code&gt;, their get started &lt;a href=&quot;https://dioxuslabs.com/learn/0.7/tutorial/component&quot;&gt;“Hot Dog”&lt;/a&gt; app covers a good chunk of what&apos;s needed for a web app including data fetching, state management, UI, and their hooks like &lt;code&gt;use_resource&lt;/code&gt; feel well thought and useful, with request invalidation integrated.&lt;/p&gt;
&lt;p&gt;There are also some negatives, as a new framework in constant development I could find some annoying bugs, some programs like widgets are not possible to develop with it yet, the ecosystem is clearly lacking compared with react-native, the initial compile time is slower too, there’s less information online and its future is not as sure, if they continue improving it I can see Dioxus as a good competitor to React Native and becoming a great tool for multi-platform development, while being more consistent and unified under Rust.&lt;/p&gt;
&lt;p&gt;This is the current state, in the near future I hope to make more use of Dioxus and ideally find it as useful as React-Native has been, while giving me even more safety and performance.&lt;/p&gt;
</content:encoded><category>rust</category><category>react</category><category>react-native</category><category>development</category><category>dioxus</category><category>frontend</category><category>ui</category><category>programming</category><author>Daniel Doblado</author></item><item><title>Supporting old browsers</title><link>https://dobla.do/blog/supporting-old-browsers/</link><guid isPermaLink="true">https://dobla.do/blog/supporting-old-browsers/</guid><description>The process of debugging and supporting an old iOS browser</description><pubDate>Tue, 07 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;The problem&lt;/h2&gt;
&lt;p&gt;I reached a point at work where I have to support old iOS versions, and by old I mean iOS 13, which at the moment of writing this post is 5 years old. If you have to ask me, this is a waste of resources for a very small amount of users and while I made this point clear, this is my job, and it has to be done.&lt;/p&gt;
&lt;p&gt;So now on, each release and each upgrade of packages will require testing on devices that even the manufacturers themselves don’t care about anymore. Even worse, we have to keep devices that could be upgraded to another major version locked in old versions.&lt;/p&gt;
&lt;p&gt;The device I’m using right now is an &lt;em&gt;iPhone 8&lt;/em&gt; running &lt;code&gt;iOS 13.5.1&lt;/code&gt; which is upgradable at least to version &lt;code&gt;16&lt;/code&gt;, a device released in 2017 and not supported anymore by Apple.&lt;/p&gt;
&lt;h2&gt;Identifying the parse error&lt;/h2&gt;
&lt;p&gt;After upgrading to &lt;code&gt;react-redux@9.2.0&lt;/code&gt; this old phone is incapable of parsing the bundle, with a very unhelpful message, even with source maps.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;We know that some token can’t be read causing the whole app to crash, meaning that one character in the bundle is code that was not supported in this version of iOS. How do we find the precise code?&lt;/p&gt;
&lt;p&gt;In this case, I was able to narrow it to the packages updated. If this was not possible, let’s say an existing codebase would need to be adapted to work on old devices, some other debug can be added like including comments with the name of the modules imported to see where it breaks.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Getting closer but how do we know the precise line of code? Bundling in this case removed all the information. Once I knew that &lt;code&gt;./node_modules/react-redux/dist/react-redux.mjs&lt;/code&gt; was the real issue, the next step was to recreate it on a smaller setup.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
&amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;&amp;gt;
    &amp;lt;title&amp;gt;Document&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;

&amp;lt;body&amp;gt;
    Debugging react-redux on iOS 13
    &amp;lt;script type=&quot;module&quot;&amp;gt;
        import &apos;./react-redux.mjs&apos;
    &amp;lt;/script&amp;gt;
&amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Once serving this file and accessing it from the device, the error was much more clear, I could see the line causing the issue.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  const contextMap = gT[ContextKey] ??= /* @__PURE__ */ new Map();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;In this case, the operator &lt;code&gt;??=&lt;/code&gt; &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Nullish_coalescing_assignment&quot;&gt;Nullish coalescing assignment&lt;/a&gt; was not supported at this version of iOS: &lt;a href=&quot;https://caniuse.com/?search=Nullish%20coalescing%20assignment&quot;&gt;Can I use Search
Nullish coalescing assignment ?&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Fixing the code&lt;/h2&gt;
&lt;h3&gt;Manual Patching&lt;/h3&gt;
&lt;p&gt;There are various routes to get the problem solved, a simple one would be to patch the library in &lt;code&gt;node_modules&lt;/code&gt;. Initially, I did it manually to make sure that was the solution for running &lt;code&gt;react-redux&lt;/code&gt; on the device simply changing the line of code for:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;//const contextMap = gT[ContextKey] ??= /* @__PURE__ */ new Map();
const contextMap = gT[ContextKey] = gT[ContextKey] === undefined || gT[ContextKey] === null 
  ? /* @__PURE__ */ new Map() 
  : gT[ContextKey];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The patch can be more &lt;em&gt;permanent&lt;/em&gt; using &lt;code&gt;npx patch-package&lt;/code&gt;: &lt;a href=&quot;https://stackoverflow.com/questions/72820625/what-is-the-proper-way-to-patch-a-node-modules-module/77385111#77385111&quot;&gt;Stack overflow - Patch a &quot;node_modules&quot; (Node.js) module&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;This could be a good hotfix, but we want it fixed for future cases.&lt;/p&gt;
&lt;h3&gt;Creating a pull request&lt;/h3&gt;
&lt;p&gt;Second solution, to create a PR in the &lt;code&gt;react-redux&lt;/code&gt; project and use less modern JavaScript, this solution is far from ideal. I don’t like to write older code, we should look at the future and while it does make the library more compatible I would have to justify and convince them to merge it, so I decided not to do it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/reduxjs/react-redux/blob/7e2fdd4ee2021e4282e12ba9fc722f09124e30cd/src/components/Context.ts#L31&quot;&gt;Line of code to change&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;Babel plugins&lt;/h3&gt;
&lt;p&gt;Third solution, transpile the library using &lt;a href=&quot;https://babeljs.io/&quot;&gt;Babel&lt;/a&gt; to more widely supported JavaScript automatically using plugins, and here is where the issue got more confusing. The project already used Babel to do exactly this, in fact &lt;code&gt;@babel/preset-env&lt;/code&gt; includes the two plugins that are required to make the failing file work &lt;a href=&quot;https://babeljs.io/docs/babel-plugin-transform-nullish-coalescing-operator&quot;&gt;@babel/plugin-transform-nullish-coalescing-operator&lt;/a&gt; and &lt;a href=&quot;https://babeljs.io/docs/babel-plugin-transform-logical-assignment-operators&quot;&gt;@babel/plugin-transform-logical-assignment-operators&lt;/a&gt;, but somehow after a build the file ended intact and still containing &lt;code&gt;??=&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;So what was happening? We had to transpile &lt;code&gt;react-redux&lt;/code&gt; in the past to support other issues, as I could see in the webpack file &lt;code&gt;resolve(__dirname, &apos;node_modules/react-redux&apos;)&lt;/code&gt; was in the &lt;code&gt;include&lt;/code&gt; for &lt;code&gt;babel-loader&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Well, the newer version of the package uses JS modules, and now the extension of the file was &lt;code&gt;.mjs&lt;/code&gt; instead of &lt;code&gt;.js&lt;/code&gt; meaning that the regex to match the files was completely ignoring the file &lt;code&gt;/\.(js|jsx)$/&lt;/code&gt;. After simply adding &lt;code&gt;/\.(js|jsx|mjs)$/&lt;/code&gt; the file was correctly parsed and worked on iOS 13.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;It’s important to understand how other people’s code end up in your build. In this case, I learned how to better debug third-party code with Safari which is not fun, how to adapt code to work in devices that were not supposed to work with it, and how to make sure that the code is transpiled correctly.&lt;/p&gt;
&lt;p&gt;I hope this post helps someone in the future, at the very least was useful for a small subset of users that still use old devices.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
</content:encoded><category>maintenance</category><category>javascript</category><category>ios</category><category>babel</category><category>redux</category><category>debugging</category><author>Daniel Doblado</author></item><item><title>Simplifying browser extensions with Tampermonkey</title><link>https://dobla.do/blog/simplifying-browser-extensions-with-tampermonkey/</link><guid isPermaLink="true">https://dobla.do/blog/simplifying-browser-extensions-with-tampermonkey/</guid><description>A brief introduction to Tampermonkey as an alternative to extensions.</description><pubDate>Mon, 28 Aug 2023 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Introduction&lt;/h2&gt;
&lt;p&gt;Not long ago, a coworker sent me the code of an extension he made, he did a wonderful work, and I loved how he came up with a better and simpler solution to the problem that I was overengineering. Unfortunately, the extension was only compatible with &lt;em&gt;Firefox&lt;/em&gt; and I use &lt;em&gt;Chrome&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Initially, I thought it was going to be a matter of adapting the manifest a bit to make it work on &lt;em&gt;Chrome&lt;/em&gt;, but it was not that simple; even when using the same manifest version, the way background scripts are loaded is different for each browser.&lt;/p&gt;
&lt;p&gt;Also, some parts of the code were incompatible with &lt;em&gt;Chrome&lt;/em&gt;, for example, how the messages could be awaited in &lt;em&gt;Firefox&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;I did end up porting the code, but it was not a simple task and left me wondering why it was so cumbersome to make an extension compatible with both browsers.&lt;/p&gt;
&lt;p&gt;After all, the changes to the extension APIs are meant to close the gap between browsers, but it seems to be a way to remove some of the more powerful APIs that get in the way of blocking advertisements.&lt;/p&gt;
&lt;p&gt;Overall, I&apos;m very disappointed with the developer experience of web extensions. Porting &lt;a href=&quot;https://github.com/dobladov/youtube2Anki&quot;&gt;Youtube2Anki&lt;/a&gt; to manifest &lt;code&gt;v3&lt;/code&gt; was a lot of work with no benefits to the users, and with the drawback that I have to keep two different versions for &lt;em&gt;Firefox&lt;/em&gt; (still &lt;code&gt;v2&lt;/code&gt;) and &lt;em&gt;Chrome&lt;/em&gt;, all these problems got me thinking about what I can do as an alternative.&lt;/p&gt;
&lt;h2&gt;Why Tampermonkey&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://www.tampermonkey.net/&quot;&gt;Tampermonkey&lt;/a&gt; allows you to execute scripts and styles on any site, exactly what a custom extension does, but it has some advantages:&lt;/p&gt;
&lt;p&gt;The development is much easier; it requires less boilerplate, and it&apos;s effortless to debug due to not having multiple contexts handling many of the tedious permissions for you.&lt;/p&gt;
&lt;p&gt;On top of it, it reloads after saving the changes of a &lt;em&gt;Tampermonkey&lt;/em&gt; script; there&apos;s no need to reload the extension; just refresh the page and the changes are applied. This means faster iterations and less time wasted.&lt;/p&gt;
&lt;p&gt;In my case, I had three requirements.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;I needed to be able to inject a script at a specific URL.&lt;/li&gt;
&lt;li&gt;Send notifications to the user.&lt;/li&gt;
&lt;li&gt;Open a URL in a new tab.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Tampermonkey allows me to do all of this.&lt;/p&gt;
&lt;p&gt;The permissions only require adding some &lt;a href=&quot;https://www.tampermonkey.net/documentation.php?locale=en#meta:grant&quot;&gt;grants&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// @grant        GM_notification
// @grant        GM_openInTab`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And executing only in the URL is as simple as &lt;code&gt;// @match  [your URl]&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;After porting the logic, what used to be basically an app with multiple files, setup, packaging, etc, became a single file with a few lines of code that just focused on doing what it needed to do.&lt;/p&gt;
&lt;p&gt;Not only that, but there was no need for boilerplate to send data back and forth between the background script and the content script, and notifications could be simplified by not requiring a &lt;code&gt;tabId&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;And the best part is that there are no differences between &lt;em&gt;Firefox&lt;/em&gt; and &lt;em&gt;Chrome&lt;/em&gt;; they both run the same script and execute the same functionality.&lt;/p&gt;
&lt;h2&gt;Other usages&lt;/h2&gt;
&lt;p&gt;I have incorporated &lt;em&gt;Tampermonkey&lt;/em&gt; into my work to the point that some other of my coworkers use it now, mostly dev code that we don&apos;t want to deliver to production.&lt;/p&gt;
&lt;p&gt;For example, as a quick way to set special cookies or log users without a form, since most of the scripts only require a file, we can easily share them as a &lt;a href=&quot;https://gist.github.com/&quot;&gt;gist&lt;/a&gt;, and they can be easily installed by anyone.&lt;/p&gt;
&lt;h2&gt;Conclusion&lt;/h2&gt;
&lt;p&gt;I would only consider &lt;em&gt;Tampermonkey&lt;/em&gt; a tool for developers; there are many cases where a full extension is needed, but for quick hacks or to avoid the hassle of maintaining multiple versions of the same extension, it&apos;s a great tool.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
</content:encoded><category>programming</category><category>javascript</category><category>tampermonkey</category><category>extensions</category><category>chrome</category><category>firefox</category><author>Daniel Doblado</author></item><item><title>SSR with Bun and React</title><link>https://dobla.do/blog/ssr-with-bun-and-react/</link><guid isPermaLink="true">https://dobla.do/blog/ssr-with-bun-and-react/</guid><description>How to set up a server side rendered app using Bun, react and react-router</description><pubDate>Tue, 28 Jan 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;The goal is to have a server able to stream React Components directly from the server using the client for navigation, removing the need to request data from the server after the first load, some call it &lt;em&gt;&quot;Universal Data Fetching&quot;&lt;/em&gt; or &lt;em&gt;&quot;Isomorphic Data Fetching&quot;&lt;/em&gt;.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This pattern ensures that the data required to render a page is fetched on the server during the initial request and then re-fetched or reused on the client side for subsequent interactions.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The full repository with the code can be seen in &lt;a href=&quot;https://github.com/dobladov/bun-ssr-react&quot;&gt;dobladov/bun-ssr-react&lt;/a&gt;, I’ll be omitting all imports to keep the snippets shorter.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;⚠️ Disclaimer&lt;/strong&gt;: I&apos;m not implementing &lt;a href=&quot;https://react.dev/reference/rsc/server-components&quot;&gt;React Server Components&lt;/a&gt;, but good old React hydrated on the client.&lt;/p&gt;
&lt;p&gt;I want to mention another repo that is doing similar logic, but all responses come from the server: &lt;a href=&quot;https://github.com/alexkates/ssr-bun-react/blob/main/index.tsx&quot;&gt;alexkates/ssr-bun-react&lt;/a&gt;, a good approach if you want to have a more traditional SSR.&lt;/p&gt;
&lt;h2&gt;Why did I choose Bun?&lt;/h2&gt;
&lt;p&gt;It’s a batteries-included tool, if you had to make SSR work with webpack and node, you might know how much of a pain it is to deal with JSX in both the server and client, then add other issues like babel plugins, common JS modules vs ESM and you realize most of the setup exists to fix issues related to JavaScript instead of focusing on writing code.&lt;/p&gt;
&lt;p&gt;Bun solves all of those directly for you, including the server, although you might want to use &lt;a href=&quot;https://elysiajs.com/&quot;&gt;Elysia&lt;/a&gt; or &lt;a href=&quot;https://expressjs.com/&quot;&gt;Express&lt;/a&gt; for more complex apps.&lt;/p&gt;
&lt;p&gt;I have to be honest, so far I’m not using &lt;a href=&quot;https://bun.sh/&quot;&gt;Bun&lt;/a&gt; at work or in any projects that are critical, only side projects and occasional scripts, but I love it so far, and the only thing that worries me is the company behind it cutting funding or trying to lock you in their ecosystem or the main developers leaving the project, because wow, what an incredible job they are making with such a small team.&lt;/p&gt;
&lt;p&gt;In any case, it’s a project that’s forcing &lt;a href=&quot;https://nodejs.org/en&quot;&gt;Node&lt;/a&gt; and &lt;a href=&quot;https://deno.com/&quot;&gt;Deno&lt;/a&gt; to improve themselves and this competition so far brings many new functionalities that make my work easier.&lt;/p&gt;
&lt;h2&gt;Build process&lt;/h2&gt;
&lt;p&gt;We start by creating a new bun project and adding the dependencies&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir example-bun-ssr
cd example-bun-ssr
bun init
# package name (example-bun-ssr):
# entry point (index.ts): src/index.tsx
bun add react react-dom react-router
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s add a script to &lt;a href=&quot;https://github.com/dobladov/bun-ssr-react/blob/main/package.json&quot;&gt;package.json&lt;/a&gt; to run in dev mode and also watch for changes.
If you wonder why &lt;code&gt;.tsx&lt;/code&gt; is because I want to have &lt;a href=&quot;https://react.dev/learn/writing-markup-with-jsx&quot;&gt;JSX&lt;/a&gt; directly in this file.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  &quot;scripts&quot;: {
    &quot;dev&quot;: &quot;bun run --watch src/index.tsx&quot;
  },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We need an entry point for the client build and to generate it to our public folder, we can start by adding this to &lt;a href=&quot;https://github.com/dobladov/bun-ssr-react/blob/main/src/index.tsx&quot;&gt;src/index.tsx&lt;/a&gt;.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Bun.build({
    entrypoints: [&apos;./src/client.tsx&apos;],
    outdir: &apos;src/public/&apos;
})
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Client logic&lt;/h2&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/dobladov/bun-ssr-react/blob/main/src/client.tsx&quot;&gt;src/client.tsx&lt;/a&gt; has two goals, to &lt;a href=&quot;https://react.dev/reference/react-dom/client/hydrateRoot&quot;&gt;hydrate&lt;/a&gt; React once the file is loaded so we can interact with the &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model&quot;&gt;DOM&lt;/a&gt; on the client, and to generate the routing for client navigation.&lt;/p&gt;
&lt;p&gt;&amp;lt;details&amp;gt;
&amp;lt;summary&amp;gt;Rant about react-router&amp;lt;/summary&amp;gt;
I’m very disappointed with the approach of react-router for their docs, they definitely care about the project and add cool functionality, but I find the docs completely lacking and confusing, I can’t link any of the next functions that I’m mentioning, there are barely any examples of how to do SSR, and the framework vs library approach is confusing to even people experienced in using it, there are also many methods that I can’t even find in the API reference even when they are not deprecated, the only way I found to understand how to implement this was to do searches in the repo.&lt;/p&gt;
&lt;p&gt;For next projects I will look at alternatives like &lt;a href=&quot;https://tanstack.com/router/latest&quot;&gt;TanStack Router&lt;/a&gt;.
&amp;lt;/details&amp;gt;&lt;/p&gt;
&lt;p&gt;We need &lt;code&gt;&amp;lt;RouterProvider&amp;gt;&lt;/code&gt; to create a router that can use loaders otherwise a simple &lt;code&gt;&amp;lt;BrowserRouter&amp;gt;&lt;/code&gt; would be enough.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;createBrowserRouter&lt;/code&gt; is equivalent to &lt;code&gt;&amp;lt;BrowserRouter&amp;gt;&lt;/code&gt;, but we need to call it this way for our purpose and I like to use &lt;code&gt;createRoutesFromElements&lt;/code&gt; because I find JSX more intuitive for nested routes.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const root = document.getElementById(&apos;root&apos;);

if (root) {
    hydrateRoot(root, (
        &amp;lt;RouterProvider router={
            createBrowserRouter(
                createRoutesFromElements(
                    Routing()
                )
            )
        } /&amp;gt;
    ));
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Routes&lt;/h2&gt;
&lt;p&gt;Our routes can be very simple, in &lt;a href=&quot;https://github.com/dobladov/bun-ssr-react/blob/main/src/routes.tsx&quot;&gt;src/routing.tsx&lt;/a&gt; we have the following, note that we will re-use this file for server and client which gives us a very clean way to handle our app with minimal config.&lt;/p&gt;
&lt;p&gt;An &lt;code&gt;errorElement&lt;/code&gt; to act as an &lt;a href=&quot;https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary&quot;&gt;error boundary&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&amp;lt;Route&amp;gt;&lt;/code&gt; for each of the routes and inside the only more unusual prop is &lt;code&gt;loader&lt;/code&gt;, used to get the data before the route is loaded.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const Routing = () =&amp;gt; {
  return (
    &amp;lt;Route errorElement={&amp;lt;ErrorPage /&amp;gt;}&amp;gt;
        &amp;lt;Route
          path=&quot;/&quot;
          element={&amp;lt;Home /&amp;gt;}
          loader={getData}
        /&amp;gt;
        &amp;lt;Route path=&quot;/about&quot; element={&amp;lt;About /&amp;gt;} /&amp;gt;
        &amp;lt;Route path=&quot;*&quot; element={&amp;lt;NotFound /&amp;gt;} /&amp;gt;
      &amp;lt;/Route&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;The components are really simple:&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://github.com/dobladov/bun-ssr-react/blob/main/src/ErrorPage.tsx
export const ErrorPage = () =&amp;gt; {
    const error = useRouteError();

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;div&amp;gt;Something went wrong&amp;lt;/div&amp;gt;
            &amp;lt;pre&amp;gt;{JSON.stringify(error, null, 2)}&amp;lt;/pre&amp;gt;
            &amp;lt;Link to=&quot;/&quot;&amp;gt;Go back to home&amp;lt;/Link&amp;gt;
        &amp;lt;/div&amp;gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;useLoaderData&lt;/code&gt; will give us the data requested in the &lt;code&gt;loader&lt;/code&gt; prop&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// https://github.com/dobladov/bun-ssr-react/blob/main/src/Home.tsx
export const Home = () =&amp;gt; {
    const data = useLoaderData&amp;lt;InitialData&amp;gt;();

    useEffect(() =&amp;gt; {
        console.log(&apos;Home loaded&apos;);
    }, []);

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;Home&amp;lt;/h1&amp;gt;
            &amp;lt;pre&amp;gt;{JSON.stringify(data, null, 2)}&amp;lt;/pre&amp;gt;
            &amp;lt;Link to=&quot;/about&quot;&amp;gt;About&amp;lt;/Link&amp;gt;
        &amp;lt;/div&amp;gt;
    )
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// https://github.com/dobladov/bun-ssr-react/blob/main/src/About.tsx
export const About = () =&amp;gt; {
    
    useEffect(() =&amp;gt; {
        console.log(&apos;About loaded&apos;)
    }, [])

    return (
        &amp;lt;div&amp;gt;
            &amp;lt;h1&amp;gt;About&amp;lt;/h1&amp;gt;
            &amp;lt;Link to=&quot;/&quot;&amp;gt;Home&amp;lt;/Link&amp;gt;
        &amp;lt;/div&amp;gt;
    )}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// https://github.com/dobladov/bun-ssr-react/blob/main/src/NotFound.tsx
export const NotFound = () =&amp;gt; (
  &amp;lt;div&amp;gt;
    &amp;lt;h1&amp;gt;404 - Page Not Found&amp;lt;/h1&amp;gt;
    &amp;lt;p&amp;gt;Sorry, the page you are looking for does not exist.&amp;lt;/p&amp;gt;
    &amp;lt;Link to=&quot;/&quot;&amp;gt;Go to Home&amp;lt;/Link&amp;gt;
  &amp;lt;/div&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;To get the data, we need &lt;a href=&quot;https://github.com/dobladov/bun-ssr-react/blob/main/src/utils.ts&quot;&gt;src/utils.ts&lt;/a&gt; with a simple async function that calls an API we will create later on the server.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const getData = async () =&amp;gt; {
    const response = await fetch(&apos;http://localhost:5000/api/example&apos;)
    const json = (await response.json()) as InitialData;
    return json
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Congrats we wrote most of our app, at least the client, we could easily use Bun’s newest &lt;a href=&quot;https://bun.sh/blog/bun-v1.2#html-imports&quot;&gt;HTML static import&lt;/a&gt; and have a client side rendered react app, but we are here to aim for the stars and to do SSR, so let’s do that now and create the server.&lt;/p&gt;
&lt;h2&gt;Server logic&lt;/h2&gt;
&lt;p&gt;We are again back to &lt;a href=&quot;https://github.com/dobladov/bun-ssr-react/blob/main/src/index.tsx&quot;&gt;src/index.tsx&lt;/a&gt;, take a moment to appreciate how simple the code is.&lt;/p&gt;
&lt;p&gt;We need to create a server that listens on a port, and we have some basic path name matching the incoming URL, that&apos;s it.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const server = Bun.serve({
    port: 5000,
    async fetch(req) {
      const {pathname} = new URL(req.url);
      console.info(`Requesting: ${pathname}`);

      // 1. Static assets
      if (pathname.startsWith(&quot;/public/&quot;)) {
        const file = Bun.file(__dirname + pathname);
        return new Response(file);
      }

      // 2. Example API
      if (pathname === &apos;/api/example&apos;) {
        return Response.json({ message: &apos;Hello, World!&apos; });
      }

      const routes = createRoutesFromElements(Routing());
      
      let { query, dataRoutes } = createStaticHandler(routes);
      let context = await query(req) as StaticHandlerContext;
      let router = createStaticRouter(dataRoutes, context);

      // 3. Server-side rendering
      const stream = await renderToReadableStream(
          &amp;lt;App&amp;gt;
            &amp;lt;StaticRouterProvider
              router={router}
              context={context}
            /&amp;gt;
          &amp;lt;/App&amp;gt;,           
        );
        return new Response(stream, {
          headers: { &quot;Content-Type&quot;: &quot;text/html&quot; },
        });
    },
  });

console.info(`Listening on http://localhost:${server.port}`);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Let’s analyze each match:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Allows to serve our static assets in the public folder including the client build generated at the beginning with &lt;code&gt;Bun.build&lt;/code&gt;, our CSS, favicon, images can be served this way.&lt;/li&gt;
&lt;li&gt;Just an example of a JSON API, that both client and server can request.&lt;/li&gt;
&lt;li&gt;Here comes the magic that makes SSR possible, we re-use the client routes with &lt;code&gt;createRoutesFromElements&lt;/code&gt;, and we need &lt;code&gt;createStaticHandler&lt;/code&gt; and &lt;code&gt;createStaticRouter&lt;/code&gt; again here because we use a &lt;code&gt;loader&lt;/code&gt; prop in our &lt;code&gt;Routes&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once we have created the routing, we can stream the App with &lt;a href=&quot;https://react.dev/reference/react-dom/server/renderToReadableStream&quot;&gt;renderToReadableStream&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;We miss only one last component, &lt;a href=&quot;https://github.com/dobladov/bun-ssr-react/blob/main/src/App.tsx&quot;&gt;src/App.tsx&lt;/a&gt;, which is an HTML wrapper where we render the server components and load our client.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export const App = (props: Props) =&amp;gt; {
    return (
        &amp;lt;html lang=&quot;en&quot;&amp;gt;
            &amp;lt;head&amp;gt;
                &amp;lt;meta charSet=&quot;UTF-8&quot;/&amp;gt;
                &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot;/&amp;gt;
            &amp;lt;/head&amp;gt;
            &amp;lt;body&amp;gt;
                &amp;lt;div id=&quot;root&quot;&amp;gt;{props.children}&amp;lt;/div&amp;gt;
                &amp;lt;script type=&quot;module&quot; src=&quot;/public/client.js&quot;&amp;gt;&amp;lt;/script&amp;gt;
            &amp;lt;/body&amp;gt;
        &amp;lt;/html&amp;gt;
    )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And we are ready, &lt;code&gt;bun run dev&lt;/code&gt; should launch the server and provide the URL &lt;a href=&quot;http://localhost:5000/&quot;&gt;http://localhost:5000&lt;/a&gt; where we can check our app.&lt;/p&gt;
&lt;h2&gt;Retrospective&lt;/h2&gt;
&lt;p&gt;As mentioned I like how simple everything is, at least compared to another production project at work with the same goal using node, express and webpack, this is far more maintainable, the build is faster and overall improves in all areas, with less dependencies.&lt;/p&gt;
&lt;p&gt;I will definitely use Bun more and more if they continue providing so much value out of the box, at the moment I&apos;m working on a side project that relies on their &lt;a href=&quot;https://bun.sh/docs/api/sqlite&quot;&gt;SQLite&lt;/a&gt; implementation and I&apos;m very happy with it.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
</content:encoded><category>javascript</category><category>deno</category><category>node</category><category>bun</category><category>ssr</category><category>react</category><category>development</category><category>programming</category><category>deno</category><author>Daniel Doblado</author></item><item><title>Upgrade Your Actions</title><link>https://dobla.do/blog/upgrade-your-actions/</link><guid isPermaLink="true">https://dobla.do/blog/upgrade-your-actions/</guid><description>How to stay up to date with your GitHub Actions and reduce unnecessary dependencies in your workflows.</description><pubDate>Fri, 23 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Discovering outdated Actions&lt;/h2&gt;
&lt;p&gt;Some care about updating their dependencies, many neglect the dependencies in their workflows, that&apos;s why I decided to vibe/make this tool.&lt;/p&gt;
&lt;p&gt;It provides a simple way to know what needs to be upgraded in your GitHub actions, when executed, it shows a list of all the third party actions that can be upgraded and to review the changes.&lt;/p&gt;
&lt;p&gt;The vibing took a while to get right, &lt;a href=&quot;https://claude.ai/download&quot;&gt;Claude&lt;/a&gt; did produce a working tool on the first try, but the first solution was too naive, using 2 requests per action to get the releases and tags, which causes it to reach the API limit very soon blocking any new requests.&lt;/p&gt;
&lt;p&gt;I had to refactor the code to use GraphQL, which unfortunately requires using a &lt;code&gt;GH_TOKEN&lt;/code&gt;, but now all information is requested much faster with a single request.&lt;/p&gt;
&lt;p&gt;Some other cleanups were required to make the tool simpler, like removing unnecessary filters, optimize grouping and sorting, and improving the output format.&lt;/p&gt;
&lt;p&gt;Checkout the tool on Gist: &lt;a href=&quot;https://gist.github.com/dobladov/ca10840ca830e687f4ff7c3c414dbad7&quot;&gt;check-actions.js&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;Getting rid of unnecessary actions&lt;/h2&gt;
&lt;p&gt;Even better than having to update your actions is to get rid of them with tools that are already there, I like to use the &lt;a href=&quot;https://cli.github.com/manual/&quot;&gt;gh-cli&lt;/a&gt; to replace some unnecessary actions, this brings many advantages:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It’s always up-to-date with the changes on GitHub&lt;/li&gt;
&lt;li&gt;Standardized API, simplifying many of the common tasks&lt;/li&gt;
&lt;li&gt;Reduces third party libraries (more secure)&lt;/li&gt;
&lt;li&gt;Faster, no need to set the action with &lt;code&gt;uses&lt;/code&gt; (already pre-installed in all public runners)&lt;/li&gt;
&lt;li&gt;Can be executed in the local terminal, easier to test&lt;/li&gt;
&lt;li&gt;Has many of the common functionality that requires using the rest API&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I had to do clean-up in a big monorepo, and here are some of the actions I replaced:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/peter-evans/create-pull-request&quot;&gt;peter-evans/create-pull-request&lt;/a&gt; → &lt;code&gt;gh pr create ….&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/actions-ecosystem/action-add-labels&quot;&gt;actions-ecosystem/action-add-labels&lt;/a&gt; → &lt;code&gt;gh pr edit --add-label &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/actions/github-script&quot;&gt;actions/github-script&lt;/a&gt; (used to create comments) → &lt;code&gt;gh pr comment ...&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/actions-ecosystem/action-add-assignees&quot;&gt;actions-ecosystem/action-add-assignees&lt;/a&gt; → &lt;code&gt;gh pr edit --add-assignee &amp;lt;name&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Some scripts using fetch to merge a PR → &lt;code&gt;gh pr merge --squash&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;After the changes, all the logic was reduced, required less maintenance, and it was unified.&lt;/p&gt;
&lt;p&gt;As a reminder whenever possible &lt;a href=&quot;https://dobla.do/blog/become-your-companys-javascript-janitor/&quot;&gt;become your company janitor&lt;/a&gt;, everyone appreciates the cleanup and improvements.&lt;/p&gt;
</content:encoded><category>github</category><category>actions</category><category>devops</category><category>automation</category><category>workflow</category><category>cli</category><category>maintenance</category><category>javascript</category><category>nodejs</category><category>gist</category><author>Daniel Doblado</author></item><item><title>Wardriving in 2025</title><link>https://dobla.do/blog/wardriving-in-2025/</link><guid isPermaLink="true">https://dobla.do/blog/wardriving-in-2025/</guid><description>A look into the current state of Wi-Fi security and a wonky portable setup.</description><pubDate>Tue, 06 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;It’s 2025 and routers are somehow secure now, are they really? Well if you have a half decent router it’s most likely safe enough, still there are some settings that can give you some peace of mind.&lt;/p&gt;
&lt;p&gt;I wanted to revise my own security and see if most of the old password cracking methods still work or new ones were found. I learned how to crack &lt;a href=&quot;https://en.wikipedia.org/wiki/Wired_Equivalent_Privacy&quot;&gt;WEP&lt;/a&gt; passwords back when I didn’t have internet, how did I learn it without having internet? I was lucky enough that a guy gave me a ton of PDFs about Linux, programming, and hacking zines, which believe it or not I used to read on my &lt;a href=&quot;https://en.wikipedia.org/wiki/PlayStation_Portable&quot;&gt;PSP&lt;/a&gt;, this probably shaped a lot of my life and career decisions.&lt;/p&gt;
&lt;h2&gt;Wired Equivalent Privacy (WEP)&lt;/h2&gt;
&lt;p&gt;Back then &lt;a href=&quot;https://es.wikipedia.org/wiki/Wired_Equivalent_Privacy&quot;&gt;WEP&lt;/a&gt; was quite easy to crack, it only requires you to listen to the AP when clients are connected, and capture enough traffic to infer the password, this was a long time ago, and &lt;strong&gt;you’ll basically don’t see any routers now with a WEP password anymore&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;Wi-Fi Protected Access (WPA)&lt;/h2&gt;
&lt;p&gt;Then we moved to &lt;a href=&quot;https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access&quot;&gt;WPA&lt;/a&gt; and here is where we are now, you might ask yourself, can you crack a WPA password like WEP? The short answer is no, but there are other methods that might work, because here is the important thing, router manufacturers are lazy, and will do the bare minimum until the problems hits them.&lt;/p&gt;
&lt;p&gt;The first problem with WPA is that an attacker can capture the handshake, this can be easily done with &lt;a href=&quot;https://www.aircrack-ng.org/doku.php?id=airodump-ng&quot;&gt;airodump-ng&lt;/a&gt;, a &lt;a href=&quot;https://en.wikipedia.org/wiki/Dictionary_attack&quot;&gt;dictionary attack&lt;/a&gt; can be used against the handshake to guess the correct password, there are many tools to speed this process like &lt;a href=&quot;https://hashcat.net/hashcat/&quot;&gt;hashcat&lt;/a&gt; which uses the GPU, being realistic if the password is secure enough, you won’t ever get it this way, but as I mentioned manufacturers are lazy and sometimes they generate these passwords in a deterministic way.&lt;/p&gt;
&lt;p&gt;For example the AP mac address might indicate the manufacturer and version of the router, and if you are lucky someone else might have generated a dictionary with all the default values, or it can be even simpler, I remember finding the mac of my router was similar to another I wanted to check, and I knew my default password was 8 numbers, which made it trivial to generate a dictionary.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;As a general rule handshake cracking is not a viable path anymore&lt;/strong&gt;, you need to be lucky with a simple password, and manufacturers, at least the half decent ones got notice and started to create safer default passwords.&lt;/p&gt;
&lt;p&gt;If we can only set WEP and WPA passwords, what’s left? &lt;a href=&quot;https://en.wikipedia.org/wiki/Wi-Fi_Protected_Setup&quot;&gt;WPS&lt;/a&gt;, probably one of the dumbest implementations ever made, at least initially.&lt;/p&gt;
&lt;h2&gt;Wi‑Fi Protected Setup (WPS)&lt;/h2&gt;
&lt;p&gt;I believe not used by any normal consumer ever, initially was a way to use an 8 digits PIN instead of the password, there are many issues with it mostly that routers enable it by default, some won’t allow you to change the PIN meaning that if it’s compromised an attacker will always be able to get your password no matter if you change them, but there are even more problems.&lt;/p&gt;
&lt;p&gt;8 digits is not such a huge number of options to try, but due to a bad implementation decision, it’s possible to guess and validate the first 4 digits independently of the other last 4 digits, meaning that an attacker can check all possible options really quickly, although manufacturers finally realized this and added rate limits eventually.&lt;/p&gt;
&lt;p&gt;Not only the guessing was easy at that point, but with the &lt;a href=&quot;https://en.wikipedia.org/wiki/Wi-Fi_Protected_Setup#Offline_brute-force_attack&quot;&gt;Pixie Dust attack&lt;/a&gt; it’s even easier and faster, again because manufacturers are lazy, and they lack randomization.&lt;/p&gt;
&lt;p&gt;Some manufacturers implemented WPS so bad, that sending a null pin returned the real pin.&lt;/p&gt;
&lt;p&gt;And this is the attack that even today I can find some exploitable routers, to give some real perspective, at my current location, within my WIFI reach I can see that only me and another neighbor use WPA 3, the rest have mostly the default security of their router and there are 2 routers vulnerable to WPS attacks so obsolete that they use WPS 1.0.&lt;/p&gt;
&lt;h2&gt;Wardriving with a phone*&lt;/h2&gt;
&lt;p&gt;From here I got curious, and I asked myself if it was possible to do this WPS attack directly from my phone, and in theory it is, in practice Android has so many fences that I kinda gave up, the main issue is that for capturing packages your phone needs to be rooted, which is not easy depending on the model, I don’t want to root my current phone and my old phone sadly died while installing &lt;a href=&quot;https://grapheneos.org/&quot;&gt;GrapheneOS&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The compromise is to use a phone connected thorough SSH to a &lt;a href=&quot;https://www.raspberrypi.com/products/raspberry-pi-3-model-b/&quot;&gt;Raspberry Pi&lt;/a&gt; powered by a power-bank, with it, I have a mobile setup capable of doing anything I need.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;We need 2 Wi-Fi cards on the Raspberry Pi, one to connect to the phone and a second one in &lt;a href=&quot;https://en.wikipedia.org/wiki/Promiscuous_mode&quot;&gt;monitor mode&lt;/a&gt; to do the actual packet capturing.&lt;/p&gt;
&lt;p&gt;At this point I got a bunch of complication because I don’t have a proper setup, my main RPI had the only good micro SD I had left a home, I was planning to simply flash &lt;a href=&quot;https://www.kali.org/&quot;&gt;Kali Linux&lt;/a&gt; to it which includes all the necessary tools, but it won’t fit on the &lt;em&gt;2gb&lt;/em&gt; card I had left.&lt;/p&gt;
&lt;p&gt;I opted to use &lt;a href=&quot;https://www.alpinelinux.org/&quot;&gt;Alpine Linux&lt;/a&gt;, a much more slim distribution, that fits on the SD card, with the advantage that boots way faster.&lt;/p&gt;
&lt;p&gt;I didn&apos;t have any USB keyboard a simple way to overcome this is to pre-configure the WIFI during flashing, there are different ways, like creating a file, I recommend using &lt;a href=&quot;https://www.raspberrypi.com/software/&quot;&gt;Raspberry Pi Imager&lt;/a&gt; which has a nice UI to add settings and also has Alpine as an option.&lt;/p&gt;
&lt;p&gt;One flashed, we can create an access-point on the phone, that the Raspberry Pi will connect after booting.&lt;/p&gt;
&lt;p&gt;But how do we connect to the RPI? We don’t know the IP assigned to the device, and Android does not show the IP for the AP or the clients connected, using &lt;a href=&quot;https://f-droid.org/en/packages/com.termux/&quot;&gt;Termux&lt;/a&gt;, we can check the AP address with &lt;code&gt;ifconfig&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;Now we can install &lt;a href=&quot;https://nmap.org/&quot;&gt;nmap&lt;/a&gt; to discover all the clients connected.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pkg install nmap
nmap 10.43.163.0-255
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;With the newly discover device, we can finally connect with SSH&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ssh root@10.43.163.249
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Alpine can be configured and permanently installed with &lt;code&gt;setup-alpine&lt;/code&gt;, and some of the necessary tools added with the package manager, &lt;a href=&quot;https://pkgs.alpinelinux.org/package/edge/testing/x86/pixiewps&quot;&gt;pixiewps&lt;/a&gt; and &lt;a href=&quot;https://pkgs.alpinelinux.org/package/edge/community/x86_64/aircrack-ng&quot;&gt;aircrack-ng&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&amp;lt;!-- [Package installation on Alpine, (pixiwps, airdump)] --&amp;gt;&lt;/p&gt;
&lt;p&gt;To make it easy to list and attack the available &lt;a href=&quot;https://en.wikipedia.org/wiki/Service_set_(802.11_network)&quot;&gt;SSID&lt;/a&gt;s, &lt;a href=&quot;https://github.com/derv82/wifite2&quot;&gt;wifite&lt;/a&gt; can be downloaded.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/derv82/wifite2.git
cd wifite

sudo airmon-ng start wlan1 # enables monitor mode on the interface 
sudo airodump-ng -wps -i wlan1 # to find wps 1.0 targets
sudo ./wifite --wps --wps-only # to attack those access points
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;And after a very short try we get the password, this was a very old router that I got for free, as it was configured.&lt;/p&gt;
&lt;p&gt;&lt;/p&gt;
&lt;p&gt;I’m quite pleased with this setup, it’s discrete (on a backpack) and portable, full Linux and boots fast.&lt;/p&gt;
&lt;p&gt;As a takeaway, change the default password for a secure one, update your router whenever possible and &lt;strong&gt;always disable WPS&lt;/strong&gt;.&lt;/p&gt;
&lt;h2&gt;More information&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Wi-Fi_Protected_Access#Security_issues&quot;&gt;Wi-Fi Security issues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Wardriving&quot;&gt;Wardriving&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded><category>wardriving</category><category>WiFi</category><category>security</category><category>WPS</category><category>WPA</category><category>WEP</category><category>raspberry pi</category><category>kali</category><category>alpine</category><author>Daniel Doblado</author></item><item><title>Why there are no posts this year</title><link>https://dobla.do/blog/why-there-are-no-posts-this-year/</link><guid isPermaLink="true">https://dobla.do/blog/why-there-are-no-posts-this-year/</guid><description>A reflection on the year and why I decided to not post during 2024</description><pubDate>Sat, 28 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;Blogging about blogging&lt;/h2&gt;
&lt;p&gt;I realized most of the drafts I wrote in 2024 were about blogging, while some meta might be interesting including this post, I don&apos;t want to be the kind of person that only writes about blogging, the purpose of blogging should be for sharing knowledge and experiences, not for explaining how to blog, probably Aristotle would have wrote this in his blog, but I&apos;m not Aristotle.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“The Purpose of &lt;s&gt;Knowledge&lt;/s&gt;Blogging is Action, not &lt;s&gt;Knowledge&lt;/s&gt;Blogging” Aristotle&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This led me to delete those drafts, and I don&apos;t have any regrets, in fact nothing of value was lost.&lt;/p&gt;
&lt;h2&gt;Negativity&lt;/h2&gt;
&lt;p&gt;Some of the other drafts talked about the &lt;a href=&quot;https://en.wikipedia.org/wiki/Dead_Internet_theory&quot;&gt;dead internet&lt;/a&gt;, and how I&apos;m less and less interested in the internet, and this is true, during 2024 I both gave another try to social networks and gave up, I deleted my Twitter account due to the amount of negativity there, rarely finding anything new and interesting, the only thing I miss is the creativity of gamedev and programmers, but more and more of the content was simply commercial, rarely there&apos;s people who simply are proud of their work and want to share it, instead everyone was selling something including me even if I made no money out of it.&lt;/p&gt;
&lt;p&gt;Then I tried Mastodon and BlueSky, in Mastodon, the content felt completely unappealing to me, maybe because I chose &lt;a href=&quot;https://mastodon.social/explore&quot;&gt;mastodon.social&lt;/a&gt; for my instance, politics, cat pics and rights are the more common topics and I like that others have a place to discuss them but I&apos;m not interested, definitely a more tech-focused instance would have been better for me, but fragmentation and content made me give up on it.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bsky.app/&quot;&gt;BlueSky&lt;/a&gt; felt somehow better, I liked the idea behind their protocol, I think they do moderation in a good way, and it feels like a good alternative to Twitter, but again I&apos;m not sure if I chose the wrong people to follow, but most of the content was meta about the protocol, cats, and politics.&lt;/p&gt;
&lt;p&gt;I even invested some time to understand how their &lt;a href=&quot;https://atproto.com/&quot;&gt;AT protocol&lt;/a&gt; works, I love how they use domains as handles and other ideas of owning your data, but given that they still need to implement important parts of the protocol like scopes developing something on it didn&apos;t feel right at the moment.&lt;/p&gt;
&lt;p&gt;Which led me to think &quot;What am I doing with my time&quot;? I consume all this content that I don&apos;t even enjoy, and then deleted the accounts.&lt;/p&gt;
&lt;p&gt;Ironically &lt;a href=&quot;https://www.instagram.com/&quot;&gt;Instagram&lt;/a&gt; is the only social network of any value for myself now, only following people I know and avoiding reels and other never-ending content, I simply have it on my phone (web app) with a timer to limit how much I use it; this keeps me connected to my friends and family which is real value, I do hate that it is owned by Meta, but both posting and commenting to people I care increases my connection to them.&lt;/p&gt;
&lt;p&gt;In general my only social interactions beside the mentioned are some occasional comments on &lt;a href=&quot;https://news.ycombinator.com/&quot;&gt;Hacker News&lt;/a&gt; and searches on Reddit but they are so sporadic that they barely count.&lt;/p&gt;
&lt;h2&gt;Focusing on what matters&lt;/h2&gt;
&lt;p&gt;Given that I don&apos;t like the web so much, the best thing I could do in 2024 was to get away from it and focus on other things, I started to take Calisthenics more seriously which had the best outcome possible, this year to my surprise, I can do some Calisthenics tricks, like handstands, muscle-up, L-sit, and my physique is great, the amount of benefits to my body have been amazing and training has become a highlight of the day where I feel great instead of being a hurdle, in fact I&apos;m training so much that I have to take periods of healing, which is a good problem to have and I&apos;m reducing with more knowledge about the topic.&lt;/p&gt;
&lt;p&gt;Because of the content I found on the Internet is not interesting I moved more to reading books, and more importantly, now I allowed myself to abandon books that I don&apos;t find interesting which was killing my will to read, this boosted my other hobbies with amazing books like &lt;a href=&quot;https://stevenlow.org/overcoming-gravity/&quot;&gt;Overcoming Gravity&lt;/a&gt;, never have I read a more well-written book about sports, a topic I never thought it could be interested in reading, I&apos;m reading more of what amuses me without feeling guilty about learning something new, focusing on Sci-Fi instead of non-fiction.&lt;/p&gt;
&lt;p&gt;For 2025 I hope to write more about my hobbies and less about the web, focus on what&apos;s positive and what I enjoy, and hopefully you enjoy it too.&lt;/p&gt;
</content:encoded><category>meta</category><category>blogging</category><category>social networks</category><author>Daniel Doblado</author></item></channel></rss>