Back to Home

Today I Learned

A collection of short notes from my cross-disciplinary studies, shared as I learn in public.

status: Growing

Status Indicator

The status indicator reflects the maturity of the content: • rough — Initial thoughts, unstructured • growing — Being actively developed • evergreen — Stable, well-developed content • withered — No longer being maintained This helps readers understand the completeness of the content.

·
certainty: certain

Confidence Rating

The confidence tag expresses how well-supported the content is, or how likely its overall ideas are right. This uses a scale from "impossible" to "certain", based on the Kesselman List of Estimative Words: 1. "certain" 2. "highly likely" 3. "likely" 4. "possible" 5. "unlikely" 6. "highly unlikely" 7. "remote" 8. "impossible" Even ideas that seem unlikely may be worth exploring if their potential impact is significant enough.

·
importance: 7/10

Importance Rating

The importance rating distinguishes between trivial topics and those which might change your life. Using a scale from 0-10, content is ranked based on its potential impact on: - the reader - the intended audience - the world at large For example, topics about fundamental research or transformative technologies would rank 9-10, while personal reflections or minor experiments might rank 0-1.

April 2026
posted on 04.12.2026

Bare Repository Attacks in Git

A bare repository is a Git repo with no working tree, just the .git internals. Git will recognize one anywhere it finds the right directory structure. An attacker can exploit this by embedding a bare repo inside a normal repository, tucked into a subdirectory or smuggled in via submodules.

When you clone the outer repo and cd into it, Git may auto-discover the embedded bare repo. If that bare repo has hooks configured (say, a post-checkout or pre-commit), they run on your machine. Arbitrary code, no prompt.

The fix landed in Git 2.38 with safe.bareRepository:

git config --global safe.bareRepository explicit

With explicit set, Git refuses to operate in any bare repository unless its path has been added to safe.directory. It won't auto-discover bare repos nested inside cloned projects.

Without this, cloning a repo someone hands you could silently execute whatever they put in a hook. The attack surface is small but the impact is full code execution.

No reactions yet
March 2026
posted on 03.30.2026

IndieWeb Note vs Article Distinction

The IndieWeb specification formally distinguishes two written post types based on structure, not length:

  • Article: A post with an explicit name (title) property. Usually has multiple paragraphs, subheadings, blockquotes. What most people call a "blog post."
  • Note: A post with no name property or an empty string name. Short, unstructured plain text. The equivalent of a tweet or toot.

The distinction matters for Post Type Discovery, the algorithm IndieWeb clients use to determine how to render a post:

if post has "name" property:
  if name != content (first N chars):
    → article
  else:
    → note
else:
  → note

This means a titled, categorized, tagged post with multiple paragraphs is an article by IndieWeb definition, even if you call it a "note" on your site. The IndieWeb "note" is specifically the titleless micro-post.

Other recognized post types: photo, video, audio, bookmark, reply, repost, like, checkin, rsvp, event. All defined by which microformat properties are present, not by an explicit "type" field.

No reactions yet
March 2026
posted on 03.30.2026

Mixture-of-Experts Architecture in LLMs

Mixture-of-Experts (MoE) is an architecture where a model is divided into smaller "expert" sub-networks instead of one monolithic neural network. For any given input, a gating mechanism routes tokens to only the most relevant experts.

Input → Gating Network → selects top-k experts → Expert outputs → Combined output

Key properties:

  • Sparse activation: Only a fraction of the total parameters are used per forward pass. A 1.8T parameter MoE model might only activate 200B parameters per token.
  • Efficiency: Training and inference are faster per FLOP because most of the network is dormant for any given input.
  • Specialization: Experts learn to handle different types of inputs (code vs prose vs math) without explicit assignment.

Google pioneered this in deep learning through a lineage of papers:

  1. Sparsely-Gated MoE (Shazeer et al., 2017) — introduced trainable gating
  2. GShard (Lepikhin et al., 2020) — scaled MoE to 600B parameters
  3. Switch Transformer (Fedus et al., 2021) — simplified to top-1 expert routing
  4. Gemini 1.5 (2024) — production MoE, 1.5 Pro matches Ultra performance at lower compute

The tradeoff: MoE models have large total parameter counts (need more memory to load), but use fewer FLOPs per token (faster inference). This is why Gemini 1.5 Pro can match 1.0 Ultra quality while being cheaper to run.

No reactions yet
March 2026
posted on 03.30.2026

Next.js force-static Pages Require Dev Server Restart

Pages with export const dynamic = 'force-static' and export const revalidate = false are fully pre-rendered at build time via generateStaticParams(). In development mode, this means:

  • New MDX files added to a content directory are not picked up by hot reload.
  • New database entries are not reflected until the dev server restarts.
  • Changes to existing MDX files may or may not trigger recompilation depending on webpack's file watcher.
export const dynamic = 'force-static'
export const revalidate = false

export async function generateStaticParams() {
  // This runs ONCE at server start in dev mode
  const posts = getContentByType("news")
  return posts.map(p => ({ slug: p.slug }))
}

The generateStaticParams() function executes once when the dev server starts. Adding a new row to the database or a new .mdx file does not re-trigger it.

To see new content in dev: kill the server (fuser -k 3000/tcp), remove the lock file if needed (rm .next/dev/lock), and restart. There is no hot-reload workaround without changing the page to force-dynamic (which sacrifices static generation).

In production builds (npm run build), this is not an issue because generateStaticParams() runs during the build and all pages are pre-rendered at that point.

No reactions yet
March 2026
posted on 03.30.2026

The fuser Command Kills Processes on a Port

fuser identifies processes using files or sockets. Combined with -k, it kills whatever is holding a port open. Essential for freeing up ports when a dev server crashes without cleaning up.

# Kill whatever is on port 3000
fuser -k 3000/tcp

# Just identify the process (no kill)
fuser 3000/tcp
# Output: 3000/tcp:  12345

# Kill with a specific signal
fuser -k -SIGTERM 3000/tcp

Comparison with alternatives:

# lsof approach (more verbose, more info)
lsof -ti:3000 | xargs kill

# ss + grep approach (most portable)
ss -tlnp | grep :3000

fuser is part of psmisc on most Linux distributions. It is simpler than lsof for the common case of "kill whatever is on this port." The output format (PID/protocol) is also easier to parse in scripts.

On macOS, fuser exists but behaves differently. Use lsof -ti:PORT | xargs kill instead.

No reactions yet
March 2026
posted on 03.29.2026

Git Submodules Are Not Cloned in Docker Builds

When a Dockerfile copies source with COPY . ., git submodules appear as empty directories. Git submodules are tracked by reference (a commit SHA in .gitmodules), not by content. Docker's build context does not include .git/ metadata, so there is nothing to resolve the submodule from.

# Submodule directory will be EMPTY after this
COPY . .

# Fix: clone the submodule content directly if missing
RUN if [ ! "$(ls src/content/blog/*.mdx 2>/dev/null)" ]; then \
      echo "Fetching content..." && \
      apk add --no-cache git && \
      git clone --depth 1 \
        "https://x-access-token:${GITHUB_TOKEN}@github.com/user/content.git" \
        src/content && \
      rm -rf src/content/.git; \
    fi

The pattern: check if the submodule directory has the expected files. If not, clone it with --depth 1 (shallow, fast) using a GitHub token passed as a build arg. Remove .git/ after cloning to keep the image small.

This applies to every submodule independently. If you have three submodules (content, prompts, til), you need three separate clone fallback blocks.

Alternative: use git clone --recursive in CI before docker build, so the build context already has submodule contents. But this requires CI to have git access, which is not always the case with managed platforms like Dokploy.

No reactions yet
March 2026
posted on 03.29.2026

HTML Button Nesting Causes Hydration Errors

In HTML, <button> cannot be a descendant of another <button>. Browsers silently restructure the DOM when they encounter this, but React's server-rendered HTML does not match the browser's restructured DOM, causing a hydration mismatch.

// BAD: nested button causes hydration error
<button onClick={openMenu}>
  <Icon />
  {isOpen && (
    <div className="submenu">
      <button onClick={selectOption}>Option A</button>  {/* nested! */}
    </div>
  )}
</button>
// GOOD: move submenu outside the button, wrap both in a div
<div onMouseEnter={showMenu} onMouseLeave={hideMenu}>
  <button onClick={openMenu}>
    <Icon />
  </button>
  {isOpen && (
    <div className="submenu">
      <button onClick={selectOption}>Option A</button>
    </div>
  )}
</div>

The error message in Next.js dev mode is: In HTML, <button> cannot be a descendant of <button>. This will cause a hydration error.

This also applies to <a> inside <a>, <form> inside <form>, and other elements with restrictions defined in the HTML spec's content model.

No reactions yet
March 2026
posted on 03.29.2026

Next.js dynamicParams and NoFallbackError

In Next.js, setting dynamicParams = false on a page with generateStaticParams() tells the framework to return a hard error for any path not pre-generated at build time. This is not a 404. It is an unhandled NoFallbackError that spams server logs.

// This CRASHES on unknown slugs — even from bots/crawlers
export const dynamicParams = false

export async function generateStaticParams() {
  return posts.map(p => ({ slug: p.slug }))
}
// This gracefully falls through to notFound()
export const dynamicParams = true

export default async function Page({ params }) {
  const post = posts.find(p => p.slug === params.slug)
  if (!post) notFound() // proper 404
}

With dynamicParams = false, every crawler hitting /blog/nonexistent-slug throws NoFallbackError in the server logs. In production with Docker Swarm, this can make the service appear unhealthy if the error rate is high enough.

The fix is dynamicParams = true combined with explicit notFound() calls in the page component. You still get static generation for known slugs via generateStaticParams(), but unknown slugs get a clean 404 instead of a crash.

No reactions yet
March 2026
posted on 03.28.2026

D3 Force Simulation Tuning for Knowledge Graphs

When building a force-directed knowledge graph with D3.js, the default force settings cluster everything into an unreadable blob. The key parameters to tune for readable cluster separation:

const simulation = d3.forceSimulation(nodes)
  .force("link", d3.forceLink(links).id(d => d.id).distance(60).strength(0.3))
  .force("charge", d3.forceManyBody().strength(-120).distanceMax(400))
  .force("center", d3.forceCenter(width / 2, height / 2).strength(0.05))
  .force("collision", d3.forceCollide().radius(d => nodeRadius[d.type] + 4).strength(0.7))
  .force("x", d3.forceX(width / 2).strength(0.03))
  .force("y", d3.forceY(height / 2).strength(0.03))
  .alphaDecay(0.02)

What each does:

  • Charge strength (-120): Repulsive force between all nodes. More negative = more spacing. distanceMax(400) caps the influence range so distant clusters do not push each other to infinity.
  • Link distance (60) + strength (0.3): How far apart linked nodes want to be. Lower strength makes the links springier, letting clusters breathe.
  • Center strength (0.05): Very weak pull toward center. Prevents drift without collapsing the graph.
  • Collision radius (+4 padding): Physical collision boundary. strength(0.7) means nodes push each other away firmly but not rigidly.
  • forceX/forceY (0.03): Gentle gravity toward center on each axis independently. Works with center force to keep things on screen.
  • alphaDecay (0.02): Slower cooldown. Default is 0.0228. Lower values let the simulation settle longer, producing better final positions.

The defaults (charge: -30, linkDistance: 30, no forceX/Y) are designed for small graphs. For 500+ nodes with multiple types, you need 3-4x stronger repulsion and explicit centering forces.

No reactions yet
March 2026
posted on 03.28.2026

React useMemo Prevents Infinite useEffect Loops

Deriving an array with .map() inside a component body creates a new array reference on every render, even if the contents are identical. If that array is in a useEffect dependency list, it triggers an infinite loop.

// BAD: entries is a new array every render, useEffect fires forever
const entries = entriesWithContent.map(e => e.entry)

useEffect(() => {
  fetchReactions(entries)
}, [entries]) // new reference every render = infinite loop
// GOOD: useMemo stabilizes the reference
const entries = useMemo(
  () => entriesWithContent.map(e => e.entry),
  [entriesWithContent]
)

useEffect(() => {
  fetchReactions(entries)
}, [entries]) // only fires when entriesWithContent actually changes

This applies to any derived value in a dependency array: .filter(), .reduce(), Object.keys(), spread into new object, etc. If it creates a new reference, wrap it in useMemo.

React's comparison is referential (Object.is), not structural. Two arrays with identical contents are still different objects.

No reactions yet
February 2026
posted on 02.07.2026

"Diary" comes from Latin diarium, meaning "daily allowance" or "daily record," from dies ("day"). At its root, a diary is just a thing organized by days. Historically it was practical: logs, expenses, travel notes, weather, religious observance. The emotional confessional angle came later, once people decided their inner turbulence deserved timestamps.

No reactions yet
January 2026
posted on 01.25.2026

I collect and use vintage Macs, so the A1502 was on my radar. The pre-2016 MacBook Pros are the last ones with keyboards that don't self-destruct.

The MacBook Pro model A1502 is a 13-inch Retina MacBook Pro from the 2013-2015 era. It uses the older scissor-switch keyboard, not the butterfly keyboard that Apple introduced in 2016 and later abandoned after years of reliability complaints. The butterfly keyboards had shallow key travel and were notorious for getting stuck from dust. The A1502's keyboard is from the generation people actually liked.

No reactions yet
January 2026
posted on 01.06.2026

I was trying to place a bassline stuck in my head and initially confused it with the Cosby Show intro. Took me a minute to realize it was the George Lopez theme.

The theme song for the George Lopez sitcom (2002-2007) is "Low Rider" by War, released in 1975. It's not a cover or a remix; it's the original funk track, licensed directly for the show. The song already had deep cultural associations with Chicano car culture and Southern California, which made it a natural fit for the show's setting and tone. Easy to mix up similar funk basslines if they're living in the same corner of memory.

No reactions yet
September 2025
posted on 09.23.2025

This came up while I was building a tier list of collector's editions and studying binding quality differences across publishers.

The International Collectors Library (ICL) was published by the Doubleday Book & Music Club starting in the 1940s. The books have faux leather or leatherette covers with gold embossing and look nice on a shelf, but they're not in the same tier as Easton Press or Franklin Library in terms of collectibility or resale value. Easton Press uses genuine leather and Smyth-sewn bindings. ICL books are more like a dressed-up book club edition. Still pleasant to own, but don't pay collector prices for them.

No reactions yet
August 2025
posted on 08.03.2025

"Sleigh Ride" was originally a purely instrumental orchestral piece composed by Leroy Anderson in 1948, with no lyrics at all. Mitchell Parish added the words ("Just hear those sleigh bells jingling...") two years later in 1950. The lyrics don't mention Christmas even once. They describe a general winter scene: snow, sleigh bells, a fire at the end of the ride. It became a holiday staple purely through cultural association, not because the song has anything to do with Christmas.

No reactions yet
July 2025
posted on 07.22.2025

Haruki Murakami's memoir about long-distance running is called What I Talk About When I Talk About Running (2007). The title is a deliberate nod to Raymond Carver's short story collection What We Talk About When We Talk About Love. Murakami actually translated Carver's work into Japanese. The memoir covers his parallel life as a novelist and marathon runner, and the discipline both require.

The Japanese title is Hashiru Koto ni Tsuite Kataru Toki ni Boku no Kataru Koto. It mirrors Carver's title structure almost word for word, which shows how literally Murakami mapped the reference across languages.

No reactions yet
July 2025
posted on 07.12.2025

The use of "baby" as a romantic pet name for women became popular in the 1900s-1920s Jazz Age. African American jazz and blues culture drove the adoption, with artists using "baby" in lyrics as a term of affection. Before that, the word just meant an actual infant. The romantic usage spread through popular music into mainstream American English. By the 1950s and 60s, rock and roll had cemented it as a default term of endearment. "Babe" followed a similar path. Both terms originated in Black vernacular and became controversial as they crossed into mainstream white usage, stripped of their original cultural context.

No reactions yet
July 2025
posted on 07.11.2025

Christopher Nolan's Memento (2000) follows Leonard Shelby, who has anterograde amnesia and can't form new memories. He tracks information through Polaroid photos, notes, and tattoos on his body. The film's structure alternates between color sequences shown in reverse chronological order and black-and-white sequences shown chronologically, with both timelines converging at the end. It was Nolan's breakthrough film, made for about $4.5 million.

The dual-timeline structure looks like this:

Color scenes (reverse):    ... C4 → C3 → C2 → C1
                                                  \
                                                   → converge at ending
                                                  /
B&W scenes (forward):     B1 → B2 → B3 → B4 ...

The color scenes show the "present" moving backward in time. The black-and-white scenes show an earlier period moving forward. Both threads meet at the chronological middle of the story, which is the film's final scene.

No reactions yet
July 2025
posted on 07.09.2025

I was going down a nursery rhyme rabbit hole, looking up the origins of "There Was a Crooked Man" and other classic English rhymes, and the Opies kept getting cited as the authority.

Iona and Peter Opie published The Oxford Dictionary of Nursery Rhymes in 1951, with a revised edition in 1997. It covers over 500 rhymes with historical context and folk origins. The Opies spent decades collecting and documenting children's folklore and are considered the foremost scholars of English nursery rhymes. Their work traces many rhymes back to political satire, street cries, and medieval songs that had nothing to do with children originally.

No reactions yet
July 2025
posted on 07.09.2025

I was reading about the Crooked Man from The Conjuring 2 (2016), one of Valak's manifestations, and got curious about the nursery rhyme behind it.

The nursery rhyme "There Was a Crooked Man" first appeared in print in James Orchard Halliwell's The Nursery Rhymes of England in 1842, though the rhyme itself is probably older. Some historians believe the "crooked man" refers to the Scottish General Sir Alexander Leslie, who signed a covenant securing religious and political freedom for Scotland. The "crooked stile" supposedly refers to the border between England and Scotland.

No reactions yet
June 2025
posted on 06.27.2025

I was watching Girl Interrupted and noticed Lisa (Angelina Jolie) calls Susanna (Winona Ryder) "Susie Q." I wanted to know where that nickname came from.

It traces back to a 1930s jazz dance step from the Lindy Hop and tap scenes. Lil Hardin Armstrong (Louis Armstrong's wife) popularized it with her 1936 novelty tune "Doin' the Suzie-Q." One story says the dance's name came from a mishearing of "Syracuse," where a couple from Georgia first introduced the step. Dale Hawkins picked up the name for his 1957 rockabilly hit "Susie Q," which Creedence Clearwater Revival covered in 1968.

No reactions yet
June 2025
posted on 06.18.2025

Newly Discovered Mathematical Resources

Upon browsing r/mathematics I came across this post.

The Premise

The inquiry of this post was to find "books that would give a layperson introduction into a mathematical way of thought.

There were numerous responses to this of which I have left below with a link to each message.

The Resources

here the numerous resources are listed with links to the original posters.

  • Higher Math for Beginners by Isaak Yaglom & Yakov Zeldovich user
  • Letters to a Young Mathematician by Ian Stewart user
  • Mathematics, a Very Short Introduction by Timothy Gowers user
  • Measurement by Paul Lockhart user
  • Numbers: The Language of Science by Tobias Dantzig user
  • The Magical Maze by Ian Stewart user
  • Several Books by Raymond Smullyan (look for his books on logic) user
  • Journey Through Genius & The Mathematical Universe by Wiliam Dunham user
  • Zero: The Biography of a Dangerous Idea by Charles Seife user
  • How Not to be Wrong by Jordan Ellenberg user
  • Schaums Outlines for Mathematics user
No reactions yet
June 2025
posted on 06.16.2025

Use Zod to validate File input

You can use Zod's instanceof validator to validate file inputs:

const MAX_UPLOAD_SIZE = 1024 * 1024 * 3; // 3MB
const ACCEPTED_FILE_TYPES = ['image/png'];

const Schema = z
  .instanceof(File)
  .optional()
  .refine((file) => {
    return !file || file.size <= MAX_UPLOAD_SIZE;
  }, 'File size must be less than 3MB')
  .refine((file) => {
    return ACCEPTED_FILE_TYPES.includes(file.type);
  }, 'File must be a PNG');

Above example validates the file is the correct max file size and file type.

No reactions yet
June 2025
posted on 06.15.2025

X-Robots-Tag

The HTTP Header X-Robots-Tag is used to instruct search engines on how it should handle indexing the page. It takes in any rule that can be specified in the robots meta tag (<meta name="robots" content="noindex">).

There is a whole list of values that can be used: https://http.dev/x-robots-tag.

I noticed it being used on Netlify preview URLs (example):

X-Robots-Tag: noindex 

Which makes sense because these are draft URLs that should not be indexed.

No reactions yet
June 2025
posted on 06.14.2025

Remove debugger statements in Vite

We can use esbuild's drop option to remove console APIs and debugger statements from our code when we build our application.

export default defineConfig({
    esbuild: {
      drop: ['console', 'debugger']
    }
});
No reactions yet
June 2025
posted on 06.13.2025

Delete lines in vi

You can delete a single or multiple lines in normal mode:

  • Delete a single line: dd
  • Delete multiple lines: [n]dd (n = number of lines)

You can delete a range of lines in command mode:

  • Delete range of lines: :[from],[to]d

There is special characters you can use in the range:

  • .: The current line.
  • $: The last line.
  • %: All lines.

Examples on using the range:

  • :5,10d: Delete lines from 5 to 10.
  • :.,$d: Delete from the current line to the end of file.
  • :.,1d: Delete from the current line to the beginning of file.
  • :5,$d: Delete from line 5 to end of file.
  • :%d: Delete all lines.
No reactions yet
June 2025
posted on 06.10.2025

I was studying creepypasta as a writing form, reading guides on crafting them alongside Greek tragedies. The structural parallels are real: both rely on dramatic irony, dread, and an audience that knows something the protagonist doesn't.

"Creepypasta" is a portmanteau of "creepy" and "copypasta" (internet slang for copied-and-pasted text). The term refers to short horror stories or urban legends shared across the internet, often presented as if they were real accounts or found documents. The form took off in the late 2000s on forums like 4chan and Something Awful. Famous examples include Slender Man, Jeff the Killer, and the SCP Foundation.

No reactions yet
June 2025
posted on 06.10.2025

Type declarations for a Vite app

Vite uses esbuild to transpile Typescript into Javascript and esbuild does not do any type checking.

To generate type declarations, you can use tsc:

tsc --declaration --emitDeclarationOnly

If you are building an application and want to check types only:

tsc --noEmit

Example package.json:

{
  "name": "vite-app",
  "version": "0.0.0",
  "scripts": {
    "build:types": "tsc --declaration --emitDeclarationOnly"
    "check-types": "tsc --noEmit"
  },
  "devDependencies": {
    "vite": "^2.7.2",
    "typescript": "^4.0.3"
  }
}

It's nice to seperate the type checking from the build because:

  • Vite (esbuild) builds faster without it (by 20-30x).
  • It allows you to only generate type declarations when you need to (eg, preparing to package your app).
No reactions yet
June 2025
posted on 06.09.2025

DARPA (Defense Advanced Research Projects Agency) was founded in 1958, originally as ARPA, in response to the Soviet launch of Sputnik. Its mission was to prevent technological surprise. ARPANET, the precursor to the modern internet, was one of its projects. The "D" for "Defense" was added in 1972, removed in 1993, then added back again in 1996.

ARPANET was created during the ARPA era, which is why it's called ARPANET and not DARPANET. The name stuck even after the agency got its D back.

No reactions yet
June 2025
posted on 06.09.2025

User-defined type guard in TypeScript

Types can be narrowed down by using the is keyword:

const isString = (value: unknown): value is string => {
  return typeof value === 'string';
}

function myFunction(value: string | number) {
  if (isString(value)) {
    value
    // ^? (parameter) value: string
  } else {
    value
    // ^? (parameter) value: number	
  }
}

When isString is called, TypeScript will narrow the type to string if the function returns true. TypeScript will also handle the else branch and narrows the type to number as it now knows that it is not a string.

Another example:

type Color = 'red' | 'blue' | 'green';

function isRed(color: string): color is 'red' {
  return color === 'red'
}

function paint(color: Color) {
  if (isRed(color)) {
    color
    // ^? (parameter) color: "red"
  } else {
    color
    // ^? (parameter) color: "blue" | "green"
  }
}

In the example above, isRed narrows color to red if the function returns true, otherwise the type is narrowed to "blue" | "green".

No reactions yet
June 2025
posted on 06.09.2025

Taylor Swift lost ownership of her original master recordings when her former label, Big Machine Records, was sold to Scooter Braun's Ithaca Holdings in 2019. Rather than buy them back, she began re-recording her first six albums starting in 2021, labeling each one "(Taylor's Version)" so fans could stream the versions she owns. It's one of the few times an artist has re-recorded an entire catalog to reclaim ownership.

As of 2025, four have been released: Fearless (April 2021), Red (November 2021), Speak Now (July 2023), and 1989 (October 2023). Reputation and her self-titled debut still don't have Taylor's Version releases.

No reactions yet
June 2025
posted on 06.08.2025

TypeScript Template Literal Types

TypeScript has the ability to add string types using template literal strings:

type EventType = "Click" | "Change";
type OnEventType = `on${EventType}`
      // ^? type OnEventType = "onClick" | "onChange"

It also returns every possible combination of the unions:


type CssAttr = "margin" | "padding";
type Position = "left" | "right" | "top" | "bottom";
type CssKeys = `${CssAttr}-${Position}`
         // ^? type CssKeys = "margin-left" 
         //                   | "margin-right" 
         //                   | "margin-top" 
         //                   | "margin-bottom" 
         //                   | "padding-left" 
         //                   | "padding-right" 
         //                   | "padding-top"
         //                   | "padding-bottom"

Can be used on object keys:

type Events = {
  [key in `on${string}`]: () => void;
}

const events: Events = {
  onClick: () => {},
  onChange: () => {}
  // ^? ✅ (property) onChange: () => void
  brokenFn: () => {}
  // ^? ❌ Object literal may only specify known properties, and 'brokenFn' does not exist in type 'Events'
}

Can be used with the other string manipulation types:

type Action = 'query' | 'mutation';

type Hook = `use${Capitalize<Actions>}`;
  // ^? type Hook = "useQuery" | "useMutation"

Can be used with generics:

type EventType = "click" | "change";

type OnEventType<T extends string> = {
  [key in T as `on${Capitalize<key>}`]: () => void
}

const events: OnEventType<EventType> = {
  onClick: () => {},
  onChange: () => {},
    // ^? ✅ (property) onChange: () => void
  brokenFn: () => {}
   // ^? ❌ Object literal may only specify known properties, and 'brokenFn' does not exist in type 'OnEventType<EventType>'
}

There is also a curated list of Awesome Template Literal Types on GitHub that is worth checking out for more examples.

No reactions yet
June 2025
posted on 06.07.2025

TypeScript: Satisfies operator

Typescript 4.9 introduced the new satisfies operator.

Steve explains it best using this example:

type Route = { path: string; children?: Routes };
type Routes = Record<string, Route>;

const routes = {
  AUTH: {
    path: "/auth",
    children: {
      LOGIN: {
        path: "/login",
      },
    },
  },
  HOME: {
    path: "/",
  },
} satisfies Routes;

routes.AUTH.path; // works
routes.AUTH.children.LOGIN.path; // works
routes.HOME.children.LOGIN.path; // error
//          ^? Property 'children' does not exist on type '{ path: string; }'.

Matt Pocock has a great example of a use-case in his TypeScript 4.9 deep dive video.

No reactions yet
June 2025
posted on 06.06.2025

Inferring the types from a Remix loader

The loader function in Remix can be inferred automatically using:

type LoaderData = Awaited<ReturnType<typeof loader>>;
  • Awaited: Extracts the value returned from a Promise.
  • ReturnType: Constructs a type consisting of the return type of a function.

Putting it together:

import { json } from "@remix-run/node"; 

type LoaderData = Awaited<ReturnType<typeof loader>>;
// ^? LoaderData: Response

export const loader = async () => {
  return json({ ok: true });
};
No reactions yet
June 2025
posted on 06.05.2025

Array.prototype.reduce() can be typed in TypeScript

The return and initial value of the reduce() method can be typed using a generic.

In the example below, the array of products is converted to an object keyed by productId. This was safely typed with ProductsById being passed as a generic.

const products = [
  {
    productId: '12345',
    name: 'Product A'
  },
  {
    productId: '67890',
    name: 'Product B'
  }
];

type Product = { productId: string; name: string };
type ProductsById = Record<string, Product>;

// Transforms the array `products` into an object keyed by `productId`
const productsById = products.reduce<ProductsById>(
  (previousValue, currentValue) => {
    return {
      ...previousValue,
      [currentValue.productId]: currentValue
    };
  },
  {}
);

productsById;
// ^? const productsById: ProductsById

The result of productsById returns the below object which matches the ProductsById type:

{
  "12345": {
    "productId": "12345",
    "name": "Product A"
  },
  "67890": {
    "productId": "67890",
    "name": "Product B"
  }
} 
No reactions yet
June 2025
posted on 06.04.2025

TypeScript: ElementRef for React.useRef

You can extract the type from a useRef hook using ElementRef:

import { useRef, ElementRef } from "react";
 
const Component = () => {
  const audioRef = useRef<ElementRef<"audio">>(null);
//       ^? React.RefObject<HTMLAudioElement>

  return <audio ref={audioRef}>Hello</audio>;
};

Read Matt Pocock's Strongly Type useRef with ElementRef.

No reactions yet
June 2025
posted on 06.03.2025

TypeScript: Type-only imports and exports

TypeScript can enforce explicit type imports and exports using the importsNotUsedAsValues configuration.

{
  "compilerOptions": {
    "importsNotUsedAsValues": "error",
  }
}

Setting this to error will report an error if there is a type being imported without import type syntax.

import type { MyType } from './types';
export type { MyType };

Bundlers prefer this to help avoid potiential problems with types being incorrectly bundled.

No reactions yet
June 2025
posted on 06.02.2025

Extending HTML Element Types in React 18

React 18 removed the children prop from React.FC and must be defined explicitly in your component's type.

When creating new React components that extend HTML elements (eg Button), you will need to specify the children prop in React 18:

type ButtonProps = React.ButtonHTMLAttributes<HTMLButtonElement> & {
  children: React.ReactNode;
};

const Button: React.FC<ButtonProps> = ({
  children,
  disabled,
  name,
  type,
  value
}) => {
  return (
    <button disabled={disabled} name={name} type={type} value={value}>
      {children}
    </button>
  );
};
No reactions yet
June 2025
posted on 06.01.2025

Overriding HTML Attributes in React TypeScript

First extend the native HTML attributes:

interface MyInputProps extends React.HTMLProps<HTMLInputElement> {
  size: "sm" | "md" | "lg";
}

A typescript will appear because TS knows that HTMLInputElement already has a size attribute and it does not match the one we added:

interface MyInputProps extends React.HTMLProps<HTMLInputElement> {
  size: "sm" | "md" | "lg";
// ^? Type 'string' is not assignable to type 'number'
}

We need to omit the native size attribute:

interface MyInputProps extends Omit<React.HTMLProps<HTMLInputElement>, 'size'> {
  size: "sm" | "md" | "lg";
}

And now we can type our new component. Spread the props so our new size prop is not set as the value to the native HTML attribute.

const MyInput: React.FC<MyInputProps> = (props) => {
  const { size, ...rest } = props;
  return <input {...rest} className="my-input" />;
};

If we want to continue using the native size, Chakra UI has a clever technique of using a new prop htmlSize that can be used instead:

interface MyInputProps extends Omit<React.HTMLProps<HTMLInputElement>, 'size'> {
  size: "sm" | "md" | "lg";
  htmlSize?: number;
}

const MyInput: React.FC<MyInputProps> = (props) => {
  const { size, htmlSize, ...rest } = props;
  return <input {...rest} size={htmlSize} className="my-input" />;
};
No reactions yet
May 2025
posted on 05.31.2025

TypeScript: @ts-expect-error

TypeScript allows you to suppress errors on a line by using the @ts-ignore directive right before the erroring line:

// @ts-ignore
const myString: string = 1;

The downside to using @ts-ignore is that there is no indication if it is really suppressing any errors unless the directive is removed. This can lead to forgotten @ts-ignore once the errors has been fixed.

Using @ts-expect-error will behave the same way but if there is no error in the code, TypeScript will report an error that the @ts-expect-error was not necessary:

// @ts-expect-error
const myString: string = 1;

This is a great alternative to @ts-ignore if you intend to fix the code at a later stage. When the error is fixed, TypeScript will remind you to remove the directive.

No reactions yet
May 2025
posted on 05.30.2025

TypeScript: Exhaustiveness checking in switch with union type

The never data type in TypeScript can be used to check that all cases in a switch are considered.

type Day =
  | "Monday"
  | "Tuesday"
  | "Wednesday"
  | "Thursday"
  | "Friday"
  | "Saturday"
  | "Sunday";

function getDayIndex(day: Day) {
  switch (day) {
    case "Monday": {
      return 1;
    }
    default: {
      // `Type 'string' is not assignable to type 'never'.`
      const _exhaustiveCheck: never = day;
      return _exhaustiveCheck;
    }
  }
}

_exhaustiveCheck will have an error because TypeScript is attempting to assign the rest of the Day union to never which cannot happen.

This can be found in the TypeScript documentation.

No reactions yet
May 2025
posted on 05.29.2025

TypeScript Exact Types

TypeScript will not show an error if additional keys are added to an object literal:

type User = {
  name: string;
};

const userData = {
  name: "Peter",
  email: "peter@email.com",
};

function printUser(user: User) {
  console.log(user);
}

printUser({ name: "Peter", email: "peter@email.com" });
// Error: Object literal may only specify known properties, and 'email' does not exist in type 'AllowedType'.

printUser(userData);
// No error on this line

This is because it is intentional. See microsoft/TypeScript#12936.

A workaround is available on https://stackoverflow.com/a/61960616 by using a custom utility Exact:

type Exact<A, B> = A extends B ? (B extends A ? A : never) : never;

type User = {
  name: string;
};

const userData = {
  name: "Peter",
  email: "peter@email.com",
};

function printUser<T>(user: Exact<T, User>) {
  console.log(user);
}

printUser({ name: "Peter", email: "peter@email.com" });
// Error: Object literal may only specify known properties, and 'email' does not exist in type 'AllowedType'.

printUser(userData);
// Error: Argument of type '{ name: string; email: string; }' is not assignable to parameter of type 'never'
No reactions yet
May 2025
posted on 05.28.2025

I was examining a physical book with the "New York: Macmillan; London: Collier Macmillan" imprint and wanted to understand why two names appeared. Bibliographic research for the collection.

When old books list "New York: Macmillan; London: Collier Macmillan," those were two separate publishing entities. The American Macmillan Company was founded in New York in 1869 and sold to the Brett family in 1896. It merged with Crowell-Collier in 1960. The British Macmillan Publishers (founded 1843 by Daniel and Alexander Macmillan) remained independent. They shared a name and some history but operated as distinct companies in different markets for most of the 20th century.

No reactions yet
May 2025
posted on 05.28.2025

TypeScript: exactOptionalPropertyTypes

TypeScript handles optional and undefined differently - exactOptionalPropertyTypes will help catch scenarios where undefined should be typed explicitly.

When turned off:

type User = {
  email?: string;
}

const user: User = { email: undefined }
// Valid

When turned on:

type User = {
  email?: string;
}

const user: User = { email: undefined }
    // Type '{ email: undefined; }' is not assignable to type 'User' with 'exactOptionalPropertyTypes: true'.
    // Consider adding 'undefined' to the types of the target's properties.

Read optional vs undefined | TkDodo's blog to learn the difference and how this can help.

No reactions yet
May 2025
posted on 05.27.2025

TypeScript Config: noUnCheckedIndexAccess

noUnCheckedIndexAccess adds undefined to any un-declared fields in a type. This is useful if you have an index signature and want to check if a property exists before accessing it.

Take this example:

const myObject: Record<string, string[]> = {};

myObject["myKey"].push("myString");

This satisfies TypeScript because myKey is typed as string in the index signature of myObject. What we do not know yet is if myObject["myKey"] is defined for us to use.

With noUnCheckedIndexAccess enabled, TypeScript will warn us that the object is possibly undefined and that we should check it exists:

const myObject: Record<string, string[]> = {};

if (myObject["myKey"]) {
  myObject["myKey"].push("myString");
}
No reactions yet
May 2025
posted on 05.26.2025

TypeScript: Callable interface

Adding an interface for callable objects:

interface Callable {
  (): string;
}

const myCallable: Callable = function () {
  return "I was called";
};

const myFunction = myCallable;
const value = myFunction();
    // ^? const value: string

Another example with parameters:

interface Callable {
  (numOne: number, numTwo: number): number;
}

const addNumbers: Callable = function (numOne, numTwo) {
  return numOne + numTwo;
};

const myFunction = addNumbers;
const value = myFunction(1, 2);
    // ^? const value: number
No reactions yet
May 2025
posted on 05.25.2025

TypeScript Assertion Functions

Assertion functions throw an error if a certain condition is not met.

assert(name === "Peter");

The assertion function will throw an error name is not "Peter".

Assertion Signatures

With assertion functions, there is "assertion signatures" which are used to narrow the type of values to be more specific.

Consider the below example: We are not sure of the type of maybeNumber. We can assert it is a number before continuing with the code flow.

function assert(condition: unknown): asserts condition {
  if (!condition) {
    throw new Error("Assertion failed.");
  }
}

let maybeNumber: any;
assert(typeof maybeNumber === "number");

maybeNumber;
  // ^? let maybeNumber: number

We now know that maybeNumber is a number because the assertion did not fail, so TypeScript narrowed the type down to `number.

We can be more specific with the condition. Below example has an assertion signature asserts value is number:

function assertIsNumber(value: unknown): asserts value is number {
  if (typeof value !== "number") {
    throw new Error("Not a number");
  }
}

let maybeNumber: any;
assert(typeof maybeNumber === "number");

maybeNumber;
  // ^? let maybeNumber: number

Putting it together

We can also include a custom message to be used as the error message when the assertion fails:

function assert(condition: unknown, message?: string): asserts condition {
  if (!condition) {
    throw new Error(message || 'Assertion failed.');
  }
}

let myVariable: { myKey: string } | undefined;

myVariable.myKey;
 // ^? 'myVariable' is possibly 'undefined'.

assert(myVariable, "myVariable is undefined");

myVariable
 // ^? let myVariable: { myKey: string } 
No reactions yet
May 2025
posted on 05.24.2025

TypeScript narrow Array.includes()

A common problem faced with using Array.includes() in TypeScript:

const environments = ["DEV", "UAT", "PROD"] as const;

function isSupported(env: string) {
  if (environments.includes(env)) {
    // Error: Argument of type 'string' is not 
    // assignable to parameter of type '"DEV" | "UAT" | "PROD"'.
  }
}

The definition of Array.includes():

interface Array<T> {
  includes(searchElement: T, fromIndex?: number): boolean;
}

The function wants both the searchElement and the array to be of the same type.

This can be solved by narrowing the type using a helper function:

function includes<T extends U, U>(arr: ReadonlyArray<T>, searchElement: U): searchElement is T {
  return arr.includes(searchElement as T);
}
  • Takes in array of type ReadonlyArray<T> and search for element of type U.
  • T extends U: T is a subset of U.
  • If condition is true, we narrow using searchElement is T.
function isSupported(env: string) {
  if (includes(environments, env)) {
    console.log(env);
              // ^? env: "DEV" | "UAT" | "PROD"
  }
}
No reactions yet
May 2025
posted on 05.23.2025

I was looking into Jeff from The Donna Reed Show and ended up reading about Reed's whole career.

Donna Reed is best known for playing Mary Hatch Bailey in It's a Wonderful Life (1946), but she actually won her Academy Award for From Here to Eternity (1953). She then moved to television, starring as Donna Stone in The Donna Reed Show from 1958 to 1966. Her career spanned over four decades, but those three roles are the ones people remember.

No reactions yet
May 2025
posted on 05.23.2025

The Idiomatic Text element

The HTML i element is named the Idiomatic Text element on MDN Web Docs.

The HTML specifications defines the i element as:

The i element represents a span of text in an alternate voice or mood, or otherwise offset from the normal prose in a manner indicating a different quality of text, such as a taxonomic designation, a technical term, an idiomatic phrase from another language, transliteration, a thought, or a ship name in Western texts.

Historically, the i element was used for presentation and browsers display it in italics. However, even though some browsers continue to display it in italics as a fallback, the i element should not be used for presentational purposes as it does not necessarily mean the text will be in italics.

The i element has semantic meaning and the usage depends on the situation and the surrounding text. MDN Web Docs has a great example:

An example for <i> could be: "The Queen Mary sailed last night". Here, there is no added emphasis or importance on the word "Queen Mary". It is merely indicated that the object in question is not a queen named Mary, but a ship named Queen Mary.

No reactions yet
May 2025
posted on 05.22.2025

The Details disclosure element

The <details> HTML element creates an accordion-like element that the user can toggle open and close.

The W3C HTML specification describes the element:

The details element represents a disclosure widget from which the user can obtain additional information or controls.

Usage

<details>
  <summary>Show/Hide</summary>
  <p>Today I learnt about the `details` element</p>
</details>

The open attribute is a boolean that can be used to indicate if the content is visible or not.

<details open="true">
  <summary>Show/Hide</summary>
  <p>Today I learnt about the `details` element</p>
</details>
No reactions yet
May 2025
posted on 05.22.2025

I was sorting out the difference between the Emmys and the Oscars while reading about Angelina Jolie's Best Supporting Actress win for Girl, Interrupted (1999). That led me to the broadcast history of the ceremony itself.

The 25th Academy Awards ceremony on March 19, 1953, was the first to be televised. Bob Hope hosted. From that ceremony through the 97th in March 2025, there have been 73 televised ceremonies. The average ceremony runs about 3.5 hours, which means if you watched every single televised Oscars back to back, it would take roughly 10.6 days.

No reactions yet
May 2025
posted on 05.21.2025

Using the tar command

Create a compressed tar archive

$ tar -czvf archive.tar.gz ./directory
  • -c: Create archive.
  • -z: Compress using gzip algorithm.
  • -f: Specify filename of archive.
  • -v: Verbose (show progress).

Uncompress a tar archive

$ tar -xvf archive.tar.gz
  • -x: Extract from archive.
  • -f: Specify filename of archive.
  • -v: Verbose (show progress).

Extract tar archive to a different directory

$ tar -xvf archive.tar.gz -C ./another-directory
  • -C: Changes the directory.

Extract specific files from tar archive

$ tar -xvf archive.tar.gz file1 file2 file3

Extract specific files from tar archive using wildcard

$ tar -xvf archive.tar.gz --wildcards '*.js'

List the content of tar archive

$ tar -tf archive.tar.gz
  • -t: List the content.
No reactions yet
May 2025
posted on 05.18.2025

Deep clone object with structuredClone()

structuredClone() is a native API in JavaScript that can do deep cloning of objects:


const original = {
  name: "Peter",
  properties: {
    age: new Date()
  }
};

const copy = structuredClone(original);

copy.properties.action = "Jump";
original.properties.action; // undefined

It can handle circular references and other JS built-in types such as Date, Set, Map.

Learn more on MDN.

What about object spread?

The object spread operator actually does a shallow copy. If you modify a deeply nested property, both objects are affected:

const original = {
  name: "Peter",
  properties: {
    age: new Date()
  }
};

const copy = { ...original };

copy.properties.action = "Jump";
original.properties.action; // "Jump"
No reactions yet
May 2025
posted on 05.17.2025

Spaces vs tabs: It's an accessibility issue

I never got involved in spaces vs tabs debates throughout my career. But the topic has popped up recently and I took notice of something that I have missed for many years: spaces have been an accessibility issue for many developers. For the visually impaired, spaces are hard to read. Tabs give developers the control and flexibility of the spacing to suit their needs.

Good reads

What I learned

  • Developers who are visually impaired can change the tab width to a size that is comfortable on their eyes.
  • If you prefer spaces, you can set the tab width to 2.
  • You can change the tab width in GitHub by using the ?ts= query.
  • You can change the tab size in GitHub globally in the Appearance settings page.
No reactions yet
May 2025
posted on 05.15.2025

setTimeout(): Node.js vs Web

Both return values are passed to clearTimeout() to cancel the timeout.

No reactions yet
May 2025
posted on 05.14.2025

Implementing a custom RTK Query baseQuery

In RTK Query, services are created like this example:

export const myApi = createApi({
  reducerPath: 'myApi',
  baseQuery: fetchBaseQuery({ baseUrl: '/api' }),
  endpoints: (builder) => ({
    getUserById: builder.query({
      query: (id) => `user/${id}`,
    }),
  }),
})

The baseQuery can be a custom utility that can wrap the default fetchBaseQuery.

Below is use-case that provides meta along with each request:

const myBaseQuery = async (args, api, extraOptions) => {
  const requestId = uuid();

  const baseResult = await fetchBaseQuery({ baseUrl: "/api" })(
    args,
    api,
    extraOptions
  );
  
  return {
    ...baseResult,
    meta: baseResult.meta && { ...baseResult.meta, requestId },
  };
};

export const myApi = createApi({
  reducerPath: "myApi",
  baseQuery: myBaseQuery,
  endpoints: (builder) => ({
    getUserById: builder.query({
      query: (id) => `user/${id}`,
    }),
  }),
});

baseResult can return an error if an error occured and we can use that to track which endpoint had an error. api parameter has an endpoint value that has the name of the endpoint (eg getUserById from the above example).

const baseResult = await fetchBaseQuery({ baseUrl: "/api" })(
  args,
  api,
  extraOptions
);

if (baseResult.error) {
  const error = baseResult.error;
  if ("status" in error) {
    const errMsg = "error" in error ? error.error : JSON.stringify(error.data);

    console.error(`Error with ${api.endpoint}: ${error}`);
  }
}
No reactions yet
May 2025
posted on 05.13.2025

Rename an AWS Lightsail instance

AWS Lightsail does not have an option to rename instances. However, you can create a new instance from a snapshot and give it the desired name.

Create a snapshot of the existing instance

  1. From the Lightsail homepage, click on the name of the instance for which you want to rename.
  2. Click on the Snapshots tab.
  3. Create snapshot under the Manual snapshots section.

Create new instance from the new snapshot

  1. Choose the actions menu icon (⋮) next to the newly created manual snapshot.
  2. Select Create new instance.
  3. On the next page, enter the correct name.

Attach existing static IP to the new instance

After verifying the new instance is running as expected, you may now re-attach the existing static IP.

  1. From the Lightsail homepage, click on the name of the instance that is no longer needed.
  2. Stop the instance.
  3. Click on Networking tab.
  4. Detach the static IP.
  5. From the Lightsail homepage, select the newly created instance.
  6. Click on Networking tab.
  7. Attach the static IP to the new instance.
No reactions yet
May 2025
posted on 05.12.2025

Remove React app from the DOM

Use ReactDOM.unmountComponentAtNode() (React < 18) or root.unmount() to remove a React app from the DOM.

import ReactDOM from 'react-dom';

const domNode = document.getElementById('root');
ReactDOM.render(<App />, domNode);

ReactDOM.unmountComponentAtNode(domNode);
import { createRoot } from 'react-dom/client';

const domNode = document.getElementById('root');
const root = createRoot(domNode);

root.render(<App />);

root.unmount();
No reactions yet
May 2025
posted on 05.10.2025

Automatically handle updated_at column

moddatetime() is a trigger that stores the current time into a timestamp field. This can be useful for tracking the last modification time of a particular row within a table.

To use, create a BEFORE UPDATE trigger using this function. Specify a single trigger argument: the name of the column to be modified. The column must be of type timestamp or timestamp with time zone.

create extension if not exists moddatetime schema extensions;

-- assuming the table name is "todos", and a timestamp column "updated_at"
-- this trigger will set the "updated_at" column to the current timestamp for every update
create trigger handle_updated_at before update on todos
for each row execute procedure moddatetime (updated_at);
No reactions yet
May 2025
posted on 05.09.2025

Pierre-Louis Moreau de Maupertuis (1698-1759) was the first to formally state the principle of least action in 1744. The principle says that the path a physical system takes between two states is the one that minimizes (or makes stationary) a quantity called the action. Euler and Lagrange later gave it rigorous mathematical form. In modern notation, the action S is defined as:

S = ∫ L dt

where L is the Lagrangian (kinetic energy minus potential energy). The principle states that the physical path makes S stationary: δS = 0. This one equation underlies classical mechanics, general relativity, and quantum field theory. Feynman's path integral formulation of quantum mechanics is built directly on it.

No reactions yet
May 2025
posted on 05.09.2025

Use pnpm's shell-emulator to execute scripts on all platforms

pnpm can do cross-platform scripting when shell-emulator is enabled.

# .npmrc
shell-emulator=true

It means scripts like this will work across all platforms:

"scripts": {
  "serve": "NODE_ENV=production node server"
}

It is powered by @yarnpkg/shell and it replaces the need to use libraries like cross-env.

Learn More

No reactions yet
May 2025
posted on 05.08.2025

Make changes to a dependency using pnpm patch

pnpm patch allows you to make changes to a dependency package without having to wait for the package maintainers to release the changes.

It first extracts the package into a temporarily directory and asks you to make the changes.

$ pnpm patch my-pkg@1.0.0
You can now edit the following folder: /tmp/5ea276f0eeb3585ea64ddf4b3b7ef377

Once you've made the changes, you patch up the changes using pnpm patch-commit:

$ pnpm patch-commit /tmp/5ea276f0eeb3585ea64ddf4b3b7ef377

This will create a patchfile in your project and pnpm will use this each time you do an pnpm install.

pnpm will reference patches in package.json:

"pnpm": {
  "patchedDependencies": {
    "my-pkg@1.0.0": "patches/my-pkg@1.0.0.patch"
  }
}
No reactions yet
May 2025
posted on 05.08.2025

I was thinking about why songs grow on me after repeat listens. The brain begins to anticipate beats, motifs, and specific moments, and that anticipation heightens the emotional payoff. Turns out there's a name for the underlying mechanism.

The mere exposure effect was first identified by psychologist Robert Zajonc in the 1960s. The finding is simple: the more often we're exposed to a stimulus (a face, a song, a word, an idea), the more we tend to like it. Repeated exposure increases familiarity, and familiarity breeds preference. This applies to advertising, music, interpersonal attraction, and just about everything else.

No reactions yet
May 2025
posted on 05.07.2025

Today I Learned: PHP

I had the opportunity to work on some PHP this week and picked up a few new tricks that are very different from JavaScript:

Computing the difference in arrays

$array1 = array("a" => "green", "red", "blue", "red");
$array2 = array("b" => "green", "yellow", "red");

$result = array_diff($array1, $array2);

Computing the intersection of arrays

$array1 = array("a" => "green", "red", "blue");
$array2 = array("b" => "green", "yellow", "red");

$result = array_intersect($array1, $array2);

Inherit a variable inside an anonymous function

$message = 'world';
$example = function () use ($message) {
    return "hello $message";
};

Reference a private function as a callback

class MyClass {

    public static function getDifference() {
        $array1 = array("a" => "green", "red", "blue");
        $array2 = array("b" => "green", "yellow", "red");

        $result = array_udiff($array1, $array2, array($this, 'filterById'));    
    }

    private function filterById($a, $b) {}
}

Reference static members of a class using self

class MyClass {
    public static $url = "https://petermekhaeil.com/";

    public static function getUrl() {
        return self::$url;
    }
}
No reactions yet
May 2025
posted on 05.05.2025

Null is a billion-dollar mistake

In 2009, Tony Hoare describes his invention as a "billion-dollar mistake":

I call it my billion-dollar mistake. It was the invention of the null reference in 1965. At that time, I was designing the first comprehensive type system for references in an object oriented language (ALGOL W). My goal was to ensure that all use of references should be absolutely safe, with checking performed automatically by the compiler. But I couldn't resist the temptation to put in a null reference, simply because it was so easy to implement. This has led to innumerable errors, vulnerabilities, and system crashes, which have probably caused a billion dollars of pain and damage in the last forty years.

Reading

No reactions yet
May 2025
posted on 05.04.2025

Show data about an npm package using npm view

npm view outputs data about an npm package:

 $ npm view zod

Output:

zod@3.20.6 | MIT | deps: none | versions: 254
TypeScript-first schema declaration and validation library with static type inference
https://zod.dev

keywords: typescript, schema, validation, type, inference

dist
.tarball: https://registry.npmjs.org/zod/-/zod-3.20.6.tgz
.shasum: 2f2f08ff81291d47d99e86140fedb4e0db08361a
.integrity: sha512-oyu0m54SGCtzh6EClBVqDDlAYRz4jrVtKwQ7ZnsEmMI9HnzuZFj8QFwAY1M5uniIYACdGvv0PBWPF2kO0aNofA==
.unpackedSize: 567.1 kB

maintainers:
- colinmcd94 <colin@colinhacks.com>
- vriad <colin@vriad.com>

dist-tags:
alpha: 3.0.0-alpha.29  beta: 3.20.4-beta.0    canary: 1.10.2-canary  latest: 3.20.6         next: 3.8.2-alpha.6    

published 4 days ago by colinmcd94 <colin@colinhacks.com>
No reactions yet
May 2025
posted on 05.03.2025

npm using latest version of package

If you want to use the latest version of a package when using npm create, you need to specify latest as the package version.

It's why most tools will have instructions like below. It's to force npm to install the latest version of the package:

$ npx create-remix@latest
$ npm create svelte@latest
$ npx create-next-app@latest

The above will fetch the latest versions from the registry and execute the script.

If you do not specify a version and have previously installed the package, npm will use that previously installed version.

The same goes with the other npm commands like npm init, npm exec, npx.

No reactions yet
May 2025
posted on 05.02.2025

npm disable audit in .npmrc

npm can get noisy during installs with the audit report:

1 vulnerabilities (0 moderate, 1 high)

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Dan Abramov has a great write-up on why npm audit is broken by design - it is worth the read.

You can disable the audit for all npm commands using .npmrc:

audit=false
loglevel=silent
No reactions yet
May 2025
posted on 05.01.2025

Accessing stdin with file descriptor 0

In Node.js, you can access stdin using file descriptor 0 to take input stream:

const fs = require("fs");
const data = fs.readFileSync(0, "utf-8");

This can be used on the Node.js CLI:

echo Peter Mekhaeil | node -p "fs.readFileSync(0, 'utf8').toLowerCase().replaceAll(' ','-')"
No reactions yet
April 2025
posted on 04.30.2025

Nginx Nested Locations

Nginx supports nested locations:

server {
  location /app {
    location /app/assets {
      # ...
    }
    
    location /app/pages {
      # ...
    }
  }
}

Note that nested locations are not relative to the parent location.

In the above example:

  • location /app parent location block that matches requests starting with /app.
  • location/app/assets nested location that matches /app/assets.
  • location/app/pages nested location that matches /app/pages.
No reactions yet
April 2025
posted on 04.29.2025

Next.js statically typed links

Next.js statically types links to avoid errors when using next/link. The type declarations are generated by the dev/build process and includes information of all valid routes in the application.

import type { Route } from 'next';
import Link from 'next/link'

// ✅
<Link href="/about" />
// ✅
<Link href="/blog/nextjs" />
// ✅
<Link href={`/blog/${slug}`} />
// ✅
<Link href={('/blog' + slug) as Route} />

// ❌ TypeScript errors if href is not a valid route
<Link href="/aboot" />
No reactions yet
April 2025
posted on 04.27.2025

Module Federation

Module Federation is a microfrontend pattern that involves 2 components:

Remote

  • Remote apps expose modules for other microfrontends apps to import.
  • Remote apps are compiled and deployed independently.
  • Remote apps expose an entry file that contains the exposed modules.

Host

  • Host app consumes modules exposed from remote apps.
  • The host app owns a module registry that maps the module names with the remote location.
  • The remote modules are imported dynamically on-demand when requested.

Both Webpack and Vite support Module Federation.

No reactions yet
April 2025
posted on 04.26.2025

Recursively delete .DS_Store

Delete all .DS_Store found within a directory recursively:

find . -name '.DS_Store' -type f -delete -print

-print will also print the path of the file when deleting.

No reactions yet
April 2025
posted on 04.25.2025

Logging variables in Chrome DevTools using logpoints

logpoints in Chrome DevTools allow you insert logging statements without adding breakpoints.

Right-click on the line you want to log:

Screenshot 2022-05-24 at 9 19 45 PMClick to enlargeScreenshot 2022-05-24 at 9 19 45 PM

Add the statement you would like to output to the console:

Screenshot 2022-05-24 at 9 23 58 PMClick to enlargeScreenshot 2022-05-24 at 9 23 58 PM

Everytime the code runs on this logpoint, it will output to the console:

Screenshot 2022-05-24 at 9 27 54 PMClick to enlargeScreenshot 2022-05-24 at 9 27 54 PM

This allows for quick console logging without having you to touch your source code and without having to add breakpoints.

No reactions yet
April 2025
posted on 04.24.2025

JSON.stringify replacer parameter

JSON.stringify has an optional second parameter replacer that can recursively transform properties during the stringify process.

The replacer parameter can be a function or an array:

JSON.stringify({ name: 'Peter', til: true }, ['name']);
// '{"name":"Peter"}'
function replacer(key, value) {
  if (key === 'name') {
    return 'Mekhaeil';
  }
  return value;
}

JSON.stringify({ name: 'Peter', til: true }, replacer);
// '{"name":"Mekhaeil","til":true}'
No reactions yet
April 2025
posted on 04.23.2025

JSON.parse reviver parameter

JSON.parse() has an optional reviver parameter that can transform the value being parsed.

This is useful for deserialization:

const jsonString = '{"date":"Sun, 12 Feb 2023 06:45:00 GMT"}';

function reviver(key, value) {
  if (key === "date") {
    return new Date(value);
  }
  return value;
}

const data = JSON.parse(jsonString, reviver);
    // ^? { date: Date }

JSON.parse's reviver and JSON.stringify's replacer can be combined together to build a JSON serialization and deserialization utility.

No reactions yet
April 2025
posted on 04.22.2025

JavaScript WeapMap

WeakMap have "weak" reference to its keys. When the references to the key is lost and there are more references to the value, the value can be garbage collected.

const pokemon = { name: "Pikachu" };

const myPokemons = new WeakMap();
myPokemons.set(pokemon, 1);

// reference removed
pokemon = null;

// pokemon is removed from memory
myPokemons.has(pokemon); // false

Map vs WeakMap

  • WeakMap keys must be objects and cannot be primitives.
  • If an object is used in a Map, as long as the Map exists, so does that the object.
  • WeakMap does not have interation methods (keys(), values(), entries()).
  • You cannot check the size of a WeakMap.
No reactions yet
April 2025
posted on 04.21.2025

JavaScript: Tagged Template Literals

Tag templates (or tag functions) parse template literals with a function:

function log(strings, ...args) {
  console.log(strings, args[0])
}

const person = "Peter";
log`My name is ${person}`;
// ['My name is ', ''] 'Peter'

First parameter is an array of strings, the following parameters are the variables that were passed in the expression. Think of them as substitutes. The string parts of the template have an index that matches the associated substitute that followed.

In the above example, ['My name is ', ''] was the string, Peter was the first substitute.

Tag templates can come useful in formatting strings:

function introduce(strings, ...args) {
let formattedString = '';

  strings.forEach((string, i) => {
    formattedString += string + (args[i] ? args[i].toUpperCase() : '');
  });

 return formattedString;
}

var person = "Peter";
introduce`My name is ${person}`
// 'My name is PETER'

Tagged Templates in the wild

const GET_DOGS = gql`
  query GetDogs {
    dogs {
      id
      breed
    }
  }
`;
const query = gql`
  {
    user(id: 5) {
      firstName
      lastName
    }
  }
`
const users = await sql`
  select
    name,
    age
  from users
`
const userId = 42
const result = await prisma.$queryRaw`SELECT * FROM User WHERE id = ${userId};`
const Button = styled.a`
  background: white;
  color: black;
`
const Input = tw.input`border hover:border-black`
No reactions yet
April 2025
posted on 04.20.2025

JavaScript Symbol.iterator

Symbol.iterator defines the default iterator for an object. Applying it to an object to enable iteration:

const myObject = { a: 1, b: 2, c: 3 };

myObject[Symbol.iterator] = function* () {
  for (const key of Object.keys(this)) {
    yield [key, this[key]];
  }
};

for (const [key, value] of myObject) {
  console.log(key, value);
}
No reactions yet
April 2025
posted on 04.20.2025

Cunningham's Law states: "The best way to get the right answer on the Internet is not to ask a question; it's to post the wrong answer." It's attributed to Ward Cunningham, the developer of the first wiki. The irony is that Cunningham himself has said he didn't coin this law. It was named after him by a colleague, Steven McGeady, who observed the pattern in Cunningham's behavior on early internet forums.

No reactions yet
April 2025
posted on 04.19.2025

Spread operator clones enumerables properties

Spread operator does a shallow clone of only enumerable properties. Non-enumerable properties such as prototype will not be cloned.

No reactions yet
April 2025
posted on 04.18.2025

Signals

Signals are reactive primitives for managing application state.

(Reference)

Signals are reactive. They keep track of the subscriptions and notify subscribers when state has changed. Calling the getter creates a subscription, telling the signal of the location that requires the value.

Signals vs useState()

  • useSignal() => getter + setter
  • useState() => value + setter

useState() is not reactive. React does not know of the location that requires the value, therefore must re-renders the whole component when calling the setter.

(Reference)

Signals in frameworks

SolidJS

import { createSignal } from "solid-js";

function Counter() {
  const [count, setCount] = createSignal(0);

  setInterval(() => setCount(count() + 1), 1000);

  return <div>Count: {count()}</div>;
}

Qwik

export default component$(() => {
  const count = useSignal(0);

  return (
    <>
      <button onClick$={() => count.value++}>Increment</button>
      Count: {count.value}
    </>
  );
});

Preact

import { signal } from "@preact/signals";

const count = signal(0);

function Counter() {
  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => count.value++}>click me</button>
    </div>
  );
}

Vue

Angular

Angular has announced some prototyping work around adding signals as a reactive primitive in Angular.

No reactions yet
April 2025
posted on 04.17.2025

JavaScript: scrollend event

The new scrollend event fires when scrolling has ended.

addEventListener("scrollend", (event) => {});

onscrollend = (event) => {};

Previously before this event, there was no straightforward way to detect when scrolling has ended. We would have had to use the onscroll and a timer to detect if scrolling has ended.

Read the full write-up on developer.chrome.com.

No reactions yet
April 2025
posted on 04.16.2025

Using Promise.race() to implement request timeout

Promise.race() takes a list of promises and returns the first promise that settles (regardless if fulfilled or rejected).

Good use to implement a request timeout. It resolves the first promise that settles which will either be the fetch request or the timeout promise.

const fetchApi = async () => {
  const res = await fetch("/api");
  return await res.json();
};

const requestTimeout = (delay: number) => {
  return new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error("Timeout")), delay);
  });
};

const data = Promise.race([fetchApi, requestTimeout(5000)]);
No reactions yet
April 2025
posted on 04.15.2025

JavaScript: Negative Zero (-0)

In JavaScript, negative zero -0 is not the same as a positive zero +0.

This is because numbers in JavaScript are represented using the IEEE 754 floating-point standard which requires zeros to have an associated sign. Floating point numbers include a sign bit (0 for positive, 1 for negative). In the case of +0, the sign bit is 0 while in the case of -0 the sign bit is 1.

How does JavaScript handle comparison?

+0 === -0 // true
-0 === +0 // true

This is because of ECMAScript's Strict Equality Comparison Algorithm:

If Type(x) is Number, then
a. If x is NaN, return false.
b. If y is NaN, return false.
c. If x is the same Number value as y, return true.
d. If x is +0 and y is −0, return true.
e. If x is −0 and y is +0, return true.
f. Return false.

How to distinguish between the two?

Object.is() can be used:

Object.is(+0, -0); // false
Object.is(-0, +0); // false

How are strings handled?

Both +0 and -0 will return "0".

const negativeZero = -0;
negativeZero.toString() // "0"

const positiveZero = +0;
positiveZero.toString() // "0"

JSON.stringify({"negativeZero": -0}); // '{"negativeZero":0}'
No reactions yet
April 2025
posted on 04.14.2025

Math.random() vs Crypto.getRandomValue()

Math.random() is implemented using a pseudo-random number generator (PRNG) - the random number is derived from an internal state, which is altered by a fixed algorithm for every new random number. The sequence of random numbers is deterministic.

Crypto.getRandomValues() use a pseudo-random number generator seeded with a value with enough entropy. Implementations do not use a truly random number generator to guarantee enough performance but the output is suitable for cryptographic purposes.

crypto.getRandomValues(new Uint8Array(10))
// Uint8Array(10) [85, 215, 117, 156, 114, 11, 222, 34, 65, 119]
No reactions yet
April 2025
posted on 04.13.2025

JavaScript Map

A much cleaner and performant alternative to objects.

const myPokemons = new Map();

const bulbasaur = { id: 1, name: "Bulbasaur" };

myPokemons.set(bulbasaur.id, bulbasaur);
myPokemons.delete(bulbasaur.id);

Iterating

for (const [key, value] of myPokemons) {
  // ...
}
  • Cleaner to iterate compared to objects.
  • Iterates in the order of entry insertion.

Iterators

// Iterator of all map keys
myPokemons.keys();

// Iterator of all map values
myPokemons.values();

Cloning

const clonedPokemons = new Map(myPokemons);

// deep cloning
const deepClone = structuredClone(myPokemons);

Converting to objects

const myObj = Object.fromEntries(myMap);

Converting to Map

const myMap = new Map(Object.entries(myObj));

Use any type of object as keys

myMap.set(document.body, value);
myMap.set(function() {}, value);
myMap.set(myPokemon, value);
No reactions yet
April 2025
posted on 04.12.2025

A fugue is a composition built on a short melody (the "subject") that enters in one voice and is then imitated by other voices at different pitches, layering on top of each other. The word comes from Latin fuga ("flight"), because the melody seems to flee from one voice to the next. Bach's The Art of Fugue (Die Kunst der Fuge) is considered the pinnacle of the form. He was still working on it when he died in 1750, leaving the final fugue unfinished. The last piece, Contrapunctus XIV, breaks off mid-phrase. The manuscript just stops. Some printed editions include a note at that point: "At this point the composer died." A movement built on interweaving voices, cut short in the middle of a sentence.

No reactions yet
April 2025
posted on 04.12.2025

JavaScript Import Map

An import map is JSON that browsers use to resolve the path of JS modules when used in import and import() statements.

An example can be found on three.js:

<script type="importmap">
  {
    "imports": {
      "three": "https://unpkg.com/three@0.149.0/build/three.module.js"
    }
  }
</script>

<script type="module">
  import * as THREE from 'three';
  const scene = new THREE.Scene();
</script>

Another example commonly seen in micro-frontends:

<script type="importmap">
  {
    "imports": {
      "react": "https://unpkg.com/react@18.2.0/umd/react.production.min.js",
      "react-dom": "https://unpkg.com/react-dom@18.2.0/umd/react-dom.production.min.js"
    }
  }
</script>

<script type="module">
  import * as React from 'react';
  import * as ReactDOM from 'react-dom';
</script>
No reactions yet
April 2025
posted on 04.11.2025

Fetch Response.statusText

Ever wondered why Response.statusText returns an empty string? It's because it is not part of the HTTP/2 spec.

HTTP/2 does not define a way to carry the version or reason phrase that is included in an HTTP/1.1 status line.

Otherwise, in HTTP/1 it will return status message such as OK, Continue and Not Found.

Reference:

No reactions yet
April 2025
posted on 04.10.2025

Copy a function in JavaScript

const newFunction = oldFunction.bind({});
  • Function.prototype.bind() creates a new function with this set to the value passed in the argument.
  • A new function is created and it references the function that bind was called on.
  • The new function will not have references to any additional properties that may have been attached to the original function.
No reactions yet
April 2025
posted on 04.10.2025

The claim that humans only use 5% or 10% of their brains is a neuromyth. Brain imaging studies show that virtually all parts of the brain are active at some point over a 24-hour period. Different regions activate for different tasks. The myth has been around since at least the early 1900s and keeps getting recycled in pop culture (the films Lucy and Limitless both used it as a premise). No neuroscientist has ever endorsed it.

The likely origin is a misquotation of William James, who wrote in The Energies of Men (1907) that people only meet "a small part" of their mental potential. Early neuroscience findings about glial cells (which outnumber neurons and were initially thought to be inert) may have reinforced the confusion. The leap from "we don't use our full potential" to "we only use 10% of our brain" is the kind of distortion that sticks because it sounds empowering.

No reactions yet
April 2025
posted on 04.09.2025

Send analytics data using the Beacon API

The navigator.sendBeacon() method is intended to be used for sending analytics data to a server.

navigator.sendBeacon("/log", analyticsData);
  • It sends the HTTP POST request asynchronously, with no access to the server response.
  • The request is non-blocking, causing no delay to unload or the next navigation.

See documentation on usage.

No reactions yet
April 2025
posted on 04.08.2025

JavaScript Barrel File

A barrel file is a module that re-exports from other modules:

// ./components/index.js
export { Button } from './Button.js';
export { Anchor } from './Anchor.js';
export { Text } from './Text.js'

Usage:

import { Button, Anchor, Text } from './components';

Performance Impact

Barrel files have an impact on your bundler performance (this is how I came across the term Barrel Files).

  • Next.js recommend to not use them.
  • They make it difficult on bundlers to analyse your code.
  • They risk exporting modules of the same name.
  • They have a chance of circular references.

Read More

No reactions yet
April 2025
posted on 04.07.2025

Add an object to existing JSON using jq

# Optional: Create new JSON file `feed.json` with empy array.
jq -n '[]' > feed.json

# Append an object to the array from `feed.json` 
# and store the new JSON in `feed.json.tmp`
jq \
    --arg date "$date" \
    --arg title "$title" \
    '. += [{"date": $date, "title": $title}]' \
    feed.json > feed.json.tmp

# Replace temp file with original file.
mv feed.json.tmp feed.json
  • --arg content "$content" creates a variable $content to be used within the jq tool.
  • '. += [{...}]' feed.json appends a new object to the array from feed.json.
  • > feed.json.tmp is redirecting the output of jq into a temporarily file.
  • mv feed.json.tmp feed.json is replacing original file with the new temporarily file. Basically updating the original file with the new content.
No reactions yet
April 2025
posted on 04.06.2025

Smart App Banners

The banners on top of web apps that promote the native version of the app is controlled using a <meta> tag in the HTML:

<meta name="apple-itunes-app" content="app-id=1477376905, app-argument=https://github.com/" />

See the documentation on Promoting Apps with Smart App Banners . This only works for iOS Safari.

Example from GitHub:

IMG_1477Click to enlargeIMG_1477

No reactions yet
April 2025
posted on 04.06.2025

I was reading about Gege Akutami, the Jujutsu Kaisen creator, and kept seeing the word "mangaka" used without explanation.

A mangaka is a manga artist in Japanese. The word combines manga (comics/whimsical pictures) with -ka (a suffix meaning "person who does"). Unlike American comics, where writing, penciling, inking, and coloring are often split between specialists, a mangaka typically handles both story and art. Some have assistants for backgrounds and inking, but the mangaka is the central creative force.

No reactions yet
April 2025
posted on 04.05.2025

import.meta

import.meta is an object that is available within Javascript ES modules. It contains metadata about the module and is extensible.

It contains import.meta.url that returns the full URL of the module.

<script type="module" src="module.mjs"></script>
// module.mjs
let img = document.createElement('img');
img.src = new URL('image.jpg', import.meta.url);
document.body.appendChild(img);

Difference from process.env

process.env is specific to Node and cannot be used in the browser.

TypeScript support

interface ImportMeta {
  myVariable: string
}

import.meta.myVariable; // string

Uses

Vite extend import.meta to add things like:

No reactions yet
April 2025
posted on 04.04.2025

The Mark Text element

The <mark> HTML element represents marked or highlighted text.

<span><mark>Peter</mark> Mekhaeil</span>

Can be styled:

mark {
  background-color: yellow;
  color: black;
}
No reactions yet
April 2025
posted on 04.03.2025

Hex color notation have an alpha channel

The hex color notation can be described as #RGB[A] or #RRGGBB[AA] - it accepts an alpha channel that can be used to represent the transparency.

When using it as #RRGGBB[AA], the alpha channel is a hexadecimal number where 00 is full transparent and FF full opaque. If using the shorter #RGB[A] notation, it is a hexadecimal number ranging from 0 and F.

#FF7f00     /* orange               */
#FF7f0000   /* orange 	 0% opaque  */
#FF7f0080   /* orange   50% opaque  */
#FF7f00FF   /* orange  100% opaque  */

#01E        /* blue               */
#01E0       /* blue     0% opaque */
#01E8       /* blue    53% opaque */
#01EF       /* blue   100% opaque */

You can find out more on MDN.

No reactions yet
April 2025
posted on 04.02.2025

GitHub Issue Forms

GitHub supports web form fields in issue templates:

name: Feature request
description: Suggest an idea for this project
body:
  - type: markdown
    attributes:
      value: |
        Thanks for taking the time to fill out this feature report!
  - type: textarea
    id: feature-description
    attributes:
      label: Description
      description: "A clear and concise description of what the problem is."
      placeholder: Ex. I'm always frustrated when [...].
    validations:
      required: true

This will render the below form:

Screenshot 2023-12-10 at 9 59 16 PMClick to enlargeScreenshot 2023-12-10 at 9 59 16 PM

Learn More

No reactions yet
April 2025
posted on 04.01.2025

Create a release in GitHub using API

There are 3 ways to create a release using the GitHub API.

Parameters

  • tag_name: The name of the tag.
  • name: The name of the release.
  • body: The description of the release.

There are more parameters in the documentation.

Sample repository with releases: git-tag-release.

Using cURL

curl \
  -X POST \
  -H "Accept: application/vnd.github+json" \
  -H "Authorization: Bearer <YOUR-TOKEN>"\
  -H "X-GitHub-Api-Version: 2022-11-28" \
  https://api.github.com/repos/OWNER/REPO/releases \
  -d '{"tag_name":"v0.0.0","name":"v0.0.0","body":"Full Changelog: https://github.com/OWNER/REPO/commits/v0.0.0"}'

Using JavaScript

Using Octokit:

const octokit = new Octokit({
  auth: 'YOUR-TOKEN'
});

await octokit.request('POST /repos/{owner}/{repo}/releases', {
  owner: 'OWNER',
  repo: 'REPO',
  tag_name: 'v0.0.0',
  name: 'v0.0.0',
  body: 'Full Changelog: https://github.com/OWNER/REPO/commits/v0.0.0'
});

Using GitHub CLI

GitHub CLI Manual

gh api \
  --method POST \
  -H "Accept: application/vnd.github+json" \
  /repos/OWNER/REPO/releases \
  -f tag_name='v0.0.0' \
  -f name='v0.0.0' \
  -f body='Full Changelog: https://github.com/OWNER/REPO/commits/v0.0.0'
No reactions yet
March 2025
posted on 03.31.2025

Show contents of Git stash

To show list of files in the most recent Git stash:

git stash show

To show the diff:

git stash show -p

To show content of nth most recent stash:

git stash show -p stash@{n}
No reactions yet
March 2025
posted on 03.30.2025

Restore a deleted file in Git

Find the commits that contain the file. Take note of the last commit that deleted the file:

git log --all --full-history --oneline -- <file-path>
  • --all: Show commits in all branches, tags and refs.
  • --full-history: Show full history of commits.
  • --oneline: Pretty format because we only need the commit hash.

Restore the file by checking out the commit that happened before it was deleted:

git checkout <deleting-commit>^ -- <file-path>

The ^ means "parent of" - in the above example, it means checkout the parent commit of the deleting commit. This would contain the file and its content before it was deleted.

No reactions yet
March 2025
posted on 03.29.2025

Replace a Git Repository with a New One

Sometimes you need to reset a remote Git repository while keeping your local history intact. Here's how:

1. Add the Remote:

git remote add origin <remote-url>

Links your local repo to the remote.

2. Force Push to Overwrite Remote History:

git push --force --set-upstream origin main

Replaces the remote history with your local history.

No reactions yet
March 2025
posted on 03.28.2025

Git Remove All Commits

Clean up history from a repository by removing the git commits and replacing it with a new single commit.

  1. Create a temporarily branch disconnected from all the other branches and commits:
git checkout --orphan temp_branch
  1. Add the files to new temporarily branch:
git add -A
  1. Commit the changes:
git commit -am "First commit"
  1. Delete the main branch:
git branch -D master
  1. Rename temporarily branch to the main branch:
git branch -m master
  1. Force push the new main branch:
git push -f origin master
No reactions yet
March 2025
posted on 03.27.2025

Git: push --force-with-lease

No reactions yet
March 2025
posted on 03.26.2025

Git: Work with multiple accounts

  • Configure multiple host aliases in ssh configuration:
# ~/.ssh/config

Host github.com
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_rsa
  
Host github.com-work
  HostName github.com
  User git
  IdentityFile ~/.ssh/id_rsa_work
  • To use a different name and email, add conditional includes in git configuration to supply a different .gitconfig path based on working directory:
# ~/.gitconfig

[user]
    name = Default Name
    email = default.name@example.com

[includeIf "gitdir:~/work/"]
    path = ~/work/.gitconfig
# ~/work/.gitconfig

[user]
    name = Work Name
    email = work.email@example.com
  • Use the new host alias as remote url:
git remote add origin git@github.com-work:username/repo.git
No reactions yet
March 2025
posted on 03.25.2025

Use same git commit message as previous commit

git commit --reuse-message HEAD

--reuse-message takes an existing commit and reuse the log message.

Add --edit to bring up the editor if you wish to edit the message before committing.

No reactions yet
March 2025
posted on 03.24.2025

Checkout previous branch in Git

  • git checkout - is a shorthand for git checkout @{-1}. It will checkout the last previous branch.
  • @{-1} refers to the last branch that was checked. It can be used with git checkout @{-1} to checkout the previous branch.
  • You can also checkout the N-th last branch using git checkout @{-N}.
No reactions yet
March 2025
posted on 03.23.2025

Add to previous Git commit

git add my-file
git commit --amend --no-edit
  • --amend: Amends previous commit.
  • --no-edit: Use previous commit message.
No reactions yet
March 2025
posted on 03.22.2025

Get last modified date using GitHub GraphQL API

Query the first item in the history of that path and return the committedDate:

query CommittedDate($name: String!, $owner: String!, $path: String!) {
  repository(owner: $owner, name: $name) {
    ref(qualifiedName: "refs/heads/master") {
      target {
        ... on Commit {
          history(first: 1, path: $path) {
            edges {
              node {
                committedDate
              }
            }
          }
        }
      }
    }
  }
}
No reactions yet
March 2025
posted on 03.21.2025

Redirect stderr to stdout using 2>&1

2>&1 is used to redirect standard error (stderr) to standard output (stdout). It allows you to capture and handle both types of output in the same way.

File descriptors

There are 3 file descriptors, represented by numbers:

Redirection

> is used to redirect the output of a command to something else.

File descriptor

& indicates that what follows is a file descriptor (in the context of a redirection). It is required otherwise it will interpret the 1 as a filename (eg 2>1 would mean "redirect stderr to a file named 1").

Putting it together

2>&1 indicates that file descriptor 2 (stderr) should be redirected to file descriptor 1 (stdout).

Examples

command > /dev/null 2>&1

The stdout of command is redirected to /dev/null and stderr is redirected to stdout. Meaning everything is redirected to /dev/null.

cat file.txt > output.txt 2>&1

Send the content of file.txt to output.txt. If any errors (eg. file does not exist), send it to stdout which is also output.txt.

ls -l ./apps/ ./packages 2> /dev/null

List the content of ./apps and ./packages. If there was any errors (eg. directory does not exist), send stderr to /dev/null.

No reactions yet
March 2025
posted on 03.20.2025

ESLint's no-restricted-syntax rule

ESLint's no-restricted-syntax uses selectors to query an AST and this can be used to restrict certain syntax from being used.

Use a AST Explorer to view the resulting AST of the JavaScript code you want to query.

This rule disallows the use of MyLibrary.myFunction():

{
  "rules": {
    "no-restricted-syntax": [
      "error",
      {
        "selector": "MemberExpression[property.name='myFunction'][object.name='MyLibrary']",
        "message": "'MyLibrary.myFunction()' is depreciated. Please use MyOtherLibrary.myNewFunction()"
      }
    ]
  }
}

This rule disallows the use of MyLibrary().myFunction():

{
  "rules": {
    "no-restricted-syntax": [
      "error",
      {
        "selector": "[property.name='myFunction'] CallExpression[callee.name='MyLibrary']",
        "message": "'MyLibrary().myFunction()' is depreciated. Please use MyOtherLibrary.myNewFunction()"
      }
    ]
  }
}
No reactions yet
March 2025
posted on 03.19.2025

Enable HTTP/2 in Vite's Dev Server by using HTTPS

Take advantage of HTTP/2 in Vite Dev Server by enabling server.https in your vite.config.js.

{
  server: {
    https: true
  }
}

Browsers limit the number of active connections per domain when using HTTP/1.1 and this can be avoided by enabling HTTP/2 which supports unlimited concurrent requests.

Vite's Dev server takes advantage of modern browser's support for ES Modules and instead of bundling your site, the dev server will serve the modules via network requests in your browser. Enabling HTTP/2 can come handy in large applications that need to serve a lot of these modules.

No reactions yet
March 2025
posted on 03.18.2025

EditorConfig Glob Expressions

The sections in .editorconfig are filepath globs. They can be used to define config for certain files and directories:

root = true

[*]
indent_style = tab
indent_size = 2
insert_final_newline = true

# Match exact file package.json
[package.json]
indent_style = space

# Match .css files under test directory
[test/**/*.css]
insert_final_newline = false
No reactions yet
March 2025
posted on 03.17.2025

How to Stop All Docker Containers

docker kill $(docker ps -q)
  • docker ps: Lists all running containers. -q only return the container IDs.
  • docker kill: Stops the continers by container ID.

Remove all containers

docker rm $(docker ps -a -q)
  • docker ps -a -q: Lists all containers (including non-running) and only return their IDs.
  • docker rm: Remove containers by their ID.

Remove all images

docker rmi $(docker images -q)
  • docker images -q: Lists all images by their IDs.
  • docker rmi: Removes docker images by their ID.
No reactions yet
March 2025
posted on 03.16.2025

Push Docker image to self-hosted registry

Steps on building a Docker image and pushing it to a self-hosted registry:

  1. Build the Docker image using docker build.
  2. Run docker login to log in to the registry.
  3. Tag the image to the registry using docker tag.
  4. Push the image to the registry using docker push.

Putting it together in an example:

# Build the image
docker build -t app:1.0.0 .

# Log into the registry
docker login example.com

# Tag the image to the registry
docker tag app:1.0.0 example.com/app:1.0.0

# Push the image to the registry
docker push example.com/app:1.0.0

This will create an image with the name example.com/app:1.0.0 being pushed to the registry.

No reactions yet
March 2025
posted on 03.15.2025

Docker: Copy files from another image

COPY --from is used for mulit-stage builds and it is used to copy from another image, either by referencing a local image name or a tag available on a Docker registry.

COPY --from=nginx:latest /etc/nginx/nginx.conf /nginx.conf

It can also be used for copying from stages created earlier in the Dockerfile:

# stage 1
FROM alpine as git
RUN apk add git

# stage 2
FROM git as fetch
WORKDIR /repo
RUN git clone https://github.com/your/repository.git .

# stage 3
FROM nginx as site
COPY --from=fetch /repo/docs/ /usr/share/nginx/html
No reactions yet
March 2025
posted on 03.14.2025

List the files with the most disk usage

du -h [DIRECTORY] | sort -hr | head -n 10

The du command returns the estimated disk usage used.

  • -h: Show sizes in human readable format (eg. 2K, 1G).

Use sort to organise the output:

  • -h: Sort by human readable numbers (eg. 2K, 1G).
  • -r: Reverse the output (so it is in descending order).

Show the top 10 items using head:

  • -[NUMBER]: Number of lines to output (Default is 10).
No reactions yet
March 2025
posted on 03.13.2025

Override web content and HTTP response headers locally

Chrome DevTools allows you to override API responses and HTTP headers directly in the browser - this is a great feature for testing, debugging, and prototyping without modifying the backend.

You can:

  • Mock API responses: Replace live API data with custom JSON or other content.
  • Modify HTTP headers: Adjust headers like CORS, Content-Type, or authentication tokens to mimic different scenarios.

You just need to enable "Local Overrides" in DevTools and edit the response or headers for the network requests you want to tweak.

More details here: Chrome DevTools Overrides

No reactions yet
March 2025
posted on 03.12.2025

Emulate a focused page in DevTools

If you switch focus from the current page to the Chrome DevTools, some overlay elements automatically hide if they are triggered by focus. For example, dropdown menus.

The Chrome DevTools have a feature to emulate page focus. Press Ctrl+Shift+P (or Cmd+Shift+P on macOS) and search for "Emulate a focused page". You won't lose focus of the page while inspecting the elements.

Screenshot 2023-10-25 at 10 32 59 PMClick to enlargeScreenshot 2023-10-25 at 10 32 59 PM

No reactions yet
March 2025
posted on 03.11.2025

Delete files by modified date

find ./my-folder -mtime +10 -type f -delete
  • -mtime +10: Filter files that have a last modified date 10 days ago.
  • -type f: Filter files only.
  • -delete: Delete files matching the filters.
No reactions yet
March 2025
posted on 03.10.2025

Dart Supports Underscore Digit Separators

In Dart (starting from version 3.6.0), you can use underscores (_) as digit separators in number literals to make them more readable. This is especially handy for large numbers or when grouping digits for higher-level clarity.

Here are some examples:

var n1 = 1_000_000;
var n2 = 0.000_000_000_01;
var n3 = 0x00_14_22_01_23_45;  // MAC address
var n4 = 65_1234_5678;  // SG Phone number
var n5 = 100__000_000__000_000;  // one hundred million million!

This feature makes it easier to read long numeric values and organize digits meaningfully.

Dart isn’t alone in supporting this - other languages like JavaScript, Java, Python, and Kotlin also allow underscore digit separators. It's a great example of a feature that crosses language boundaries to make code cleaner and more human-readable.

No reactions yet
March 2025
posted on 03.09.2025

Cypress cy.clock()

cy.clock() allows you to control time in your Cypress test:

cy.clock();
cy.intercept('GET', '/pokemon').as('pokemon');
// 1st call
cy.wait('@pokemon');

// forward 10 seconds and wait for next `/pokemon` request
cy.tick(10_000);
cy.wait('@pokemon');
No reactions yet
March 2025
posted on 03.08.2025

CSS pseudo-classes and pseudo-elements

Pseudo-classes are states applied to the selected elements. They have a single colon (:).

button:hover {
  background: red;
}

Pseudo-elements selects specific parts of the selected elements. They have double colons (::).

p::first-line {
  text-transform: uppercase;
}
No reactions yet
March 2025
posted on 03.07.2025

CSS :is() pseudo-class

The :is pseudo-class takes a list of selectors and selects any elements that matches the selectors in that list. Its useful to compact large selectors.

Take this example:

header h2,
nav h2,
article h2 {
  color: black;
}

Can be written as:

is:(header, nav, article) h2 {
  color: black;
}

Learn More

It's worth noting there is a difference between :is and :where() when it comes to specificity value. Detailed explanations:

No reactions yet
March 2025
posted on 03.06.2025

CSS :has() pseudo-class

The :has(<selector>) pseudo-class selects elements that contain certain child elements that match the <selector> selectors.

Demo

Open the CodeSandbox demo. The :has() selector is being used to select the pricing card that contains a .popular element:

.pricing-card:has(.popular) { 
  border-color: blue; 
}

Examples

/* section that contain an image */
section:has(img) {
  color: red;
}

/* section that contain a .sale element */
section:has(.sale) {
  border-color: red;
}

/* section that does NOT contain a .sale element */
section:not(:has(.sale)) {
  border-color: red;
}

/* paragraph that contains an anchor */
p:has(a) {
  color: blue;
}

/* paragraph that contains a an image as first sibling */
p:has(> img) {
  color: blue;
}

/* paragraph that contains a an image as first sibling */
p:has(> img) {
  color: blue;
}

/* h1 that is followed by a paragraph */
h1:has(+ p) { 
  margin-bottom: 0; 
}

Support

See caniuse.com for browser support.

Can also use CSS to detect if the feature is supported:

@supports(selector(:has(img))) {}
No reactions yet
March 2025
posted on 03.05.2025

CSS property: font-variant-numeric

Some OpenType fonts support alternate numeric glyphs that can be styled using font-variant-numeric.

Demo

Here is a CodeSandbox that demonstrate each value. This demo uses the "Source Sans Pro" font which supports these features.

Take a look at the tabular-nums feature - it can make the design of tabular data very satisfying.

Syntax

/* normal: Disable using alternate glyphs */
font-variant-numeric: normal;

/* ordinal: Use letters to represent numeric order */
font-variant-numeric: ordinal;

/* slashed-zero: Use a 0 with a diagonal slash */
font-variant-numeric: slashed-zero;

/* lining-nums: Use glyphs that are all aligned by their baseline. */
font-variant-numeric: lining-nums;

/* oldstyle-nums: Use glyphs where some numbers have descenders */
font-variant-numeric: oldstyle-nums; 

/* proportional-nums: Use glyphs where numbers are not all of the same size */
font-variant-numeric: proportional-nums; 

/* tabular-nums: Use glyphs where numbers all have the same width */
font-variant-numeric: tabular-nums; 

/* diagonal-fractions: Use diagonal fractions (numbers are made smaller and separated by a slash) */
font-variant-numeric: diagonal-fractions; 

/* stacked-fractions: Numbers are made smaller, stacked and separated by a horizontal line */
font-variant-numeric: stacked-fractions; 

Values can be combined together:

font-variant-numeric: slashed-zero tabular-nums;

Support

Not all fonts support these features. The values will have no effect if the font family does not have support.

Learn More

No reactions yet
March 2025
posted on 03.04.2025

CSS ::first-letter pseudo-element

CSS can be applied to the first letter of an element:

p::first-letter {
  text-transform: uppercase;
}
No reactions yet
March 2025
posted on 03.03.2025

CSS.escape()

CSS.escape() returns an string that conforms to the CSS selector specs. Useful when generating dynamic CSS selectors based on an input.

CSS.escape("app:@org/name");
// 'app\\:\\@org\\/name'
No reactions yet
March 2025
posted on 03.02.2025

No browser cache with self-signed certificates

If there is a certificate error, Chromium based browsers will not cache responses and will treat a self-signed certificate as a certificate error.

See https://bugs.chromium.org/p/chromium/issues/detail?id=110649#c8.

Mkcert can create a locally-trusted development certificate for your dev servers to prevent this from happening.

No reactions yet
March 2025
posted on 03.01.2025

Buttons can have a value like input elements

The <button> element can have a value like <input> and this value can also be passed to the server when the form is submitted:

<form action="#" method="POST">
  <input type="text" name="name" value="Peter" />
  <button type="submit" name="_action" value="add">Add</button>
  <button type="submit" name="_action" value="delete">Delete</button>
</form>

The form can be submitted with this data without the need of JavaScript. On the server, you can check the value of _action to decide what to do next based on which button the user clicked to submit the form.

If you are submitting the form programmatically using JavaScript, FormData needs to know which button was used to submit the form:

document.querySelector("form").addEventListener("submit", (event) => {
  event.preventDefault();

  const formData = new FormData(event.target);

  // The FormData does not know how the form was submitted. 
  // There could be more than one submit button on the form and
  // we want to include the one that was used to submit the form. 
  // We append the button (also known as the submitter) to FormData.
  // See more: https://developer.mozilla.org/en-US/docs/Web/API/SubmitEvent/submitter
  if (event.submitter) {
    formData.append(event.submitter.name, event.submitter.value);
  }

  const data = Object.fromEntries(formData.entries());
  console.log(data); // {name: "Peter", _action: "add"}
  
  // When ready, submit the form programmatically
});

Remix does a great job at using this technique - they demonstrate it in their video Remix Single: Multiple Forms and Single Button Mutations. The Remix implementation can be found here.

No reactions yet
February 2025
posted on 02.28.2025

Add custom config to Axios requests

Axios supports adding custom config to requests that can be used later throughout the request.

Here is an example adding a custom config called endpointName:

const instance = axios.create();

const { data } = await instance.get("/api", { 
  endpointName: "myApi"
});

It can be used in interceptors:

instance.interceptors.request.use(function (config) {
  console.log(config.endpointName);
  return config;
});

instance.interceptors.response.use(function (response) {
  console.log(response.config.endpointName);
  return response;
});

It is also included in Axios errors:

instance.interceptors.response.use(null, function (error) {
  if (isAxiosError(error)) {
    console.log(error.config.endpointName);
  }

  return Promise.reject(error);
});

TypeScript support

Create an axios.d.ts and add the custom config under AxiosRequestConfig:

import "axios";

declare module "axios" {
  export interface AxiosRequestConfig {
    endpointName?: string;
  }
}
No reactions yet
February 2025
posted on 02.25.2025

GPTBot is OpenAI’s web crawler

OpenAI's web crawler has a user-agent:

User agent token: GPTBot
Full user-agent string: Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko; compatible; GPTBot/1.0; +https://openai.com/gptbot)

Which means it can be restricted to crawl your site using robots.txt:

User-agent: GPTBot
Disallow: /

Found in OpenAI's documentation.

No reactions yet
February 2025
posted on 02.23.2025

Add color using FORCE_COLOR

Node.js supports the FORCE_COLOR environment variable to force color in the terminal output.

FORCE_COLOR=0 // 2 colors (no color)
FORCE_COLOR=1 // 16 colors
FORCE_COLOR=2 // 256 colors
FORCE_COLOR=3 // 16,777,216 colors

Color is automatically disabled when a process is piping output into another process. Use FORCE_COLOR to force color in the piped output.

No reactions yet
February 2025
posted on 02.22.2025

G.H. Hardy once visited Ramanujan in the hospital and mentioned he'd arrived in a taxi numbered 1729, calling it a rather dull number. Ramanujan immediately replied that it wasn't dull at all: 1729 is the smallest number expressible as the sum of two cubes in two different ways. It's now called the Hardy-Ramanujan number, and "taxicab numbers" are named after this story.

The two decompositions:

 1^3 + 12^3  =     1 + 1728  =  1729
 9^3 + 10^3  =   729 + 1000  =  1729
No reactions yet
February 2025
posted on 02.21.2025

There are 44 CSS length units

Adam Argyle has written a Codepen that lists the different units.

To learn more about what each unit means:

Relative

UnitDefinition
%Percentage of the parent element's font size
emFont size of the element
exHeight of the element's font
chWidth of the "0" (zero) character of the element's font
capHight of the capital letters of the element's font
icAdvance measure of the 水 CJK (Chinese/Japanese/Korean) character of the element's font
lhHeight of the element's line height

Root Relative

UnitDefinition
remFont size of the root element
rexHeight of the root element's font
rchWidth of the "0" (zero) character of the root element's font
rlhHeight of the root element's line height
ricAdvance measure of the 水 CJK (Chinese/Japanese/Korean) character of the root element's font
rcapHeight of the capital letters of the root element's font

Absolute

UnitDefinition
pxPixels
ptPoints
pcPicas
inInches
cmCentimeters
mmMillimeters
QQuarter-millimeters

Viewport

UnitDefinition
vwViewport width
vhViewport height
viViewport inches
vbViewport breadths
dvwDynamic viewport width
dvhDynamic viewport height
dviDynamic viewport inches
dvbDynamic viewport breadths
svwSmallest possible viewport width
svhSmallest possible viewport height
sviSmallest possible viewport inches
svbSmallest possible viewport breadths
lvwLargest possible viewport width
lvhLargest possible viewport height
lviLargest possible viewport inches
lvbLargest possible viewport breadths
vminSmallest value between the viewport's width and height
vmaxLargest value between the viewport's width and height

Container

See CSS Container Queries.

UnitDefinition
cqwContainer width
cqhContainer height
cqiContainer inches
cqbContainer breadths
cqminSmallest value between the element's parent container's width and height
cqmaxLargests value between the element's parent container's width and height
No reactions yet
December 2024
posted on 12.10.2024

Chinese Classics & Poetry — Vivien's Recommendations

As I wrap up site development, I finally have time to dive into the deep lit world my friend Vivien introduced me to. Below is a complete tabled overview of the Chinese classics and poetry she recommended, complete with her thoughts and why they matter.

Poetry & Poets

Title / PoetPinyinWhat it isVivien’s take / why start here
苏轼Sū ShìSong-dynasty master of cí lyric poetry“My idol!!! His poems and essays are all gems; his upbeat view of life is worth learning.”
 定风波Dìng Fēng Bōlyric written in exileSee how Su turns hardship into calm humour.
  念奴娇·赤壁怀古Niàn Nú Jiāo · Chì Bì Huái Gǔlyric on the Red CliffGrand historical sweep + personal feeling.
  水调歌头Shuǐ Diào Gē TóuMid-Autumn lyric to his brotherThe famous “May we share the moon” line.
辛弃疾Xīn Qì JíLate-Song patriot-poetHis bold military imagery balances Su’s cosmopolitan ease.
李白Lǐ BáiTang “Immortal of Wine”For exuberant free-spirited verse; try 《将进酒》.
曹操Cáo CāoStatesman-poet of the Three KingdomsRead 《短歌行》 for the heroic Jian’an style.
楚辞Chǔ CíWarring–States anthology of lyrical verseOn Vivien’s shortlist; good gateway to earlier poetic diction.
人间词话Rén Jiān Cí HuàWang Guowei’s notes on cí aestheticsDip in once you’ve sampled Song lyrics – sharp, elegant criticism.

Vivien’s note:
Classic Chinese can feel tough at first; don’t hesitate to read modern annotated editions or bilingual versions. Learning by doing beats endless theory, so write your own verses alongside the reading!

Literature / Classics

TitlePinyinGenre & eraQuick orientation
《道德经》Dào Dé JīngDaoist philosophy, c. 4ᵗʰ BCE81 laconic chapters on dao & governance.
《庄子》Zhuāng ZǐDaoist philosophy & talesPlayful paradoxes that loosen rigid thinking.
《孟子》Mèng ZǐConfucian dialoguesMoral psychology & “benevolent government.”
《荀子》Xún ZǐConfucian realismArgues human nature is “bad” → need for ritual.
《韩非子》Hán Fēi ZǐLegalist essaysRuthless statecraft; counter-weights Mencius.
《大学》 + 《原本大学微言》Dà XuéConfucian primer + modern commentaryPath from self-cultivation to good rule.
《周易》 ★Zhōu YìClassic of ChangesVivien’s pick; read both text & commentaries.
《吕氏春秋》Lǚ Shì Chūn QiūEncyclopedic Qin-era synthesisEarly attempt to blend the Hundred Schools.
《鬼谷子》Guǐ Gǔ ZǐStrategy & persuasionRhetoric for diplomats and spies.
《了凡四训》Liǎo Fán Sì XùnMing self-cultivation manualKarma, virtue, and practical life advice.
《增广贤文》 ★Zēng Guǎng Xián WénMing proverb anthologyCompact wisdom; great for aphoristic style.
《战国策》Zhàn Guó Cè“Warring-States Strategems”Anecdotes & persuasion tactics of envoys.
《左传》Zuǒ ZhuànSpring-&-Autumn historyNarrative history + moral judgment.
《史记》Shǐ JìGrand Scribe’s RecordsFoundation of Chinese historiography.
《孙子兵法》Sūn Zǐ Bīng FǎThe Art of WarTimeless strategy in 13 terse chapters.
《三国演义》Sān Guó Yǎn YìMing epic novel“70 % history, 30 % fiction” – gripping, vast cast.
《古文观止》Gǔ Wén Guān ZhǐAnthology of exemplary proseUse for prose-style imitation drills.
《郁离子》Yù Lí ZǐYuan-dynasty fablesPhilosophical allegories in terse prose.
《警世通言》, 《醒世恒言》, 《喻世明言》Féng MènglóngMing vernacular story collectionsMorality tales; vivid characters and dialogue.

★ = Vivien explicitly recommended

Thanks to Vivien for curating this treasure trove. I’m going to post reflections, translations, and analyses here as I go. If you’re reading any of these too, feel free to reach out or tag me in your own write-ups.

Next up: writing my own ci after Su Shi

No reactions yet
November 2024
posted on 11.14.2024

I was going through AoPS material for competition prep, looking at their calculus coverage and geometry/stereometry resources, and using Alcumus for integration bee practice. It's one of the better free tools for drilling at the AMC/AIME level.

Alcumus is a free adaptive math practice tool built by Art of Problem Solving (AoPS). It adjusts difficulty based on the student's performance and covers algebra, number theory, combinatorics, and geometry. It's primarily used by students preparing for math competitions like AMC, AIME, and MATHCOUNTS. The name is a play on "calculus" (or possibly a reference to Alcuin of York, the medieval scholar who posed famous math puzzles).

No reactions yet
November 2024
posted on 11.08.2024

I was researching the unified field theory timeline and found that Schrodinger also spent years on the same problem. Both men working in parallel on the same dead end is a striking detail.

The last major project Einstein worked on before his death in 1955 was a Unified Field Theory, an attempt to combine gravity and electromagnetism into a single mathematical framework. He spent roughly the last 30 years of his life on it. He never succeeded, and most physicists at the time considered the effort misguided. The problem he was trying to solve, unifying all fundamental forces, remains unsolved.

No reactions yet
October 2024
posted on 10.16.2024

Alfred North Whitehead and Bertrand Russell published Principia Mathematica in three volumes from 1910 to 1913. The project attempted to derive all mathematical truths from a set of axioms using symbolic logic. The proof that 1+1=2 doesn't appear until Volume II, around page 362. Russell later said the work took them ten years of grinding labor and nearly broke both of them intellectually.

This is a completely different book from Isaac Newton's Philosophiae Naturalis Principia Mathematica (1687), which laid out the laws of motion and universal gravitation. They share a name and nothing else. Newton's is physics; Whitehead and Russell's is pure logic.

No reactions yet
October 2024
posted on 10.15.2024

I was deep into Lockhart's work when I found this. I'd already read Arithmetic and Measurement and was tracking down the books Lockhart himself cites. This essay was the starting point for all of it.

"A Mathematician's Lament" is a 2002 essay by Paul Lockhart, a K-12 math teacher who previously worked in research mathematics at Brown and UC Santa Cruz. The essay argues that math education has been reduced to rote procedures and has lost all contact with the creative, exploratory nature of actual mathematics. Lockhart compares it to teaching music by having students fill out sheet music notation worksheets without ever hearing a song. Keith Devlin helped popularize it by sharing it on his MAA column in 2008.

No reactions yet
October 2024
posted on 10.08.2024

I was mapping my own study areas against the trivium and quadrivium to see what classical education covers and where the gaps are. I also looked into classical education publishers and the role of printing presses in spreading the model. This was curriculum design research.

In classical education, the Trivium consists of three disciplines: Grammar (the mechanics of language), Logic (the art of reasoning), and Rhetoric (the art of persuasion). Together with the Quadrivium (arithmetic, geometry, music, astronomy), they formed the seven liberal arts that structured medieval European education. The word "trivium" literally means "three ways" or "three roads" in Latin. "Trivial" originally meant "of the trivium," something basic that any educated person should know.

No reactions yet
August 2024
posted on 08.13.2024

I was building a list of names for a project, things like Edith, Erudite, Verity, and Minerva kept surfacing. I ended up using "Minerva Remington" as a character name. But I wanted to know what it actually meant.

The name Minerva comes from the Latin word mens, meaning "mind" or "intellect." She was the Roman goddess of wisdom, arts, trade, and strategic warfare, the equivalent of the Greek Athena. The etymological root is the same one that gives us "mental" and "mentor."

No reactions yet
July 2024
posted on 07.09.2024

I was combining Pomodoro with time blocking and ultradian rhythms, trying to build a daily structure that actually held. That's when I looked up whether anyone had patented the technique.

The Pomodoro Technique can't be patented because it's a method, not a tangible invention. Francesco Cirillo developed it in the late 1980s: work in 25-minute intervals separated by short breaks, using a timer. But patent law doesn't cover abstract ideas or methods of organizing human activity. The technique is named after the tomato-shaped kitchen timer Cirillo used as a university student.

No reactions yet
June 2024
posted on 06.30.2024

I was studying Pareto as a productivity principle, not just as economics history. I'd been mapping competence pyramids and looking for frameworks that actually predict where effort pays off.

The Pareto Principle (the 80/20 rule) is named after Italian economist Vilfredo Pareto, who observed around 1896 that roughly 80% of the land in Italy was owned by 20% of the population. He also noticed that 20% of the pea pods in his garden produced 80% of the peas. The principle was later generalized by Joseph Juran in the 1940s, who named it after Pareto and applied it to quality control and business management.

No reactions yet
June 2024
posted on 06.22.2024

The writing technique of placing short, blunt sentences side by side without conjunctions is called parataxis. Hemingway used it constantly:

He sat down. He drank his coffee. He looked at the rain.

The opposite is hypotaxis, which uses subordinate clauses to create complex, nested sentences. Parataxis creates a feeling of immediacy and flatness, like the narrator is just reporting what happened without interpreting it.

No reactions yet
June 2024
posted on 06.02.2024

I spent a while on this one. I was looking for simpler ways to explain cognitive load, studying Sweller's empirical outcomes, and trying to frame cognitive load as a finite yet renewable resource. Self-directed pedagogy research, not homework.

Cognitive Load Theory was developed by John Sweller, an Australian educational psychologist, in the late 1980s. The core idea is that working memory has limited capacity, and instructional design should minimize unnecessary cognitive load. Sweller identified three types: intrinsic (inherent difficulty of the material), extraneous (caused by poor instruction), and germane (effort devoted to building schemas). It's one of the most empirically supported theories in educational psychology.

No reactions yet
April 2024
posted on 04.17.2024

I came across semiotics while studying how occult and esoteric symbols function in institutional contexts, how a symbol gets loaded with meaning and then used to reinforce ideological structures.

The study of signs and symbols, how they create meaning, and how they're interpreted is called semiotics. Ferdinand de Saussure and Charles Sanders Peirce developed the field independently in the early 20th century. Saussure called it "semiology" and approached it from linguistics; Peirce called it "semiotics" and came at it from logic and philosophy. Peirce's term won out.

No reactions yet
April 2024
posted on 04.01.2024

The English word "April" comes from Latin Aprilis, which is believed to derive from the verb aperire, meaning "to open." The connection is to buds and blossoms opening in spring. There's no consensus on this etymology, but it's the most widely accepted one. The month was the second month in the early Roman calendar before January and February were added.

No reactions yet
March 2024
posted on 03.31.2024

The legal field governing activities on the world's oceans is called admiralty law (or maritime law). It covers navigation, commerce, marine pollution, fisheries, territorial waters, and jurisdiction over ships. The name comes from the "Admiral," originally an Arabic term (amir al-bahr, "commander of the sea") that entered European languages during the Crusades. In England, admiralty courts operated separately from common law courts until the 19th century.

No reactions yet
March 2024
posted on 03.25.2024

"Lacrimosa" is Latin for "weeping" or "tearful." In music, it refers to a specific section of the Dies Irae sequence in the Catholic Requiem Mass. Mozart's version, from his Requiem in D minor (K. 626), is the most famous setting. It's the last section Mozart worked on before his death in December 1791; the rest of the Requiem was completed by his student Franz Xaver Sussmayr. There's a bitter symmetry to it: the movement about weeping was the last thing Mozart ever composed. His pen stopped in the middle of the Lacrimosa, eight bars in.

No reactions yet
March 2024
posted on 03.22.2024

The Litany Against Fear is a fictional incantation from Frank Herbert's Dune, published in 1965. Characters in the novel use it as a mantra to calm themselves and push through terror. It's spoken by the Bene Gesserit, the secretive order that trains its members in mental and physical discipline.

The full text:

I must not fear. Fear is the mind-killer. Fear is the little-death that brings total obliteration. I will face my fear. I will permit it to pass over me and through me. And when it has gone past I will turn the inner eye to see its path. Where the fear has gone there will be nothing. Only I will remain.

No reactions yet
March 2024
posted on 03.13.2024

Descartes' "I think, therefore I am" comes from his 1637 work Discourse on the Method. The idea is that the act of doubting or thinking is itself proof of one's existence. It's the starting point of his entire philosophical system: the one thing that can't be doubted is that there's a mind doing the doubting.

A commonly missed detail: Descartes wrote it in French, not Latin. The original line is "je pense, donc je suis." The famous Latin version, "Cogito, ergo sum," came from a later translation. The Discourse was written in French precisely because Descartes wanted it to be accessible beyond the Latin-reading academic world.

No reactions yet
March 2024
posted on 03.10.2024

Indila is a French singer-songwriter whose music blends pop and R&B with North African and Middle Eastern influences. Her songs use instruments like the oud and accordion alongside electronic beats. Her 2014 debut album Mini World produced the hit "Derniere Danse," which charted in over 20 countries. She's notably private and rarely does interviews or public appearances.

No reactions yet
March 2024
posted on 03.08.2024

I was looking up the name after seeing it attached to a film character, probably Mildred Pierce, and wanted to know what the name carried before Hollywood got to it.

The name Mildred comes from Old English elements: milde ("gentle, mild") and thryth ("strength, power"). So it literally means "gentle strength" or "gentle ruler." It was a popular medieval English name that went through several revivals, most recently in the early 20th century.

No reactions yet
February 2024
posted on 02.07.2024

In Turkish, "Aga" historically referred to a feudal lord or person of authority, but it doesn't mean "king." The Turkish word for king is "kral" (borrowed from Slavic languages). "Aga" functioned more like "sir" or "lord" and was used as a title of respect, especially in the Ottoman period. The Aga Khan title uses the same root but in a different cultural context.

No reactions yet
September 2023
posted on 09.19.2023

"Ein Sof" (Hebrew: אין סוף) is a Kabbalistic term meaning "The Infinite," "The Endless," or "The Boundless." It refers to the unknowable, transcendent aspect of God that exists beyond any human description or comprehension. In Kabbalistic cosmology, Ein Sof is the source from which all creation emanates, but it itself is wholly beyond form, attribute, or limitation.

No reactions yet
June 2023
posted on 06.12.2023

Paul Morphy, the 19th-century American chess prodigy, didn't follow a rigid opening repertoire but frequently played the Italian Game. He preferred open, tactical positions that let him develop pieces quickly and attack. He also played the Evans Gambit and the King's Gambit. Morphy's approach was ahead of his time: rapid development, open lines, and king-side attacks. He dominated European masters during a brief tour in 1858-59, then essentially retired from chess at 22. Bobby Fischer called him "perhaps the most accurate player who ever lived."

The Italian Game in algebraic notation:

1. e4   e5
2. Nf3  Nc6
3. Bc4
No reactions yet
April 2023
posted on 04.11.2023

The word "baptism" comes from Greek baptizo (baptizo), meaning "to dip," "to immerse," or "to submerge." Before its religious use, the word was used for ordinary acts of dipping or washing, like dyeing cloth. The religious sense of ritual purification by immersion in water was a later development.

No reactions yet
April 2023
posted on 04.10.2023

The Hebrew word for heaven is shamayim (shamaiyim). It's a dual-form noun, which is unusual. It refers both to the physical sky and to the spiritual realm where God dwells. In Jewish tradition, there are seven levels of heaven, each with distinct characteristics. The word appears in the very first verse of the Torah: Bereshit bara Elohim et ha-shamayim ve'et ha-aretz ("In the beginning God created the heavens and the earth").

No reactions yet
April 2023
posted on 04.10.2023

The name Mary comes from Hebrew Miriam (Miryam), derived from the root marah, meaning "bitterness" or "rebelliousness." In the Hebrew Bible, Miriam is Moses and Aaron's older sister. The same name was later carried by Mary the mother of Jesus. It's one of the most common names in Western history, and most people who bear it have no idea it means "bitter."

No reactions yet
April 2023
posted on 04.09.2023

The Hebrew word "tehillim" (תְּהִלִּים) is the title of the biblical Book of Psalms. It means "praises" or "songs of praise." Not all of the 150 psalms are praise poems; many are laments, confessions, or cries for help. But the overall collection is categorized under the umbrella of praise, which is why the Hebrew title stuck.

No reactions yet
March 2023
posted on 03.09.2023

The English word "cure" comes from the Latin "cura," which meant "care, concern, attention." In its earliest English usage, "cure" referred to the care and attention given to a person, not a specific remedy for illness. Over time the meaning narrowed to mean treatment or remedy. The older sense survives in phrases like "cure of souls," where a parish priest has the "cure" (care) of his congregation.

No reactions yet
March 2023
posted on 03.04.2023

I was naming something, a chapter or a project, and "chronomancy" was a candidate. I ended up not liking it. But I kept the etymology.

"Chronomancy" combines "chrono-" (time) and "-mancy" (divination or magic). It means predicting the future through the interpretation of time and natural cycles. The "-mancy" suffix shows up all over the place: necromancy (the dead), pyromancy (fire), bibliomancy (books). Chronomancy is one of the rarer ones, but it follows the same Greek construction.

No reactions yet
February 2023
posted on 02.13.2023

The word "lord" comes from Old English hlafweard, which literally meant "loaf-keeper" or "bread-guardian." The master of a household was defined by his role as the one who provided bread. The related Old High German form is hlaf-wart. Over time the pronunciation collapsed from hlafweard to lord, but the original meaning tells you exactly what medieval authority was built on: whoever controlled the food.

No reactions yet
January 2023
posted on 01.07.2023

I was studying Camus broadly at the time, collecting his quotes and trying to describe him in one sentence. The Rebel came up as the most divisive thing he wrote.

Albert Camus's famous line "The only way to deal with an unfree world is to become so absolutely free that your very existence is an act of rebellion" comes from his 1951 essay The Rebel (L'Homme revolte). He was 37 when it was published. The book caused a bitter falling-out with Jean-Paul Sartre, who disagreed with Camus's rejection of revolutionary violence. Their friendship never recovered.

No reactions yet

About TIL

Short notes on things I learn day to day across disciplines. Mathematics, programming, literature, theology, film, languages. Inspired by Simon Willison's TIL. Currently 169 entries and counting.


2026.10.01 09:00:22 in Naperville, IL
Last visitor from Mitaka, Japan
Theme
Color theme
⌘K