Sign in

Karl Horky

@karlhorky.com
2.8K followers 2.4K following 479 posts

Technical Founder, Curriculum Engineer @upleveled.io Org Team React Amsterdam, AmsterdamJS, @reactvienna.com Canadian, Austrian, he/him 📍Amsterdam github.com/karlhorky

PostsRepliesMedia
Karl Horky @karlhorky.com · 07/10/2026
Had a great time delivering my Full Stack Guardrails for AI Agents masterclass at The Geek Gathering 2026 (Osijek, Croatia) 🙌 Topics - ways AI can fail in software development - deterministic guardrails to help AI make fewer mistakes - exercise to build app feature with guardrails
Karl Horky delivering the Full Stack Guardrails for AI Agents masterclass at The Geek Gathering, standing behind a laptop with the event banner and workshop agenda projected behind him, with participants and laptops visible in the foregroundDiagram describing a Harness, showing how Guardrails can Prevent (permissions, sandboxing, package install rules) and Detect (ESLint, TypeScript, SafeQL, mdx-tsc, tests, CI), while Guidance can Steer (AGENTS.md, skills, prompts) and Review (AI code review)Presentation slide showing lint warnings caught in a React component, including a floating promise detected by typescript-eslint, a fetch inside useEffect warning and a missing AbortController warningVS Code screenshot showing SafeQL catching an invalid SQL query in app/database/animals.ts: column animals.fur_color does not exist, with the error highlighted directly in the query
360
Karl Horky @karlhorky.com · 05/10/2026
@lirantal.com @notwes.bsky.social circling back around to this after a while in the AI agentic era, there have been new tools that also follow a similar pattern to using TS as a security tool if you ask your agent, i'm sure you'll find a lot more precedent of this eg github.com/rauchg/gdp-ts
Middleware only authenticates. The check happens in the route body, right where its result is needed, and disablePasswordProtection does not compile without it.
120
Karl Horky @karlhorky.com · 26/08/2026
introducing the `code-comments` skill: clearer, more deterministic code comments why: AI mostly writes in horrible, unclear, twisted ways (esp Claude) and that slop is saved in your codebase code comments, w. human + AI comprehension costs npx skills add karlhorky/agent-skills
https://github.com/karlhorky/agent-skills/pull/2

Add code-comments skill

karlhorky

3 hours ago

Comments written by AI agents often:

lead with the mechanism rather than the reason (the "what" instead of the "why")
bury the failure being avoided ("bury the lede")
use vague nouns or identifiers from another layer
repeat column or field names instead of describing stored values
The code-comments skill defines three forms:

an action opening with an imperative verb
a fact opening with the system or entity it describes
a column or field description written as a noun phrase
Actions use <verb> <noun> <reason>, except when the action and nearby code make the reason clear. Comments put affected records or entities near the start for faster debugging and use identifiers from the surrounding code so they remain searchable.

The skill also covers failure-first comments, matching TSDoc and CREATE TABLE descriptions, comment prefixes, and long background comments.

 Add action, https://github.com/karlhorky/agent-skills/blob/main/.agents/skills/code-comments/SKILL.md

Code Comments
Overview
Write comments for what the code does not express, usually a reason or behavior. Most comments take one of three forms:

Action: start with an imperative verb
Fact: omit the action and state a reason or behavior with its subject first
Description: use a noun phrase for what a database field or TypeScript property stores
Write actions as <verb> <noun> <reason>. Common words before an explicit reason include because, so, so that, to and for.

Exceptions:

Omit <reason> only when the action and nearby code make it clear
Use one sentence with no trailing period, wrapping it across lines when needed. Use multiple paragraphs only when the comment requires background.

Announce at start: "I'm using the code comments skill."

Examples of Code Comments
Start actions with an imperative verb
Example 1: omit a reason clear from nearby code
Good:

-- Sort optional lectures after other rPut debugging details first for scannability
Comments are often read while debugging, so two things are critical for speed of scanning:

1. The first word of the comment
2. The words near the start of the comment
Start actions with a verb followed by affected records or entities. Start facts with affected records or entities. Put generic conditions and reasons afterward.

Example 1: action verb directly followed by affected records
Good:

-- Exclude Tech Fundamentals Foundations (Immersive) cohorts, which
-- never had graduation events, so their certificates have no end
-- date to print
Example 2 (bad): put a generic condition first
Bad:

-- Return no certificate for Immersive cohorts with no graduation event,
-- because there is no end date to print - currently only Tech
-- Fundamentals Foundations (Immersive) cohorts
Why bad:

- Names the affected cohorts on the last line
- Starts with a generic condition that does not identify the affected cohortsName failures with Avoid or Prevent
Start workaround, guard and fallback comments with Avoid or Prevent. Follow the action form above, naming the failure before the reason (<Avoid/Prevent> <failure> <reason>). Common words before the reason include by and with.

Example 3: name the failure before the reason
Good:

-- Avoid signup failure when the browser reports a time zone missing
-- from time_zones by falling back to 'Etc/UTC'
Example 4 (bad): start with the fallback
Bad:

-- Fall back to 'Etc/UTC'
Why bad:

Starts with the fallback instead of the failure
Omits the missing time zone reason
230
Karl Horky @karlhorky.com · 19/08/2026
still trying to get Claude Code (Opus 5, but all Anthropic models are like this) to be more grounded in reality and less persuasive / rhetoric-heavy the responses are by default so unstructured and void of proof / verification push a little bit against the rhetoric and "You're absolutely right!"
https://github.com/karlhorky/dotfiles/blob/393f4d4149db43af38f04210d9d80a0de1c88cb4/settings/.codex/agents.md?plain=1#L49-L56

# Reasoning

- avoid rhetoric in your reasoning: report like a lab notebook, not an argument
  - show the command / code and its output for every factual claim
  - tables or other illustrative instruments which summarize data are nice, but they never replace the data behind them - show both the summary and the cited data
  - when you haven't verified something, mention:
    - you haven't verified yet
    - you're guessing / inferring
    - the next steps for verifying
  - drop persuasive language in headlines and body text: `Verdict:`, `Why X is wrong`, conclusions with no accompanying data
131
Karl Horky @karlhorky.com · 12/08/2026
ahh @github.com is removing "Close" notifications?? 😬 notifications are already bad enough, making them worse? submit your feedback here: github.com/orgs/communi...
August 10, 2026 • 1 minute read
Custom thread subscriptions are being deprecated

You’ll no longer be able to configure custom thread subscriptions for GitHub notifications.
300
Karl Horky @karlhorky.com · 12/08/2026
new proof of concept script: diagnostics from Next.js TypeScript language service plugin on the command line 😍 (also in CI) like `astro check` or `vue-tsc` or `mdx-tsc`
https://github.com/karlhorky/next-js-tricks#check-nextjs-typescript-plugin-diagnostics-in-terminal

Check Next.js TypeScript Plugin Diagnostics in Terminal
Next.js includes a custom TypeScript plugin for diagnostics such as missing types on metadata exports. However, the plugin diagnostics are not reported by tsc or a next CLI subcommand.

The TypeScript team have up until now (August 2026) rejected the feature request that tsc execute TypeScript language-service plugins:

microsoft/TypeScript#16607
To report the Next.js plugin diagnostics in the terminal and CI, install @typescript-eslint/project-service:

pnpm add --save-dev @typescript-eslint/project-service
Make sure the existing tsconfig.json enables the Next.js plugin:

{
  "compilerOptions": {
    "plugins": [{ "name": "next" }]
  }
}
Add the following script:

scripts/next-tsc.ts

...VS Code showing the Next.js metadata TS71008 warning and next-tsc exit code 1GitHub Actions failing on the Next.js metadata TS71008 warning from next-tsc
220
Karl Horky @karlhorky.com · 10/08/2026
another of my weird code review habits check PR changes (even for your PRs) - if not self-explanatory in PR context, make line comments on what/why benefits: 1. finds many logic errors + overengineering 2. returning to PRs later offers extra context both for AI and people
playwright/leadSeesMatchingDesignAndContent.spec.ts

```diff
// after Reach UI Tabs hides some images
// - https://github.com/microsoft/playwright/issues/31737#issuecomment-2233775909
-  await expect(lazyImagesLocator).toHaveCount(23);
+  await expect(lazyImagesLocator).toHaveCount(26);
```

@karlhorky
add 3 lazy-loaded SVG <Image /> (next/image) on homepagepackages/database/tables/0150-quizzes.ts

```diff
-  typeId: QuizType['id'];
+  curriculumVersionId: CurriculumVersion['id'];
+  quizTypeId: QuizType['id'];
```

@karlhorky

switch from `type_id` to `quiz_type_id` to match existing foreign key naming patterns
110
Karl Horky @karlhorky.com · 07/08/2026
that kind of day ... where a React key warning in your app leads you to discover a bug in React 🤯 github.com/react/react/...
https://github.com/react/react/issues/37240

Bug: False-positive missing key warning when Flight outlines a static child

 #37240
Open
Description
@karlhorky
karlhorky
opened 5m ago
Contributor
React version: 19.2.8

Steps To Reproduce
Open /repro and observe the Each child in a list should have a unique "key" prop. error message in the browser console.
Compare with /control, which reduces the preceding serialized prop from 4000 to 2500 characters.
020
Karl Horky @karlhorky.com · 07/08/2026
Even with all of these new code review tools lately, we still have diffs like this on GitHub (even SemanticDiff can't ignore the {' '} diff) Wonder if these belong in Git as semantic diff drivers per language/framework 👀 so diffs everywhere can ignore these 🤔
-                      {formField.label} {formField.required && <Asterisk />}
+                        {formField.label}{' '}
+                        {formField.required && <Asterisk />}-                            <option key={`option-${option}`} value={option}>
+                              <option
+                                key={`option-${option}`}
+                                value={option}
+                              >
450
Karl Horky @karlhorky.com · 05/08/2026
Naming is hard So try to avoid naming things as much as possible - use existing names, from the language, framework, external APIs, etc One of my latest additions to AGENTS . md github.com/karlhorky/do...
https://github.com/karlhorky/dotfiles/blob/main/settings/.codex/agents.md

- simplicity and transparency of values and program flow over abstraction and multiple levels of indirection
- naming
  - avoid inventing project-specific DSLs, aliases, or terminology when the underlying languages (eg. HTML, TS, SQL, etc), frameworks, protocols, external APIs or existing project-specific DSLs already name the concept
    - eg. instead align application APIs and props with underlying data (entities, values, elements, external names)
      - eg. expose `className`, HTML attributes, and Google/GitHub/Mailchimp/etc. field names
- prefer the Principle of Least Surprise over cleverness when choosing value shapes, ids, naming, and control flow
000
Karl Horky @karlhorky.com · 30/07/2026
Lightweight AI planning (avoiding a "plan" or "spec" which easily gets out of date): Start PRs with an empty commit + include all of the planned tasks in PR title and description (using checkboxes) Then keep this up to date and check off the points as you / AI implement github.com/karlhorky/do...
https://github.com/karlhorky/dotfiles/blob/main/settings/.codex/agents.md?plain=1#L4-L12

# GitHub

- opening PRs
  - always make user review title and description before starting work
    - as usual: create a branch
    - create an empty commit with a title with the prefix `Add initial empty commit`
    - as usual: push the branch to GitHub
    - propose 3 possible PR titles along with 1 possible PR description, following your PR title and description rules
    - ask the user for approval
    - after any iteration and approval, create a PR with the approved title and description
    - once you're ready to commit the first time, amend the empty commit and force pushhttps://github.com/upleveled/youtube-private-invitations/pull/2

Example PR title and description:

Add endpoint sync with OpenAPI contract #2

Depends on PR #1

Manual sync in PR #1 lets channel owners paste add/remove invitees in YouTube Studio, so it works without a backend. Some teams already decide private-video invite changes outside the extension (eg. cohort tools, admin dashboards, CRMs), and need the extension to fetch those pending changes without background polling.

This PR adds endpoint sync on top of the manual YouTube Studio automation. The options page stores API request settings, and the popup fetches pending invitations only when the user opens it or refreshes it.

Before changing YouTube private-video invitees, the popup shows one pending invitation change:

affected videos
invitees to add
invitees to remove
previous/next controls
an Apply button
When the user clicks Apply, the extension opens the first pending video's YouTube Studio edit page for the native private-share seed save, applies the same invitee changes to the affected videos, and reports each result back to the endpoint.

TODO

- [ ] Add options page API request settings textarea
- [ ] Parse .http-style endpoint URL and request headers
- [ ] Fetch pending invitations from GET <endpoint> only from popup open or refresh
- [ ] Add popup pending-change preview, navigation, refresh, and Apply action
- [ ] Show add/remove invitees and compact affected video links before applying
- [ ] Collapse affected video links after 5 videos
- [ ] Open the first pending video's YouTube Studio edit page for the native seed save
- [ ] Detect ytcp-error-section.app-load for wrong active channel or inaccessible videos
- [ ] Ask the user to switch YouTube Studio channels manually when YouTube Studio blocks access
- [ ] Reuse YouTube Studio automation for add and remove actions
- [ ] POST each invitation result to <endpoint>
- [ ] Keep failed invitations pending by endpoint behavior
- [ ] Always send reque...
231
Karl Horky @karlhorky.com · 17/06/2026
Security: VS Code 1.125 finally has a delay for auto-updates of extensions 👍 // Delay extension updates for 7 days "extensions.autoUpdateDelay": 168, Prompted by the recent security breaches and over 300 upvotes on my feature suggestion code.visualstudio.com/updates/v1_1...
VS Code release notes blog post showing a new setting:

Configurable extension auto-update delay
Setting: 
extensions.autoUpdateDelay
This setting is managed at the organization level. Contact your administrator to change it.ORG
To give you more control over when extension updates are installed, you can now configure a delay for automatic extension updates. This builds on the delayed extension auto-updates feature introduced in the previous release.

Use the 
extensions.autoUpdateDelay
This setting is managed at the organization level. Contact your administrator to change it.ORG setting to configure the delay in hours. By default, VS Code waits two hours before installing extension updates. The delay only applies when auto-update is enabled.issue on GitHub:

"Security: minimumReleaseAge setting for mitigating supply chain attacks on extensions" by @karlhorky

which received 334 upvotes
111
Karl Horky @karlhorky.com · 17/06/2026
Had a great time MCing at @jsnation.gitnation.org 2026 in Amsterdam! Great to see all the friends and make new connections Thanks to the organizers at @gitnation.bsky.social , and everyone else who helped organize, spoke and otherwise joined the event! #JSNation
Hall full of people at JSNation 2026 in AmsterdamKarl Horky MCing on stage at JSNation 2026 in AmsterdamKarl Horky asking questions to Erick Wendel on stage at JSNation 2026 in Amsterdam
100
Karl Horky @karlhorky.com · 02/06/2026
Looking forward to MCing at JSNation Conf 2026 in Amsterdam next week 🚀 As a preview, here's multiple badly-generated AI photos of me on stage, which don't look like me at all hint: I like talking about AI slop / AI fails ✨ and how we can do better See you all there!
bad AI-generated photo of Karl, wrong eyes, wrong facebad AI-generated photos of Karl on stage as a giant, pointing down at peoplebad AI-generated photos of Karl on stage as a giant, sitting in the air on nothing, with spotlights shining down on him
120
Karl Horky @karlhorky.com · 17/05/2026
VS Code extension Security: I proposed a setting in VS Code to limit extension installs to versions older than X days This is to mitigate supply chain attacks, similar to pnpm's minimumReleaseAge github.com/microsoft/vs...
Security: minimumReleaseAge setting for mitigating supply chain attacks on extensions #316867

In the last years, supply chain attacks have increased dramatically. A few examples in the VS Code extension ecosystem:

- AI-Slop ransomware test sneaks on to VS Code marketplace - BleepingComputer
- Malicious VS Code AI Extensions With 1.5M Installs Found Exfiltrating Code - The Hacker News
- Fake Clawdbot VS Code Extension Installs ScreenConnect RAT - Aikido
- Solidity Devs Targeted Again: Malicious VS Code Extension Drops ScreenConnect RAT - Checkmarx
- VS Code extensions contain trojan-laden image - ReversingLabs
- Malicious VSCode extensions on Microsoft's registry drop infostealers - BleepingComputer
- Malicious VSCode extensions infect Windows with cryptominers - BleepingComputer
- Nx Compromised to Steal Wallets and Credentials - Semgrep
- Supply Chain Risk in VS Code Extension Marketplaces - Wiz

Allow for minimumReleaseAge-style...
1291
Karl Horky @karlhorky.com · 16/04/2026
VS Code: I suggested an "edit mode" for the integrated browser, for WYSWYG-style workflows of changing text copy If you also want this: it is a backlog candidate now, needs 20 👍 reactions to move it to backlog github.com/microsoft/vs...
Integrated browser "edit mode": use inline text edits as a signal for Copilot edits #310710

karlhorky
opened 5 hours ago · edited by karlhorky
When performing marketing copy edits, it is often faster to just change text copy directly in the integrated browser than to describe the change in chat and wait for Copilot to make the change and the dev server to update the integrated browser (especially true with slower dev servers).

In the integrated browser, introduce a mode where DOM elements/nodes containing text are temporarily editable (eg. the behavior of contenteditable attribute). The user can then edit text inline and click on a "Save" button, upon which Copilot should use the diff of the edited text (plus nearby DOM context and innerHTML, as necessary) as a signal to propose source edits in the backing tsx / jsx / html / svelte / vue / etc file.

This proposal is NOT about persisting DOM edits deterministically to files. The browser would only be the interaction surface, while Copilot still generates normal source edits for review, based on the diff.

This could be extended to other non-text edits in future, such as image paths.

Alternatives considered
Full WYSIWYG editor, mapping changes to source files deterministically

cc @meganrogge @jruales @kycutler
140
Karl Horky @karlhorky.com · 13/01/2026
🔒Node.js v22.22.0, v24.13.0, v25.3.0 fix 3 high, 4 med, 1 low severity security vulnerabilities (+ dependencies) Upgrade now: Windows: choco upgrade nodejs # or nodejs-lts macOS: brew upgrade node # or node@24 Ubuntu: sudo apt-get --only-upgrade install nodejs
Security releases available

Updates are now available for the 25.x, 24.x, 22.x, 20.x Node.js release lines to address:

- 3 high severity issues.
- 4 medium severity issues.
- 1 low severity issue.

This security release includes the following dependency updates to address public vulnerabilities:

- c-ares (1.34.6) on 20.x, 22.x, 24.x, 25.x
- undici (6.23.0, 7.18.0) on 20.x, 22.x, 24.x, 25.x
220
Karl Horky @karlhorky.com · 29/11/2025
Petition in Germany recognizing open source as volunteering for the common good 🤩 Seems like a good step towards recognition and a healthier open source ecosystem www.openpetition.de/petition/onl...
openPetition

Recognition of Work on Open-Source as Volunteering in Germany
Henning Lammert
 Petition is addressed to
German Bundestag, Petition Committee
 3,256 Signatures

3,134 from 30,000 for quorum in Germany
0184
Karl Horky @karlhorky.com · 16/10/2025
npx-safe by @rafaelgss.dev : Use the Node.js permissions model to make running npx on untrusted packages safer 🔥 github.com/RafaelGSS/do...
alias npx-safe='function _npx_safe() {
  local node_opts="--permission --allow-fs-read=$(npm prefix -g) --allow-fs-read=$(npm config get cache)"
  local package=""
  local package_args=()
  while [[ $# -gt 0 ]]; do
    if [[ "$1" == --* ]]; then
      # Anything starting with `--` goes into node_opts
      node_opts+=" $1"
    else
      # The first non-`--` argument is the package; the rest are package args
      if [[ -z "$package" ]]; then
        package="$1"
      else
        package_args+=("$1")
      fi
    fi
    shift
  done
  echo "============================="
  echo "         npx-safe Log        "
  echo "============================="
  echo "Node.js options:"
  echo "  $node_opts"
  echo
  echo "Package:"
  echo "  $package"
  echo
  if [[ ${#package_args[@]} -gt 0 ]]; then
    echo "Arguments:"
    for arg in "${package_args[@]}"; do
      echo "  $arg"
    done
    echo
  fi
  echo "============================="
  npx --node-options="$node_opts" "$package" "${package_args[@]}"
}; _npx_safe'
010
Karl Horky @karlhorky.com · 25/09/2025
Why? Dependabot security alert appears and update generated -> fails because the update is for a pnpm transitive dependency with the error: Dependabot doesn't support the 'updating transitive dependencies' feature for pnpm package_manager
tar-fs has a symlink validation bypass if destination directory is predictable with a specific tarball #1

Error: Dependabot doesn't support the 'updating transitive dependencies' feature for pnpm package_manager

Dependabot attempted to update your dependencies but encountered an unsupported feature: 'updating transitive dependencies' for pnpm package_manager. Currently, this feature is not supported by Dependabot.
010
Karl Horky @karlhorky.com · 25/09/2025
My request for Dependabot: Full support for @pnpm.io (updates to transitive deps fail currently) Voice support in the issue 🙌 github.com/dependabot/d...
pnpm transitive dependency updates support #13177

Dependabot doesn't support transitive dependency updates for pnpm, a very popular package manager (31M downloads / week as of writing).
140
Karl Horky @karlhorky.com · 30/08/2025
Codemods for Node.js 😍 Looking great, thanks to all contributors!
npx codemod search scope:nodejs
Found 8 packages:

╭─────────────────────────────────────────┬──────────────┬──────────┬──────────────────╮
│ 📦 Name                                 │ 📊 Downloads │ ⭐ Stars │ 👤 Author        │
├─────────────────────────────────────────┼──────────────┼──────────┼──────────────────┤
│ @nodejs/fs-access-mode-constants        │ 8            │ 4        │ nekojanai (Jana) │
│ @nodejs/util-log-to-console-log         │ 8            │ 3        │ Bruno Rodrigues  │
│ @nodejs/process-main-module             │ 5            │ 4        │ Bruno Rodrigues  │
│ @nodejs/tmpDir-to-tmpdir                │ 4            │ 4        │ nekojanai (Jana) │
│ @nodejs/rmdir                           │ 7            │ 6        │ Augustin Mauroy  │
│ @nodejs/create-require-from-path        │ 5            │ 4        │ Augustin Mauroy  │
│ @nodejs/import-assertions-to-attributes │ 7            │ 5        │ Augustin Mauroy  │
│ @nodejs/correct-ts-specifiers           │ 0            │ 0        │ nodejs           │
╰─────────────────────────────────────────┴──────────────┴──────────┴──────────────────╯
1111
Karl Horky @karlhorky.com · 11/08/2025
@netlify.com multiple users reporting Netlify edge functions being down, in case you didn't know yet Maybe you can update the status page with the outage? answers.netlify.com/t/the-site-s...
My site https://fincaguarumo.com (hosted by Netlify via fincaguarumo.netlify.app, a next.js app) suddenly returns a 500 error.

In the logs, I can see only this:

Error handling request: TypeError: functions is not a function
at file:///root/src/bootstrap/server.ts:53:45
at mapped (ext:deno_http/00_serve.ts:407:24)
at mapped (ext:deno_http/00_serve.ts:513:16)
at ext:deno_http/00_serve.ts:729:29
at eventLoopTick (ext:core/01_core.js:178:7)
at async netlify:bootstrap-stage1:4:1

I have not changed anything recently, I do not have any custom functions. I have no idea how to debug this error as it seems to stem from Netlify itself and not something on my end.

Just to be sure, I triggered a manual deploy without cache, updated the @netlify/plugin-nextjs to 5.12.0, but to no avail.

The app uses next.js 15.

Is there anything else that can be done on my end?
110
Karl Horky @karlhorky.com · 09/08/2025
VS Code 1.103 (Jul 2025) finally has expandable hovers in JavaScript and TypeScript 😍 for when the hover info is showing the type name instead of the object / array / etc code.visualstudio.com/updates/v1_1...
3294
Karl Horky @karlhorky.com · 17/07/2025
Looks like `experimental.typedRoutes` is coming to Next.js Turbopack, thanks to Ben Gubler 🚀 🎉 github.com/vercel/next....
Screenshot of PR, showing that `experimental.typedRoutes` is no longer marked as "unsupported"
080
Karl Horky @karlhorky.com · 21/05/2025
My work in open source, from fixing papercuts to support students to discussing standards 🚀 Thanks so much to the Open Source Initiative @opensource.org for featuring me as a maintainer for Maintainer Month 2025! opensource.org/maintainers/...
Screenshot of Open Source Initiative blog post by Karl Horky:


Karl Horky: From Papercuts to Standards

Hi, I’m Karl Horky (GitHub, LinkedIn), Technical Founder at UpLeveled – tech education programs for all skill levels.

In an educational landscape of AI-generated solutions, disconnected islands of knowledge and barriers to entry, I focus on helping students level up by designing accessible curricula and contributing to open source.

Then and Now

I’ve been in open source for over 13 years, and in tech for more than 20, through which I have used a range of languages and technologies, from QBasic and C to...
1151
Karl Horky @karlhorky.com · 09/05/2025
AI-generated image alt text in HTML and Markdown in VS Code April 2025 (1.100) 😍 code.visualstudio.com/updates/v1_1...
Generate alt text in HTML or Markdown

You can now generate or update existing alt text in HTML and Markdown files. Navigate to any line containing an embedded image and trigger the quick fix via ⌘. or by selecting the lightbulb icon.

[Screenshot that shows generating alt text for an image html element.]
050
Karl Horky @karlhorky.com · 08/05/2025
Oh nice, looks like the 2019 idea I had to "skip parameters in function parameter lists" may come to life in @chronicles.org's proposal "void Discard Bindings for ECMAScript" 😍
Parameters

void discard bindings in parameter declarations help to avoid needing to give a name to parameters that might be unused by a callback or an overridden method of a subclass:

```
// project an array values into an array of indices
const indices = array.map((void, i) => i);

// passing a callback to `Map.prototype.forEach` that only cares about keys
map.forEach((void, key) => { });

// watching a specific known file for events
fs.watchFile(fileName, (void, kind) => { });
```Tweet from @karlhorky

ECMAScript spec people 👇

Is / was there a proposal to skip parameters in function parameter lists like this?

function mapper(, key) { /* use only the key */ }
110
Karl Horky @karlhorky.com · 24/03/2025
GritQL Biome plugins looking great 🔥 More options for simpler linting plugins 👍 ESLint `no-restricted-syntax` is almost there, but esquery can get pretty verbose...
Here is an example of a plugin that reports on all usages of Object.assign():

// ./path-to-plugin.grit

`$fn($args)` where {
    $fn <: `Object.assign`,
    register_diagnostic(
        span = $fn,
        message = "Prefer object spread instead of `Object.assign()`"
    )
}

output:

$ biome lint
/packages/tailwindcss-config-analyzer/src/introspect.ts:12:17 plugin ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

  ✖ Prefer object spread instead of `Object.assign()`

    10 │ function createContextFromConfig(config: Partial<Config>) {
    11 │        return createContext(
  > 12 │                resolveConfig(Object.assign({}, DEFAULT_CONFIG, config)),
       │                              ^^^^^^^^^^^^^
    13 │        );
    14 │ }
070
Karl Horky @karlhorky.com · 11/02/2025
`Cannot find matching keyid` error with latest pnpm? Upgrade to Node.js v22.14.0, which updates to the fixed Corepack 0.31.0 version: Windows: choco upgrade nodejs # or nodejs-lts macOS: brew upgrade node # or node@22 Ubuntu: sudo apt-get --only-upgrade install nodejs
Deploy log showing "Cannot find matching keyid" error:

3:29:43 PM: ! Corepack is about to download https://registry.npmjs.org/pnpm/-/pnpm-10.2.1.tgz
3:29:43 PM: /opt/buildhome/.nvm/versions/node/v22.13.1/lib/node_modules/corepack/dist/lib/corepack.cjs:21535
3:29:43 PM:   if (key == null || signature == null) throw new Error(`Cannot find matching keyid: ${JSON.stringify({ signatures, keys })}`);
3:29:43 PM:                                               ^
3:29:43 PM: Error: Cannot find matching keyid: {"signatures":[{"sig":"MEYCIQDkZyZZmBzkRcQowEEFiEcGp4/xV8GBLXxTEzz9QstrsAIhAPx6tvZixjTub6GPqJa82vcWFhUU39JCtoJvcoRK/K39","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"keys":[{"expires":null,"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","keytype":"ecdsa-sha2-nistp256","scheme":"ecdsa-sha2-nistp256","key":"MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE1Olb3zMAFFxXKHiIkQO5cJ3Yhl5i6UPp+IhuteBJbuHcA5UogKo0EWtlWwW6KSaKoTNEYL7JlCQiVnkhBktUgg=="}]}
3:29:43 PM:     at verifySignature (/opt/buildhome/.nvm/versions/node/v22.13.1/lib/node_modules/corepack/dist/lib/corepack.cjs:21535:47)
3:29:43 PM:     at installVersion (/opt/buildhome/.nvm/versions/node/v22.13.1/lib/node_modules/corepack/dist/lib/corepack.cjs:21882:7)
3:29:43 PM:     at process.processTicksAndRejections (node:internal/process/task_queues:105:5)
3:29:43 PM:     at async Engine.ensurePackageManager (/opt/buildhome/.nvm/versions/node/v22.13.1/lib/node_modules/corepack/dist/lib/corepack.cjs:22316:32)
3:29:43 PM:     at async Engine.executePackageManagerRequest (/opt/buildhome/.nvm/versions/node/v22.13.1/lib/node_modules/corepack/dist/lib/corepack.cjs:22416:25)
3:29:43 PM:     at async Object.runMain (/opt/buildhome/.nvm/versions/node/v22.13.1/lib/node_modules/corepack/dist/lib/corepack.cjs:23102:5)
3:29:43 PM: Node.js v22.13.1
2110
Karl Horky @karlhorky.com · 24/01/2025
Thanks for the great talk at React Amsterdam @mickey.studio 🎉 Great to see more about this topic in design systems creation: - Drawbacks of rigid, highly-coupled components - Patterns of fine-grained component factoring and composition to counter these drawbacks
Mickey presenting at React Amsterdam, with the slide listing out problems with rigid, highly-coupled components:

- Lacks context
- Complex to maintain
- Complex to modify
- High cognitive load
- Slow iterations
- Prop-drilling
- Lack of control
0111
Karl Horky @karlhorky.com · 13/01/2025
> if you use the demo code and print `.get('a[]')` you get the value, right? you get a string - that was the point there is no way to receive an array value, which was the vulnerability forked sandbox: codesandbox.io/p/devbox/pen...
Go to /?a=1&a=2
Go to /?a[]=1&a[]=2
Go to /?a=1,2

Search param for a: null
typeof search param for a: object

Search param for a[]: 1
typeof search param for a[]: string
100
Karl Horky @karlhorky.com · 13/01/2025
This is what we teach to students in the first lecture about TypeScript, that narrowing with runtime code is sometimes required
Narrowing 2/4

In circumstances where you are uncertain about the type of the data, typeof expressions can be used to check the type and also serve as a type guard, which narrows the type in TypeScript:

```ts
function log(id: string | number) {
  console.log(id.length); // Error: Property 'length' does not exist
                          // on type 'number'

  if (typeof id === 'string') { // Type narrowed to string
    console.log(id.length); // No errors!
  }
}
```

instanceof expressions also work.
110
Karl Horky @karlhorky.com · 06/01/2025
PostgreSQL: Ever wanted to insert test data with explicit `id`s into a table with an identity column eg. `GENERATED ALWAYS AS IDENTITY`? Added a new trick to PostgreSQL Tricks with a seeder script which achieves this (short version: detect + drop + re-add the identity) github.com/karlhorky/po...
Seed Test Fixture Data with Explicit ids to Generated Identity Fields
When adding data to a database for testing purposes, it's often useful to have explicit id values to reference records in other tables via foreign keys.

However, these explicit id values are incompatible with identity fields such as a field specified with id PRIMARY KEY GENERATED ALWAYS AS IDENTITY - inserting records with explicit id values will lead to cannot insert a non-DEFAULT value into column errors from PostgreSQL:

```
2025-01-06 12:49:32.107 UTC [17659] ERROR:  cannot insert a non-DEFAULT value into column "id"
2025-01-06 12:49:32.107 UTC [17659] DETAIL:  Column "id" is an identity column defined as GENERATED ALWAYS.
2025-01-06 12:49:32.107 UTC [17659] HINT:  Use OVERRIDING SYSTEM VALUE to override.
2025-01-06 12:49:32.107 UTC [17659] STATEMENT:
  INSERT INTO
    regions (id, slug, title)
  VALUES
    ($1, $2, $3),
    ($4, $5, $6)
  ON CONFLICT (id) DO UPDATE
  SET
    id = excluded.id,
    slug = excluded.slug,
    title = excluded.title
```To use explicit id values in test fixture data while using generated identity fields, drop the identity, insert the records and add the identity back.

The following example of this approach uses:

- Postgres.js

`seedFixtures.ts`

```
import { readdir } from 'node:fs/promises';
import postgres from 'postgres';

if (!process.env.FEATURE_TEST_SEEDING) {
  throw new Error('Set the environment variable FEATURE_TEST_SEEDING to seed database with test data');
}

const sql = postgres({
  transform: postgres.camel,
});

const testFixtures = (await readdir('./tables', { withFileTypes: true }))
  .filter((entry) => {
    return entry.isFile() && /^\d+-[^.]+\.fixture\.ts$/.test(entry.name);
  })
  .sort((a, b) => {
    return parseInt(a.name.split('-')[0]!) - parseInt(b.name.split('-')[0]!);
  });

// continues at link
```
190
Karl Horky @karlhorky.com · 06/12/2024
one last edge case where I can imagine TS has only partial errors: overlapping identically-named APIs between different types (Array.prototype.concat and String.prototype.concat) but more uncommon, and I guess could be caught by types in other parts of program or other tooling like linters
Code:

['a'].concat('b')
'c'.concat(['d'])

Error on ['d']:

Argument of type 'string[]' is not assignable to parameter of type 'string'.(2345)
110
Karl Horky @karlhorky.com · 06/12/2024
In case this is still not clear, here's a demo In this demo, tsc (with @types/node and @types/sanitize-html) will not allow building type-unsafe, insecure JS, because of the type error on line 25 (see tsc error in alt text, or just run `pnpm tsc` in the sandbox) codesandbox.io/p/devbox/l7w...
Code:

import { createServer } from "node:http";
import { parse } from "node:url";
import sanitize from "sanitize-html";

const port = 3000;

const server = createServer((request, response) => {
  if (!request.url) {
    response.statusCode = 400;
    response.end("Invalid request");
    return;
  }

  const url = parse(request.url, true);

  // Narrow the type in TS to string | string[]
  if (url.query.name === undefined) {
    response.statusCode = 400;
    response.end('Missing "name" query parameter');
    return;
  }

  // This will still throw a TypeScript error,
  // and prevent a build with tsc
  const sanitized = sanitize(url.query.name);
  // Error: Argument of type 'string | string[]' is not assignable to parameter of type 'string'.

  response.statusCode = 200;
  response.setHeader("Content-Type", "application/json");
  response.end(JSON.stringify({ message: "Query received", query: url.query }));
});

server.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

TypeScript error when `tsc` is run:

➜  workspace git:(master) ✗ pnpm tsc
index.ts:25:30 - error TS2345: Argument of type 'string | string[]' is not assignable to parameter of type 'string'.
  Type 'string[]' is not assignable to type 'string'.

25   const sanitized = sanitize(query.name); // Error: Argument of type 'string | string[]' is not assignable to parameter of type 'string'.
                                ~~~~~~~~~~


Found 1 error in index.ts:25
310
Karl Horky @karlhorky.com · 05/12/2024
Or in Next.js use, you can't even pass in an array without special handling - everything is strings (I think that's my favorite - secure by default, make the insecure thing harder) codesandbox.io/p/devbox/sto...
Browser view:

https://4fmkh6-3002.csb.app/?a=1&a=2

Search param for a: 1
typeof search param for a: string

Code: 

'use client';

import { useSearchParams } from "next/navigation";

export default function Home() {
  const searchParams = useSearchParams();
  console.log(searchParams.get('a'));
  return (
    <div>
      <a href="/?a=1&a=2">Go to /?a=1&a=2</a><br />
      <a href="/?a[]=1&a[]=2">Go to /?a[]=1&a[]=2</a><br />
      <a href="/?a=1,2">Go to /?a=1,2</a><br /><br />

      Search param for a: {String(searchParams.get('a'))}<br />
      typeof search param for a: {typeof searchParams.get('a')}
    </div>
  );
}
Browser view:

https://4fmkh6-3002.csb.app/?a[]=1&a[]=2

Search param for a: null
typeof search param for a: object

Code: 

'use client';

import { useSearchParams } from "next/navigation";

export default function Home() {
  const searchParams = useSearchParams();
  console.log(searchParams.get('a'));
  return (
    <div>
      <a href="/?a=1&a=2">Go to /?a=1&a=2</a><br />
      <a href="/?a[]=1&a[]=2">Go to /?a[]=1&a[]=2</a><br />
      <a href="/?a=1,2">Go to /?a=1,2</a><br /><br />

      Search param for a: {String(searchParams.get('a'))}<br />
      typeof search param for a: {typeof searchParams.get('a')}
    </div>
  );
}
Browser view:

https://4fmkh6-3002.csb.app/?a=1,2

Search param for a: 1,2
typeof search param for a: string

Code: 

'use client';

import { useSearchParams } from "next/navigation";

export default function Home() {
  const searchParams = useSearchParams();
  console.log(searchParams.get('a'));
  return (
    <div>
      <a href="/?a=1&a=2">Go to /?a=1&a=2</a><br />
      <a href="/?a[]=1&a[]=2">Go to /?a[]=1&a[]=2</a><br />
      <a href="/?a=1,2">Go to /?a=1,2</a><br /><br />

      Search param for a: {String(searchParams.get('a'))}<br />
      typeof search param for a: {typeof searchParams.get('a')}
    </div>
  );
}
120
Karl Horky @karlhorky.com · 05/12/2024
Yeah, I guess I'm used to TypeScript param types catching these things for me already, eg. Express query param types: www.typescriptlang.org/play/#code/J...
Code:

import {type Request} from '@types/express-serve-static-core'
import sanitizeHtml from 'sanitize-html';

const request = {} as Request

sanitizeHtml(request.query.abc!)


Error (on "request.query.abc"):

Argument of type 'string | ParsedQs | string[] | ParsedQs[]' is not assignable to parameter of type 'string'.
  Type 'ParsedQs' is not assignable to type 'string'.(2345)
const request: Request<ParamsDictionary, any, any, QueryString.ParsedQs, Record<string, any>>
110
Karl Horky @karlhorky.com · 05/12/2024
Ah interesting, and in the case of Dust, it seems like it was to avoid XSS vulnerabilities caused by missing encoding github.com/linkedin/dus...
I just stumbled upon this issue and I honestly couldn't understand why this happens. Let's say you have a template like

<ul>{~n}
{#section}
    <li>{name}</li>{~n}
{/section}
</ul>

and the data to render is

{
    "section" : [
        {
            "name" : "Test &amp; more test"
        },
        {
            "name" : ["Another test &amp; testing stuff"]
        }
    ]
}

If we render it like so, the first li will have the value Test &amp; more test, but the second one will render Another test & testing stuff. Why do the two behave differently? In my head they should behave the same way, but I can't for the life of me find what causes that difference.
120
Karl Horky @karlhorky.com · 28/11/2024
it's crazy how often a new typescript-eslint rule ends up teaching JavaScript and TypeScript fundamentals 😮 🚀 typescript-eslint.io/rules/return...
left pane (code):

async function invalidAlways1() {
  try {
    return Promise.resolve('try');
  } catch (e) {}
}

async function invalidAlways2() {
  return Promise.resolve('try');
}

async function invalidAlways3() {
  return await 'value';
}

reported errors:

@typescript-eslint/return-await - docs 
Returning an awaited promise is required in this context. 3:12 - 3:34
> Add `await` before the expression. Use caution as this may impact control flow.
Returning an awaited promise is required in this context. 8:10 - 8:32
Returning an awaited value that is not a promise is not allowed. 12:10 - 12:23return-await

Enforce consistent awaiting of returned promises.

This rule builds on top of the eslint/no-return-await rule. It expands upon the base rule to add support for optionally requiring return await in certain cases.

The extended rule is named return-await instead of no-return-await because the extended rule can enforce the positive or the negative. Additionally, while the core rule is now deprecated, the extended rule is still useful in many contexts:

- Returning an awaited promise improves stack trace information.
- When the return statement is in try...catch, awaiting the promise also allows the promise's rejection to be caught instead of leaving the error to the caller.
- Contrary to popular belief, return await promise; is at least as fast as directly returning the promise.
5101
Karl Horky @karlhorky.com · 26/11/2024
Playwright 1.49's new `.toMatchAriaSnapshot()` 😍 Nice and compact YAML syntax to test multiple elements in an accessibility tree 🎉 Thanks Pavel Feldman, @max.sh , @skn0tt.bsky.social , Dmitry Gozman and everyone else involved! playwright.dev/docs/release...
Aria snapshots

New assertion `expect(locator).toMatchAriaSnapshot()` verifies page structure by comparing to an expected accessibility tree, represented as YAML.

```ts
await page.goto('https://playwright.dev');
await expect(page.locator('body')).toMatchAriaSnapshot(`
  - banner:
    - heading /Playwright enables reliable/ [level=1]
    - link "Get started"
    - link "Star microsoft/playwright on GitHub"
  - main:
    - img "Browsers (Chromium, Firefox, WebKit)"
    - heading "Any browser • Any platform • One API"
`);
```

You can generate this assertion with Test Generator and update the expected snapshot with `--update-snapshots` command line flag.

Learn more in the aria snapshots guide.
2183
Karl Horky @karlhorky.com · 24/11/2024
nice! some before and after code shots from the video (with alt text)
Before:
@Component({
  standalone: true,
  selector: 'zippy',
  ...
})
class Zippy {}

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [Zippy],
  template: '<zippy />',
})
export class AppComponent {
  name = 'Minko';
}

bootstrapApplication(AppComponent);After:
@Component({ ... })
class Zippy {}

@Component({ template: '<Zippy/>' })
export class AppComponent {
  name = 'Minko';
}

bootstrapApplication (AppComponent);
010
Karl Horky @karlhorky.com · 17/11/2024
I like the overall idea behind privacy protections for users 👍 But cookie banners / similar are a bad technical implementation, imposing bad UX on millions of users This makes a noticeable impact on user frustration, not to mention Europe's productivity and economy legiscope.com/blog/hidden-...
For the most part, small businesses use cookies efficiently without precise user identification. Identifying users typically requires a court order to process IP addresses, which is rarely pursued. Therefore, cookie banners primarily serve to mitigate theoretical legal risks rather than enforce extensive user tracking.

It is not to say that some businesses do not use cookies to operate user tracking on a massive scale. Some companies relying exclusively on advertising do share user data with very large pools of partners—sometimes hundreds of ad partners. In that case, cookie banners do offer privacy protections for users.

However, looking at the general scale of the internet, only a very small fraction of websites use mass-scale partnerships as their main economic model.

For users, repeated interactions with cookie banners lead to significant frustration and complete loss of vigilance. The consent fatigue results in users mindlessly accepting terms without proper consideration, thereby undermining the very intent of the regulations. The constant barrage of consent prompts not only reduces productivity but diminishes user satisfaction and erodes trust in online platforms.

Conclusion

The realization that Europeans spend 575 million hours annually clicking on cookie banners highlights a significant, yet often overlooked, economic and productivity drain. These processes deliver minimal privacy benefits and little enhancement to business performance.
161
Karl Horky @karlhorky.com · 16/11/2024
React Scan by @aidenybai.bsky.social 🤩 latest iteration of tooling visualizing React re-renders, looks great!
3180
Karl Horky @karlhorky.com · 13/11/2024
@esft.bsky.social now that Bluesky is #1 app in the U.S. , recently with 700k followers in a week, what do you think of also posting over here? (or just leaving Twitter behind, like many are doing) Would be great to get your content over here too!
tweet by Eric Simons (@ericsimons40)

One of the best things about bolt is that you can tell it to use any framework, design system, component library/styles, etc and it just works ✨
130
Karl Horky @karlhorky.com · 11/11/2024
@tailwindcss.com @adamwathan.com thanks for `--spacing: 1px` in Tailwind CSS v4 🙌 still torn whether it's a good idea or not because consistency 🤔 but seems it could be the correct tradeoff for some projects!
In Tailwind CSS v4.0, this is all you'll have to do for `py-1` to mean 1px instead of 4px, `py-7` to mean 7px instead of 28px, etc.

@theme {
  --spacing: 1px;
}
130
Karl Horky @karlhorky.com · 11/11/2024
check out SafeQL - I guess this may just work out of the box with the client safeql.dev/guide/introd...
screenshot of SafeQL docs menu, showing:

Compatibility
- Prisma
- Postgres.js
- @vercel/postgres
- slonik
- node-postgres (pg)
- Sequelize
120
Karl Horky @karlhorky.com · 10/11/2024
Thanks for thinking of these types of security details when designing bcrypt for Bun @jarredsumner.com @bun.sh 👏 From screenshotted post: > Bun.password.hash’s “bcrypt” option automatically SHA512 hashes input longer than 72 bytes to prevent bcrypt from silently truncating passwords
Jarred Sumner: Bun.password.hash’s “bcrypt” option  automatically SHA512 hashes input longer than 72 bytes to prevent bcrypt from silently truncating passwords

(quote tweet of Okta security breach caused by bcrypt vulnerability)
010
Karl Horky @karlhorky.com · 17/09/2024
Bluesky now has over 10 million users, and I was #241,490!
000
Karl Horky @karlhorky.com · 20/05/2024
We recently migrated to ESLint v9 with Flat Config (somewhat painful migration) some things that we found out that may be helpful for you 👇
PR migrating to ESLint v9, showing 2692 lines added and 461 lines removed
111