RSSAmplifier

Blog

Matias Kinnunen – Blog

A collection of my wildest adventures with computers. Mostly about web development.

mtsknn.fiRSS feed ↗161 posts

Latest posts

Better way to indent ordered lists in Markdown

1. foo is annoying because the list item's text is indented with three spaces. Solution: add another space after the period. Before: 1. Using ** 3 ** spaces of indentation. 2. ... 3. Code blocks are ** especially annoying ** : ```js // ** 3 ** spaces of indentation, yuck function foo() { // ** 5 ** spaces of indentation, yuck alert('lolz') } ``` 4. ... 5. And when there are lots of list items: 6.…

Previewing HTML files directly on GitHub with a bookmarklet

There's no built-in way to preview HTML files on GitHub, but you can use this nifty bookmarklet. (GitHub's project managers HATE it!) Create a bookmark with this "URL": javascript : ( ( ) => { const selector = '#read-only-cursor-text-area' ; const html = document . querySelector ( selector ) ?. value ; if ( ! html ) { return alert ( ` Error: element ${ selector } not found ` ) ; } document . open…

Beware implicit `Content-Type: "application/json"`

If you are sending a FormData body with fetch , things might not work correctly if you are (accidentally) setting the Content-Type request header to "application/json" . Problem I was sending a FormData object to a 3rd party API: const formData = new FormData ( ) formData . append ( 'foo' , someValue ) const response = await request ( 'https://example.com/api/' , { body : formData , method :…

Safari doesn't support `position: relative` on `<tr>` elements

This is a problem if you want whole table rows to be clickable. Luckily there's a (hacky) workaround. Problem I want whole table rows ( <tr> s) to be clickable. I don't want to: Wrap the <tr> s in <a> elements either because that wouldn't be semantic. (Not sure if this would even work; haven't tested.) Use onClick on the <tr> elements because that wouldn't be accessible. Use an <a> element in each…

"Slap" means "sounds like a plan"

I came up with this acronym myself, and I'm proud of it! I use it often when chatting and conversing with my fam & friends. Not with my parents though, they wouldn't understand. Sometimes I go so far as to say "slapety slap."

How to use Azure AD authentication in DBeaver

Alternative title: How to fix the "Failed to load MSAL4J Java library for performing ActiveDirectoryPassword authentication" error. I'm using the free Community Edition of DBeaver , version 23.3.5. In the "Connection Settings" pop-up window: Select e.g. "Active Directory – MFA" as the authentication method and type your username Click Driver Settings Choose the "Libraries" tab Click Add Artifact…

`mailto:` links with `target="_blank"` are surprisingly tricky

Only because Firefox ignores target="_blank" in mailto: links. Let's say you have an email link that opens in a new tab: < a href = " mailto:hello@example.com " target = " _blank " > Email me </ a > ✅ Clicking the link in Edge works correctly (opens an email client, e.g. Outlook). ✅ Clicking the link in Safari works correctly (opens an email client). ✅ If Firefox is configured to open an email…

Fix all requests timeouting in Burp Suite's Repeater

Make sure you are not missing two empty lines after the request headers. There's a UX lesson here. Problem I was using Burp Suite (Community Edition, version 2023.12.1.5) for the first time. Sending requests in the Repeater tab resulted only in timeouts: HTTP/1.1 408 Request Time-out Content-length : 110 Cache-Control : no-cache Connection : close Content-Type : text/html < html > < body > < h1 >…

Two great video games I played through in 2023

I managed to find time to play Desperados III and Lost in Play (and Before Your Eyes too a bit). Desperados III (Screenshot taken from the game's official website.) Desperados III is a story-driven, hardcore tactical stealth game, set in a ruthless Wild West scenario. ✅ Great visuals ✅ Nice gameplay (tactical stealth game) ✅ Plenty of content – Steam shows that I spent 27.5 hours playing the game…

Fiction books I read and liked in 2023

I started a new hobby in 2023: reading fiction books! I started with fantasy: Harry Potter, Mistborn, and The Stormlight Archive. Harry Potter After several years of not reading fiction books (almost) at all, I started my new reading hobby with this classic heptalogy (= 7-book series) by J. K. Rowling. I have written about Harry Potter before: I read all 7 seven Harry Potter books in 33 days .…

Tweaking website styles with uBlock Origin filters

Use the :style() operator in a uBlock Origin filter to apply CSS styles to the targeted elements. Here's how I use the :style() operator : Underline links on BazQux (RSS reader) . Hide points on hckr news . Highlight visited comment links on hckr news. Here are the filters (under the "My filters" tab in the uBlock Origin dashboard): ! underline links bazqux.com##.post .mtext a : style (…

"Else equals" in Handlebars (in SendGrid's Dynamic Templates)

There's a way to do an "else equals" check in Handlebars (in SendGrid's Dynamic Templates), though it's undocumented and a bit tricky. Context SendGrid's Dynamic Templates use Handlebars. Handlebars is a templating language. Problem I wanted to do something like this: {{! Pseudo code, doesn't work }} {{ #if locale = = 'en' }} < h1 > New article: {{ articleNameEn }} </ h1 > {{ else if locale = =…

HTTP headers: Accept vs Content-Type

I sometimes forget what these two headers do in requests vs responses, so here's a refresher for me. Accept in request headers What type of response the client expects from the server. Accept in response headers N/A Content-Type in request headers The type of the request data (e.g. in a POST or PUT request). Content-Type in response headers The type of the response data. The Content-Type header is…

Cmd-tabing to Firefox on Mac gets progressively slower

The longer Firefox is kept open, the longer it takes for Firefox to receive focus after Cmd-tabing to it. At some point it can take even about a second, which really kills the flow. I initially thought that maybe the problem is in the AltTab app , which is an essential Mac app by they way. But nope, the problem exists even when not using AltTab. Then I thought that maybe it's some kind of memory…

JavaScript's default exports are a footgun

It's too easy to accidentally import a default export with a misleading name. Example: What I have wanted: import useSWR from 'swr' // or import useSWRImmutable from 'swr/immutable' What I have accidentally done (more than once): import useSWRImmutable from 'swr' // or import useSWR from 'swr/immutable' Then I have wasted time being confused by SWR not working like I expect it to. Named exports…

Moving zeros to the end of an array in JavaScript

Let's learn about anti-symmetry by solving this interview puzzle: "Move all zeros of an array of integers to the end while maintaining the relative order of all non-zeros." Example: moveZeros ( [ 1 , 3 , 0 , 111 , 57 , 0 , 5 , 0 , 9 ] ) //=> [1, 3, 111, 57, 5, 9, 0, 0, 0] Ok, ez because there's a great visual mnemonic for an array sorter's return values , so we can simply™ sort() the values: const…

Logging hours to Jira's "Work log" in bulk

There's apparently no nice way to bulk-log hours in Jira. Use this One Weird Trick to do it anyway – Jira consultants HATE it! Problem Jira's UI for logging hours ("Work log" / "Time tracking") is clunky: It requires lots of mouse clicks and/or key presses. It gets very repetitive after a few times. The UI keeps changing from time to time, breaking my workflow. Officially hours can apparently be…

Pin exact dependency versions

In a perfect world, minor- and patch-level version bumps don't have breaking changes. In the real world, things are complicated and shit happens. Example: I recently witnessed yarn install starting to fail mysteriously after Node.js was automatically upgraded from v18.16 to v18.18 . I had thought that pinning the major-level version number of Node.js would have prevented this kind of sudden…

`error.cause` needs to be logged manually to App Insights

If you are logging errors to Azure App Insights, and an Error object has a cause property (which too could be an Error object), the cause Error's message and stack properties are not shown in Azure App Insights. But you can log them manually. Problem Use App Insights SDK for Node.js . Use the trackException method to log an error with a cause property : import * as appInsights from…

`\d` in a regex isn't always equal to `[0-9]`

In JavaScript, \d and [0-9] are equal and match only Arabic numerals (the numbers 0–9). But in some other languages, \d matches also non-Arabic numerals. There are 680 Unicode characters in the "Number, Decimal Digit" category . For example: Name Characters Digits ( Arabic numerals ) 0123456789 Arabic-Indic digits ٠١٢٣٤٥٦٧٨٩ Extended Arabic-Indic digits ۰۱۲۳۴۵۶۷۸۹ NKo digits ߀߁߂߃߄߅߆߇߈߉ Devanagari…

`response.json()` can throw an error in JavaScript

If the response body is not valid JSON, response.json() will throw, just like JSON.parse() would throw if the same data was passed to it. Example: const response = new Response ( '{ "foo": true }' ) await response . json ( ) //=> { foo: true } // vs const response = new Response ( 'OK' ) await response . json ( ) //=> SyntaxError: JSON.parse: unexpected character at line 1 column 1 of the JSON…

`error.stack` includes the error message in some JS environments

In V8-based JS environments (e.g. Chrome, Deno and Node.js) and in Bun, error.stack = error class name + error.message + stack frames. Context I was logging an Error object's message and stack properties to Azure App Insights (because error.cause needs to be logged manually to App Insights ), and was baffled because error.stack seemed to contain not only the stack trace, but also the error…

`yarn install` failing mysteriously on Node.js v18.18

TL;DR: Upgrading Yarn from v1 to v4 fixed it for me. Problem Recently some CI pipelines [1] started to fail at yarn install (v1.22.19) with cryptic error messages like these (the error message kept changing): An unexpected error occurred: "/path/to/project/node_modules/core-js-pure: Cannot read properties of undefined (reading 'getOption')". An unexpected error occurred:…

Visual mnemonic for an array sorter's return values

When sorting an array with a custom comparator function, the function should return <0 , 0 or >0 . But what do the values do? Use this weird visual mnemonic – sorting consultants HATE it! First, recall that an array sorter compares two values, a and b : array . sort ( ( a , b ) => { // ... } ) Second, because the sorter function should return a number, imagine an x-axis with the numbers -1 , 0 and…

Strict object validation is handy for data exploration

Alternative title: I finally found a good use case for z.object().strict() in Zod. Zod's z.object() ignores unknown keys (= object keys not specified in a Zod schema) by default. Unknown keys can be disallowed with Zod's .strict() method . When would .strict() be useful? From a GitHub comment by Kamil Trebunia : For consuming APIs I would almost always pick z.object , but for data exploration as I…

Adding type-safety to Node-Cache

Node-Cache (a JS library) has generic get and set methods, but it's easy to accidentally misuse them. I made a type-safer wrapper around the library. Problem / Context Node-Cache is a caching module for Node.js : A simple caching module that has set , get and delete methods and works a little bit like memcached. Keys can have a timeout ( ttl ) after which they expire and are deleted from the…

YouTube's "Show transcript" tool is great for taking video notes

Incidentally, it's also great for searching for certain words in a video. Click the three dots under a video and then "Show transcript." Example screenshot from Agile Product Ownership in a Nutshell (15:51) by Henrik Kniberg : This tool saves time and effort: I don't need to be a human speech-to-text machine; I can instead focus on fixing typos and errors in the transcript and splitting the text…

Bookmarklet to download still images from YouTube videos

Quick and dirty code to grab a single frame of a YouTube video. Might work with other video players too. The bookmarklet Drag and drop this to the browser's bookmarks bar: { const $video = document.querySelector('video'); const { height, width } = $video.getBoundingClientRect(); const $canvas = document.createElement('canvas'); const ctx = $canvas.getContext('2d'); ctx.canvas.height = height;…

Legacy code is code written by other people

Legacy code means unfamiliar code. Legacy code has much more to do with unfamiliarity than the quality of the code itself. From the Prioritizing Technical Debt as If Time & Money Matters talk by Adam Tornhill (28:42) , from 19:53–22:14: Legacy code is related to technical debt, but the two are not the same. So what is legacy code? There are many definitions out there. The definition I prefer is…

`Date` objects are nicer to work with than date strings

When parsing data, date strings should likely be converted to Date objects. But beware mutations. Intended audience / Applicable languages This post is for you if you are dealing with date strings. This post uses JavaScript and TypeScript, but the main ideas are likely quite language-agnostic. Context Let's say some data from e.g. an API or database contains a date string in ISO format: const data…

I read all 7 seven Harry Potter books in 33 days

I don't know if I have ever read so much in so short time. I just liked the books so much. Pages per day Here's a table of my reading progress for my own amusement (books published by Bloomsbury, 2014 editions): Book Pages Days Average pages per day 1st 332 5 66 2nd 360 4 90 3rd 462 4 116 4th 617 5 123 5th 800 6 133 6th 542 6 90 7th 620 5 124 Total 3,733 33 113 There were 33 total days instead of…

Parsing data is nicer than only validating it

Parsing encompasses validation, plus it can increase type-safety, plus it can transform the data to a better format. Parse, don't just validate. Validation Validating data means checking if the data matches a certain shape or certain rules. For example, when using Joi, a JavaScript validation library , you write a schema and check if some data matches the schema: import Joi from 'joi' const…

Auto-converting EPUB files to AZW3 instead of MOBI in Calibre

MOBI is an old e-book format. AZW3 is a newer and apparently better format; for instance, it supports embedded fonts. Here's how to configure Calibre to prefer the better format. Global settings In Calibre, go to Preferences → Behavior Change "Preferred output format" from MOBI to AZW3 Device settings might override global settings, so check device settings too Plug in your device (via USB) In…

De-ACSM'ing and de-DRM'ing e-books for fun (but not profit)

A reminder for myself how to make library e-books pleasant to use with the help of Calibre and two Calibre plugins. Problem My local library provides many e-books for free, which is great, but actually getting those e-books to my Kindle Paperwhite is a hassle: Download an EPUB file (so the button says on the library site) – whoops, it's actually an ACSM file. Download Adobe Digital Editions (ADE)…

Sending (duplicate) emails to email aliases can be tricky

If you send a single email to a bunch of email addresses, and some of the addresses are aliases, i.e. they point to the same email inbox, chances are high that only one copy of the email is delivered to that inbox. Example problem I had five test users and one email alias per test user; e.g.: Test user 1: foo@example.com Test user 2: foo+test-user-2@example.com Test user 3:…

Harry Potter, the Mirror of Erised, and mindless internet surfing

Mindlessly surfing the internet is like staring at the Mirror of Erised from the Harry Potter books. (Minor Harry Potter spoilers ahead.) Mindless surfing vs the Mirror of Erised My bro shared me a Reddit post on /r/nosurf the other day. Here's the full post (with a few typos fixed): Mindless surfing is like the Mirror of Erised in Harry Potter For those of you who haven't read Harry Potter, the…

Video notes: Agile Product Ownership in a Nutshell

This 16-minute video by Henrik Kniberg is a great overview of the agile software development process, but also contains lots of smaller great insights. The video Watch Agile Product Ownership in a Nutshell (15:51) on YouTube. I found the video via Critter's blog post Pull methodology, flow time, and WIP limits: visualized . Top 3 moments The 16-minute video is packed with great insights. I guess…

Book notes: Atomic Habits

Tiny good things accumulate into great things. Tiny bad things accumulate into horrible things. This book tells you how to do more of those good things and less of those bad things. About the book People do things out of habit all the time. Atomic Habits tells you how to make good habits easy and effortless, and how to make bad habits difficult and unattractive. Read more about Atomic Habits on…

From a super ultrawide monitor to a regular 16:9 monitor

After 2.5 years of using a 120 cm wide super ultrawide computer monitor with a 32:9 aspect ratio, I'm back to a boring 64 cm wide 16:9 monitor. The smaller monitor now feels better in every regard. Super ultrawide I bought Samsung CRG9 in July 2020 for 950 € (sale price). Some specs: 49", 120 cm wide 5120×1440 pixels (Dual QHD) 120 Hz Curved VA panel Nice: it's wide I wanted to try a super…

I wrote a weekly log for half a year and it was stressful

"Weekly log" basically meaning weekly blog posts with a specific structure. Today I removed the whole Weekly log. Why I started a weekly log I started the weekly log to reduce stress: by publishing short weekly log entries, I wouldn't need to stress so much about my blog. Writing blog posts takes time, and I wanted to publish something regularly. And I did publish regularly: I published the first…

Switching to Mac after 20+ years of using Windows

I have used Windows PCs since I was a kid. A year ago (Jan 2022) I got my first MacBook. While Mac isn't perfect, it's so much better that I'm not going back to Windows. Why I switched to Mac Three things: I had long had the impression that many developers use and like Macs, so I was intrigued to try it myself. Macs are relatively expensive (same amount of money gets you more horsepower if you buy…

Automatically retrying a failing function in JS/TS

A little helper function to retry a function automatically a limited number of times (or until success) and with a certain delay between retries. Useful e.g. when working with a flaky API. Starting point After a bit of googling, I found Jaromanda X's answer on Stack Overflow to be a good starting point: Using a couple of helper functions I've used a lot, this becomes very easy The "helpers"…

SWR (v1): show previous/stale data on revalidation errors

If I'm already displaying data that I have successfully fetched with SWR, and then another fetch call fails (when revalidating the data), I don't want to replace the data view with an error view. Here's how to avoid that. Problem Basic usage of SWR (v1; I haven't upgraded to v2 yet): export const useItems = ( ) => { const { data , error } = useSWR ( '/api/items/' , ( url ) => fetch ( url ) . then…

Wild ideas to avoid burnout

Care less, take a sabbatical, get a second job, get monster ear protection, hide behind your manager. Someone recently asked on Hacker News if anyone has managed to find enjoyment in their work after burnout . Excerpts from comments that I liked (not all are that wild, but anyway): Care less (comment by highwaylights) This advice is going to be hated by a lot of people but... care less. You're in…

Four ways to use a fallback ref with `React.forwardRef`

A component wrapped in React.forwardRef normally expects to receive a ref from its parent component. What if you want the component to have a ref even if it doesn't receive one from its parent? The problem React.forwardRef allows passing a ref to a child component: function Parent ( ) { const ref = useRef ( null ) useEffect ( ( ) => { console . log ( ref . current ) } , [ ] ) return < Child ref =…

Software for syncing internal and external hard drives

I'm looking for a software that does 2-way syncing between my MacBook's hard drive and an external hard drive. Syncthing vs rsync vs Unison vs FreeFileSync vs Resilio Sync... oh my! My use case Problem I have important files (photos and such) scattered on three computers: A MacBook that I started using 9 months ago. A Lenovo laptop (Windows) that I stopped using 9 months ago because I switched to…

Stashing only unstaged changes in Git

git stash -k doesn't work as expected. Use a temporary commit instead. Non-solution git stash -k # -k = --keep-index What this does: Keeps the staged changes: nice. Stashes unstaged and staged changes: not nice since the stash entry will also contain the staged changes. This can be problematic as (in this blog post) we want to stash only unstaged changes. Solution Using my Git aliases : git c -m…

JS solutions to the "coin combinations" interview puzzle

"Given an array of coins and a target amount, return all combinations of coins that add up to the amount, or an empty array if no such combinations exist." Example inputs and outputs let coins = [ 1 , 2 , 1 , 5 , 5 , 10 ] let amount = 12 getCoinCombinations ( coins , amount ) //=> [[1, 1, 5, 5], [1, 1, 10], [2, 5, 5], [2, 10]] coins = [ 2 , 5 , 5 , 5 ] amount = 13 getCoinCombinations ( coins ,…

Local mutations are all right

For example, mutating a reducer function's accumulator array can be better than religiously avoiding mutations and returning only new arrays from the reducer. Example of a mutating code I recently published a blog post about how to find all possible combinations of array items in JS . Here's the code: const getCombinations = ( items ) => items . reduce ( ( combos , item ) => { const newCombos =…

Generating permutations of large arrays is complex

That's because it would take a very long time, and at some point you would have so many permutations that they wouldn't fit in an array. Starting point By "array permutations" I'm referring to my blog post from yesterday: Generating array permutations with all lengths in JS . Here's the code: const getPermutations = ( items ) => items . reduce ( ( permutations , item ) => { const newPermutations =…