Today I Learned

Daily reflections on work and learning—what I did and what I learned.

20268

2026-08-23

Locking Down a Reusable Table Component with Types

What I learned deriving a sort-key union from a column array and passing a per-domain row type all the way down into compound component parts — and the variance rabbit hole that kept showing up along the way

  • Deriving a type from a value and the const type parameter are a set. Drop one and the other quietly becomes meaningless, with no error
  • You can't attach type parameters to a const declaration, so a generic context has to be created inside a function closure
2026-08-14

Lock the correctness criteria before the implementation

Writing the judgment criteria down before handing work to a code generation tool, demoting code, screens, and reviewer opinions to evidence, and stopping when confidence runs out

  • Restricting the source of correctness to people and approved specs removes the failure of mistaking the implementation for the spec
  • Moving the definition of done past "tests pass" removes any incentive to loosen assertions
2026-08-09

Tracing request responses and change notifications separately in a browser extension

A note on requesting writes through message-based RPC in separated extension contexts, then using change signals and query invalidation so other screens re-read the single source of truth

  • Chrome's message APIs are transport; request validation, routing, and error responses are defined by the application
  • Command responses and change notifications for other screens have different purposes and should stay separate
2026-08-07

What a middle layer passes through and what it guesses — proxies, input commit, image ratios

An infinite redirect caused by a rewrite passing an upstream 3xx straight through, Korean input committed before composition ended, and image ratios that were being guessed as 16:9 — what I learned auditing what middle layers pass through and what they assume

  • A rewrite is a proxy, not a redirect, so it hands the upstream's 3xx straight to the browser
  • Treat a Korean input bug as a commit-boundary problem first, not a value-normalization problem

20267

2026-07-31

Legacy Browser Compat Is More Than babel Alone — the Multi-Layer Contract of Syntax, Polyfill, Conditional Load, and Smoke

What I learned fixing a bug where an app dies wholesale on a browser more than five years old — when several features die at once, suspect hydration; separate syntax down-leveling from runtime polyfills; load polyfills only on old browsers via feature-detect; and make the smoke run real interactions

  • When several UI features die at once, first suspect a common root cause like hydration/bundle parsing rather than separate bugs
  • Syntax down-leveling (babel) and runtime APIs (polyfill) are separate layers — a transpiler rewrites syntax but does not create missing functions
2026-07-25

First-Paint Splash and Offline-First — Getting Instant UX While Keeping ISR

The inline-script technique for showing a native-app-like loading splash from the first paint while hiding it from bots, and how to make 'my writes reflect instantly' in offline-first with invalidate and a durable queue rather than staleTime

  • Reveal before first paint + hide from bots is inline blocking script injection — a UA gate breaks ISR into dynamic via headers()
  • In react-query, "my writes reflect instantly" comes not from lowering staleTime but from invalidateQueries after the write (invalidate ignores staleTime)
2026-07-24

When the LCP bottleneck is hydration, not the image — and restrained scroll motion

What I learned improving landing performance — when LCP is slow but the image is small, suspect render delay (hydration) and strip client JS; prefer entrance stagger reveal over flashy scroll motion; structured data must be 1:1 with the screen

  • When LCP is slow but TBT is 0 and the image is small, suspect element render delay (= hydration at the client boundary) and strip the client JS from that surface
  • A short viewport-entrance stagger reveal is often the universal answer over flashy scroll-dependent motion (scroll-jacking)
2026-07-23

Intermittently Broken Images — objectURL Race, WebP Fallback, Service Worker Shell

Three things I learned chasing an image bug that flickered in and out on every refresh — the objectURL lifetime race, preventing test hangs from browser-only libs, and scoping the service worker shell fallback to navigation requests

  • Render images the server provides by URL directly, do not wrap them in an objectURL blob — this eliminates the createObjectURL lifetime race at its source
  • When putting a browser-only (canvas) lib on a test path, block jsdom hangs with a synchronous capability gate before execution
2026-07-22

Moving an infinite-list feature without the code — clean-room contracts and keyset pagination

What I learned moving a virtualized infinite list + scroll-restoration feature to a different stack — the clean-room approach of writing down observable behavior as a contract rather than the implementation code, and offset vs keyset pagination, which decides how stable infinite scroll is

  • A migration doc records the contract of "how it behaves from the outside and what it guarantees on failure", not "which libraries we used"
  • Infinite-scroll stability comes mostly from the pagination method — offset duplicates/skips on insert/delete, keyset is value-based and stable
2026-07-20

What I Learned from Public Surface, Analytics Facade, and Motion Gates

TIL summarizing structured data and visible FAQ parity, auth-resolution-gated fetch, analytics event fan-out facade, and prefers-reduced-motion JS transition gate as general concepts

  • Structured data (FAQPage, etc.) must come from the same source as visible on-screen text
  • Enable protected APIs only after session loading finishes; do not decide anonymous/fetch from !user alone during loading
2026-07-14

General concepts I hit while tidying up my dev environment: shell quoting, monorepo publishing, CI runners, guard design

A TIL distilling shell word splitting, workspace publish rewriting, CI runner mismatches, and fail-open guards into general concepts encountered while maintaining tooling and infrastructure

  • A path held in a shell variable, if it contains spaces, splits into multiple arguments when used unquoted and the command silently breaks
  • Internal monorepo dependencies should be linked with the workspace protocol and published with a manager that rewrites them to real versions
2026-07-13

Starlight graph, SW cache, Playwright triage, ESLint guardrail

A TIL covering Canvas star nodes, Service Worker SWR and generation security, CI flaky classification, and design-system ESLint guardrails in one day

  • Stabilize Canvas node decoration with ID hash and always restore globalAlpha and shadowBlur right after drawing
  • For red Playwright CI, split flaky from deterministic failed using retry results first
2026-07-11

Directing a scroll-driven home-hero narrative

A day implementing a TIL→Knowledge promotion demo and a pipeline section with sticky scrolly, CSS keyframes, and view-timeline — no GSAP

  • sticky scrolly makes a scroll time axis as tall as the wrapper, and if phases derive from progress the JS stays thin
  • A hero live demo can build a loop with just a clip-path steps wipe + animation-delay
2026-07-10

Modal peek stack and a CSS-layer package

A day aligning peek UX and theme customization via document push inside a single dialog and @layer-based editor CSS splitting

  • gallery→peek is more continuous when you just swap documentId in the same shell rather than opening a fresh dialog
  • async resolve must block the (newId, oldContent) intermediate frame with a render-time reset
2026-07-09

Hierarchical document nav and viewer bundle splitting

A day tuning both portfolio-demo depth and bundle size at once, via a page push/peek navigator and a separate read-only viewer entry

  • A document graph is better separated from the flat editor via a page block + push/peek navigator contract
  • peek/embed can share DOM with the editor, but the import graph must differ for the viewer bundle to shrink
2026-07-08

Mobile pointer gestures and a server image proxy

A day spent stabilizing touch drag/resize and, in an unoptimized environment, cutting transferred bytes with an image proxy

  • Reworked the drag behavior so dragging by touch doesn't scroll the page along with it
  • Added a proxy so images ship only the size needed, not the whole original
2026-07-07

Making Nested UI Re-renders O(1) — read-model, external store, and render-count tests

In an immutable-tree editor, editing a single line redrew everything; here's how I first pinned the problem with a test, then cut row and container re-renders with a normalized projection and per-id subscriptions

  • An immutable tree patch gives ancestors a new ref too, so passing props down triggers O(depth) re-renders — a normalized entry plus per-id subscription can cut it to O(1)
  • The entry cache must be stabilized by output value-equality, not input ref, and the external store sync must run synchronously at the choke point right before setState
2026-07-06

Bundle Shared Vendor, Cross-Block Selection, Floating-UI Coordinates

webpack splitChunks traps, cross-block range operations in a hybrid editor, selectionchange·scroll anchor patterns, Context subscription separation

  • Using chunks:all with a fixed name on a catch-all vendor pulls dynamic imports up into a shared chunk.
  • For cross-block text operations, stateless derivation from getSelection + a single boundary split is safer than range state.
2026-07-05

Block-Editor Ordering, Collaboration, and Keyboard Contracts

General patterns: fractional ordering, optimistic rebase, IME defer, keyboard undo delegation, scoped popover, Jest ESM shim, and more

  • For sibling ordering, a fractional position key + neighbor anchor + tie-break is better for concurrent inserts than an integer index.
  • Optimistic UI can't rebase without an inverse patch, and the remote seq must be the merge authority.
2026-07-04

The Document Is the Host SoT; the Editor Is a Focused Guest

Explaining, for a first-time reader, the host/guest boundary in a block-document editor, React external-store snapshots, the 2-stack undo, and HTML5 drag and selection/copy

  • Document meaning is owned by host patches, and the rich-text engine is kept only as a guest for the focused block.
  • Using only a ms timestamp in a useSyncExternalStore snapshot drops re-renders on same-ms mutations — a monotonic revision is needed.