Chris Hallberg · Blog · TIL · Reviews · RSS

Today I learned

JS: Defer and Queue Order (2026-08-30)

Macro Task -> All Micro Tasks -> Paint -> Next Macro Task

  1. Main Script (Macro)
    1. queueMicrotask (micro)
    2. requestAnimationFrame (micro)
  2. setInterval (Macro)
    1. process.nextTick (Node high-priority micro)
    2. MutationObserver (micro)
  3. Promise.resolver().then() (Macro)
  4. setTimeout (Macro)
  5. Promise.then().then() (Macro added when first then finished)

scheduler.postTask(fn)

Git: Conventional Commits (updated 2026-08-19)

From Conventional Commits and Angular's convention

Prefix commit messages with meaningful tags. Start with a type, add a (scope), and end with ! if it's a breaking change. i.e., fix(css): saved items not underlined or feat(config)!: switch to YAML.

CSS: grid-based media object (2026-08-10)

A classic case: text in a box with an image to the left. I was having problems with the height of the image until I came across this article by Muhammad Yafi Alhakim. Thank you!

TL;DR: make the image absolute positioned.

.media {
    display: grid;
    grid-template-columns: auto 1fr;
}
.media__pic {
    position: relative;
}
.media__pic__img {
    object-fit: cover;
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
}
.media__text {
    padding: 1em;
}

CSS: prevent grid blowout (2026-08-10)

The default minimum for 1fr columns is auto, not 0.

.equal-columns {
    grid-template-columns: repeat(3, minmax(0, 1fr));
}

PostgreSQL: resetting SERIAL ids (2026-08-06)

SELECT setval(
    pg_get_serial_sequence('{TABLE}', '{COLUMN}'),
    COALESCE((SELECT MAX({COLUMN}) FROM {TABLE}), 0) + 1,
    false
);

monospace, m (2026-06-30)

Browsers size monospace text too small if monospace is the last family in a stack. I've started using font-family: monospace, m;.

For example: this is monospace and this is monospace, m.

Text on dark grading (2026-06-24)

It is recommended to reduce grade (GRAD) on variable fonts to make them easier to read on darker backgrounds.

N-Gram Scoring (2026-06-10)

The simplest way to score n-gram matches is the size of the set intersection over the size of the set union of terms.

const score = term_ngrams.intersection(data_ngrams).size / term_ngrams.union(data_ngrams).size;
score = len(term_ngrams & data_ngrams) / len(term_ngrams | data_ngrams)

Comparing dice pools (2026-06-09)

The odds of 5d8 rolling higher than 3d6 are the same as 5d6+3d6 rolling higher than 21 (7x3).

# complement
Z = ((sides + 1) * dice)
Y = Z - Yβ€²

# derive
P(X > Y) = P(X > 21 βˆ’ Yβ€²) = P(X + Yβ€² > Z)
P(5d6 > 3d6) = P(8d6 > 21)

Since you need to sum up all the combinations, you can also flip the comparison to do less expensive math.

# 8 to 21 is less than 21 to 48
P(8d6 > 21) = P(8d6 <= 21)

We have await at home (2026-06-08)

// Do we need more items?
const promise =
    channelEl.hiddenItems < pageSize && channelEl.ajaxAvailable
        ? requestMoreItems(loadMoreBtn, channelEl)
        : Promise.resolve(); // no load needed

// update button after load
promise.then(() => {
    if (channelEl.hiddenItems === 0 && !channelEl.ajaxAvailable) {
        hideLoadMoreBtn(loadMoreBtn);
    } else {
        enableLoadMoreBtn(loadMoreBtn);
    }
});

Email Emissions (2026-06-02)

Each unread email hanging out in your inbox accounts for at least 0.03g of CO2 emissions.
~ Back Market, Unsubscribe Page

Clock Emojis (2026-04-07)

There is an analog clock emoji for every thirty-minute interval.

πŸ•› πŸ•§ πŸ• πŸ•œ πŸ•‘ πŸ•
πŸ•’ πŸ•ž πŸ•“ πŸ•Ÿ πŸ•” πŸ• 
πŸ•• πŸ•‘ πŸ•– πŸ•’ πŸ•— πŸ•£
πŸ•˜ πŸ•€ πŸ•™ πŸ•₯ πŸ•š πŸ•¦

Discharging a Capacitor (2026-01-05)

Source: https://www.youtube.com/watch?v=PmK4ArtR5pQ

The Soup Formula (2026-01-03)

  1. Aromatics in Fat. 2 cups. Mirepoix of 2 part onion, 1 part celery, 1 part carrot (cut smaller). You can freeze this.
  2. Herbs.
  3. Flavorful Liquid. 2 quarts. Stock is better (gelatin from bones). Chicken stock, wine.
  4. Chunkies. Turkey, beans, tomatoes.

- 30 minutes, taste test -

  1. Acid. After serving! Sumac (spice) is good.
  2. Garnish. Sol in omnibus. You can use cheese for saltiness.

Source: Alton Brown Cooks Food E7 - Soup

Sweating vs SautΓ©ing (2026-01-03)

Source: Alton Brown Cooks Food E7 - Soup

Faster Binary Search (2025-12-16)

In the regular procedure, the algorithm checks whether the middle element is equal to the target in every iteration. Some implementations leave out this check during each iteration. The algorithm would perform this check only when one element is left (when L equals R). This results in a faster comparison loop, as one comparison is eliminated per iteration, while it requires only one more iteration on average.

def binary_search_alternative(arr, target):
    left = 0
    right = len(arr) - 1
    while left < right:
        mid = left + ceil((right - left) / 2)
        if arr[mid] > target:
            right = mid - 1
        else:
            left = mid
    if arr[left] == target: # move target check out of loop
        return left
    return None

Hermann Bottenbruch published the first implementation to leave out this check in 1962. ~ Wikipedia

How To Wrap Christmas Lights (2025-12-06)

  1. Cut two slits in a piece of cardboard.
  2. Wrap starting with the female plug.
  3. Tuck or plug in the male end.

It's the Christmas light video again - 2025 edition by Technology Connections

Add pre-wash soap to your dishwasher (2025-12-06)

There's a tiny slot next to the soap dispenser with holes in the lid. This is for soap during the pre-wash rinse. Just a little sink soap in this slot makes a huge difference!

I was right about dishwasher pods, and now I can prove it by Technology Connections

i3 config (2025-12-10)

set $mod Mod4 # Windows key

Then press $mod+Shift+R to reload config

ssh key pairs (2025-12-09)

  1. Check for id_rsa: ls ~/.ssh (if there, skip to 3.)
  2. Make ssh key ssh-keygen
  3. Copy to server ssh-copy-id {YOU}@server
  4. If on Windows or doing for another account:
    1. copy id_rsa.pub to /home/{USER}/.ssh/authorized_keys
    2. folder permissions: chmod 700 .ssh
    3. file permissions: chmod 600 .ssh/authorized_keys

lazygit is the best (2025-11-27)

https://github.com/jesseduffield/lazygit

Learned from lazygit

The Performance Inequality Gap, 2026 (2025-11-24)

https://infrequently.org/2025/11/performance-inequality-gap-2026/

Budget @ P75 Total JS (15%) Total JS (50%)
3 sec 2.0 MiB 0.3 MiB 1.2 MiB 0.62 MiB
5 sec 3.7 MiB 0.57 MiB 2.3 MiB 1.15 MiB

Updated network test parameters for 2026 are:

  • 9 Mbps downlink
  • 3 mbps uplink
  • 100 millisecond RTT

The goal of these recommendations is to emulate a 75th percentile user experience, meaning a full quarter of devices and networks will perform worse than this baseline.

Per usual, we consider pages built in two styles:

  • JS-light, where only 15% of critical-path bytes are JavaScript
  • JS-heavy, comprised of 50% JavaScript

JS: Null Coalescence Assignment (??=) (2025-11-30)

function memoize(fn) {
    const known = [];
    return (num) => (known[num] ??= fn(num));
}

It's beautiful.

Outlook conversations (2025-07-29)

Outlook can only group by conversation when sorted by Date. Sorting by Category completely breaks the grouping.

git bisect (2025-03-28)

If you know a bug was introduced between two releases, you can find it quickly:

  1. git bisect start
  2. git bisect good {tag/commit}
  3. git bisect bad {tag/commit}
  4. Test: git bisect good or git bisect bad (or git bisect skip)
  5. git stash and git stash pop if you need test code.

A Great Joke (2025-03-27)

In the late '90s, the USA was trying to build a super collider in Texas. They needed to get a foreign partner on board, but no countries came forward... until it was suggested that Russia's dirt-cheap manufacturing could be used to produce components at massive discounts, and those discounts could be viewed as their cash contributions. The same labs once used to develop ballistic missiles would now be used to build detector components.

A Russian physicist at the SSC joked by saying "Why, this is nothing new. For many years we made high-precision components for delivery to the United States".

Source: https://www.youtu.be/3xSUwgg1L4g; Part 2 - Bush.

Zoom (2025-02-13)

You can share multiple windows, which will hide switches and other windows but allow multiple apps!

Excel (2025-01-14)

You can reference the same cell in every sheet with =SUM('*'!A1). It will probably expand to a sheet range: =SUM(Demian:Susan!A1).

cosmopolitan (2024-12-08)

fat binaries, including redbean and greenbean : greenbean might only provide a static page (404 for everything else)

Update: cosmopolitan libc (2025-01-12)

Compile C into a fat binary (run on any platform). https://justine.lol/cosmopolitan/index.html

git rebase branch (2024-08-07)

A ── B ── C ┬─ (<- dev)
            β”‚
            └─ D ─ E ─ (<- branch)
git rebase dev {branch} --onto origin/{base}
A ── B ┬─ C ─ (<- dev)
       β”‚
       └─ D ── E ─ (<- branch)

HTML/CSS/JS Playground (2024-07-29)

MDN Playground

CSS transparent variable colors (2024-07-29)

body {
    color: color-mix(in srgb, var(--color) 50%, transparent);
}

Math: chance statistics (2024-06-27)

Odds:

Chance of Happening X times:

Combinations:

Permutations:

Linux: deleting large folders

Using rsync is 3x faster than rm.

mkdir empty

rsync -aP --delete empty/ target/

HTML: <em> vs <i>

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/em#i_vs._em

SQLITE: LIMIT 0,30

7z update option

https://7-zip.opensource.jp/chm/cmdline/switches/update.htm

7z u {archive} {paths}

Will:

7z excluding folders

7z.exe a ./Desktop/Documents.20230612.7z \
    ./Documents/git/ \
    ./Documents/GitHub/ \
    ./Documents/htdocs/ \
    ./Documents/VILLANOVA/ \
    -xr!node_modules \
    -xr!.git -xr!fonts

Javascript: variable keys in maps

let x = "apple";

return { [x]: 10 }; // { "apple": 10 }

return { ...state, [x]: 10 };

return { ...state, apples: { [fav]: 10 } };

Python: venv

2023-04-28

Using Python's built in virtual environment means that all libraries are installed locally, allowing you to edit the libraries if understanding or functionality escapes you.

python -m venv ./.venv     ## Creates a virtual env in the current folder

source .venv/bin/activate  ## Activates the venv

PHP multi-thread

One

// returns the PID of the child to the parent

// the child will receive a 0

$isChildProcess = pcntl_fork() === 0;

if ($isChildProcess) {
    doAsyncTask();
}

Many

// A pool of threads

foreach ($urls as $index => $page) {
    if (pcntl_fork() === 0) {
        doAsyncTask();
        break; // no grandchildren!
    }
}

Bash Inputs

From Files

$var = (cat file.txt)

Prompts

echo -n "username: "
read -r user

echo -n -s "password: "
read -r pwd

SQL: ordered list of values

2021-04-20

You can quickly get an ordered list of ids or values by joining to a temporary table.

SELECT f.id, dbo.professors.* FROM dbo.professors
    JOIN (VALUES (33),(33),(33),(90),(90)) as f(id)
    ON f.id = dbo.professors.professor_id;
id  professor_id    name
33            33    Sir Isaac Newton
33            33    Sir Isaac Newton
33            33    Sir Isaac Newton
90            90    Carl Sagan
90            90    Carl Sagan

Javascript: forward declaration (2019-08-20)

Obsolete! Functions are now hoisted automatically.

Many linters check for functions to exist before they're referenced. With circular calling, you need to declare a function before it's defined. TIL this is called forward declaration.

let bind = function bindForward() {};

function ajax(url) {
    load(url).then(bind);
}

bind = function bind() {
    $("a").click(ajax);
};

Javascript: Destructuring (2019-08-08)

When destructuring, JS only assigns the deepest nested keys:

const req = { query: { person: { first: "Chris", last: "Hallberg" } } };

const {
    query: {
        person: { first, last },
    },
} = req;

console.log(query); // undefined
console.log(person); // undefined
console.log(first); // "Chris"
console.log(last); // "Hallberg"

You can also assign names that don't match the keys you're grabbing. Just be careful with your brackets.

const req = { query: { data: "apples" } };

// rename query to data
var { query: data } = req;
console.log(data); // { data: "apples" }

// destruct data from query
var {
    query: { data },
} = req;
console.log(data); // apples

Warning: In browsers, name seems to be assigned to "[object Object]" somewhere along the line, so be careful.

Javascript: Destructuring as parameters

2019-09-19

I learned today that you can destructure variables as part of a function declaration or as part of a for-of loop:

function getHeight({ height }) {
    return height;
}

getHeight({ width: 10, height: 200, depth: 7 }); // 200

const points = [
    { x: 3, y: 4 },
    { x: 5, y: 5 },
];

for (let { x, y } of points) {
    console.log(`distSq = ${(x ** 2, y ** 2)}`);
}

// distSq = 25

// distSq = 50

Javascript: Destructuring as default parameters

2019-09-24

It's been a good month for destructuring. Building on the previous revelation, you can set parameter defaults with destructuring.

function showConfig({ tabSize = 2, theme = "Nord" } = {}) {
    console.log(`Theme: ${theme}, Tab Size: ${tabSize}`);
}

showConfig({ tabSize: 4, theme: "Monokai" }); // Theme: Monokai, Tab Size: 4

showConfig({ theme: "Monokai" }); // Theme: Monokai, Tab Size: 2

showConfig(); // Theme: Nord, Tab Size: 2

WebVR

Oculus Quest debugging

A-Frame

2019-09-07

I couldn't find any official way to get debugging info from the Oculus Quest since it only connects over Wi-Fi (if at all) and the SteamVR logs don't pick up anything.

So I dropped a text element into the world and used that as my string log. You can put this element inside the camera if you want it to be visible as a UI element, but I didn't do that.

<a-text id="log" value="Hello!" color="#000" align="center" position="0 2 -5"></a-text>
const logEl = document.getElementById("log");

function log(msg) {
    logEl.setAttribute("value", msg);
}

// For updating messages

function debug() {
    requestAnimationFrame(debug);
    log(blueGun.object3D.matrix.toArray().join(","));
}

requestAnimationFrame(debug);

// For everything else

try {
    // do a back flip
} catch (e) {
    log(e);
}

Move the World, Not the Camera

A-Frame

2019-09-07

I have a few game concepts that involve pulling the player along or changing their view but trying to override the default WebVR camera is a pain.

If you do manage to get control of the camera (either with groups or with rigs) you need to then manually ensure you draw both eye "cameras" anyway. It's just more work that it's worth when you can move the whole world instead!

A hierarchy like this makes this quite easy to do:

- scene
  - camera
  - world
    - hand controllers
    - all physical items

Thank you, Stefan Judis for the idea and encouragement.