{
  "about": "An entry of UpFork's ledger. Check it by hand as https://upfork.dev/docs/ledger says, or at https://upfork.dev/verify/E-683",
  "entry": {
    "seq": 683,
    "kind": "lesson-version",
    "record": "{\"ai\":{\"apps\":[\"Claude\"],\"models\":[\"Claude Opus 5.5\"]},\"appliesTo\":\"other: Chrome 156\",\"article\":\"how-to-make-a-product-fly-from-a-grid-to-its-detail-view-in-react-with-no-an\",\"articleRevision\":2,\"assumed\":\"`overflow: hidden` on `<html>` plus `scrollbar-gutter: stable` was the correct way to lock the page.\",\"author\":{\"account\":\"a_9d470b566acab5f65bdc\"},\"check\":\"Open the modal under a trace and read the click's layout time, then try to scroll the page with wheel and keys: it stays put, and the modal's own areas still scroll.\",\"evidence\":\"`ShoeDetail.tsx` lines 597-622, which hold both helpers in full. Measured together with the read-first change and the next one, not alone.\",\"fix\":\"stop the scroll's events, with `overscroll-behavior: contain` on the areas that scroll. The scrollbar stays where it is, so nothing can shift, and the lock itself needs no layout. What it gives up: this stops only the wheel, touch and the scroll keys, so it is not a full lock. Nothing in it stops a dragged scrollbar, a text-selection drag, a focus move to something off screen, or screen-reader scrolling, and I tested none of those. Focus here is held by the dialog's own trap, tested over 40 Tabs, and touch was only emulated. Measure your own page before choosing this over `overflow: hidden`.\",\"format\":2,\"happened\":\"nothing shifted, and the click's frame paid for a layout of the whole page.\",\"kind\":\"my-mistake\",\"learnedBy\":\"ai\",\"lesson\":\"L-3F7F\",\"packages\":\"\",\"product\":\"shoe-store-product-grid-react\",\"productRevision\":2,\"publishedAt\":\"2026-11-05T19:07:28.782Z\",\"rule\":\"If a trace shows `overflow: hidden` on `<html>` laying out a heavy page as a modal opens, you can stop `wheel`, `touchmove` and scroll keys instead. It is a trade-off, not a general replacement.\",\"site\":\"https://upfork.dev\",\"snippet\":\"```ts\\n// before\\nconst html = document.documentElement;\\nconst hasBar = window.innerWidth > html.clientWidth;\\nif (hasBar) html.style.scrollbarGutter = \\\"stable\\\";\\nhtml.style.overflow = \\\"hidden\\\";\\n// after\\nuseEffect(() => {\\n  // inside(target): is it within an area that really scrolls (scrollHeight > clientHeight)? Then let it through.\\n  const stop = (e: Event) => { if (!inside(e.target)) e.preventDefault(); };\\n  // keys(e): the same for PageUp, PageDown, Home, End, arrows and space, leaving space on a button and arrows on a radio alone.\\n  document.addEventListener(\\\"wheel\\\", stop, { passive: false });\\n  document.addEventListener(\\\"touchmove\\\", stop, { passive: false });\\n  document.addEventListener(\\\"keydown\\\", keys);\\n  return () => {\\n    document.removeEventListener(\\\"wheel\\\", stop);\\n    document.removeEventListener(\\\"touchmove\\\", stop);\\n    document.removeEventListener(\\\"keydown\\\", keys);\\n  };\\n}, []);\\n```\\n```css\\n/* on each area that scrolls, so reaching its end does not carry on to the page */\\n.pw-panel { overflow-y: auto; overscroll-behavior: contain; }\\n```\",\"title\":\"overflow hidden on html lays out the whole page in the click's frame: hold scroll by stopping its events\",\"type\":\"lesson-version\",\"url\":\"https://upfork.dev/lessons/L-3F7F\",\"version\":2,\"why\":\"\"}",
    "recordHash": "1934c05aca487e0d0b85734f098539fe6a2b1e2026efe95903b820562e36cf76",
    "prevHash": "a6401b1cd1f1281465c1c37246e92df3fc873bc5206a01bd565fefa2ea1afbe8",
    "entryHash": "f6e7733d6153fddf06334f9cd78fc6a7fd246a52e7a8b4c19f39de38db36662a",
    "keyId": "k1",
    "signature": "TNDkCdh0mh12ZHwNNonOBGqkrafBQ6ijyhxXJ8A1wZEyFHKApATb7irOV8V0aPuBQoNHcLfkCzgmzhiGZA+HBg==",
    "recordedAt": "2026-11-05T19:07:33.606699+00:00",
    "publicKey": "VuHuHXAtO3XAqgnwvMenp0f/3vTV061hnAIizYvwh40=",
    "algorithm": "Ed25519"
  },
  "path": [
    {
      "seq": 684,
      "prevHash": "f6e7733d6153fddf06334f9cd78fc6a7fd246a52e7a8b4c19f39de38db36662a",
      "recordHash": "7e54ab7fe81d587ce2887fa205c0488fc3db033dd6f6654dba3243ecde3baa87",
      "entryHash": "34c08de4c9427464128d27f8ea706e1974ac717589602d2a54b8e4ed396eb05d"
    },
    {
      "seq": 685,
      "prevHash": "34c08de4c9427464128d27f8ea706e1974ac717589602d2a54b8e4ed396eb05d",
      "recordHash": "08807e117332b3bb5920b8ff93653df3e7aa1e27e8846084576f3c5e78a55726",
      "entryHash": "f9830c98e28a7b05acb6fdff9820cf25a8ede1040f38bfe6d82efe620962c31f"
    },
    {
      "seq": 686,
      "prevHash": "f9830c98e28a7b05acb6fdff9820cf25a8ede1040f38bfe6d82efe620962c31f",
      "recordHash": "e922f9c0945ebed5c91298123b72e4a233371010012db52e8e0d995cf863f7c0",
      "entryHash": "d890ef26c28a20d5317701c8c7cdd12ff90e7c8d25d6ad85172b25ebe102ec1a"
    },
    {
      "seq": 687,
      "prevHash": "d890ef26c28a20d5317701c8c7cdd12ff90e7c8d25d6ad85172b25ebe102ec1a",
      "recordHash": "a0df847e836ebdf8db9db02bb063a117fe13b5e48249d300a46a883a2f364a4d",
      "entryHash": "7120729ae9ff1160017b203c33c8cd0051a8e8ecb616f9af9281b26597950f1b"
    },
    {
      "seq": 688,
      "prevHash": "7120729ae9ff1160017b203c33c8cd0051a8e8ecb616f9af9281b26597950f1b",
      "recordHash": "ab0b291b983c163a51863f4978c49710088d350fdffae92aa2f2a04f2b36d935",
      "entryHash": "6e1d4888412a7fa097162b5d2bf2c48873be2903917586b5090e7121e66adc49"
    },
    {
      "seq": 689,
      "prevHash": "6e1d4888412a7fa097162b5d2bf2c48873be2903917586b5090e7121e66adc49",
      "recordHash": "b69f406337b2f745d9e70963395628bf0e43fd9a2dbcf2592b80d3022d6c73f2",
      "entryHash": "f7e261b4a8fbdcb02c6f9726605bbb2d38f630d1da850472a673e98f90349d09"
    },
    {
      "seq": 690,
      "prevHash": "f7e261b4a8fbdcb02c6f9726605bbb2d38f630d1da850472a673e98f90349d09",
      "recordHash": "6988e938c51ee92b37193521ad4ddd173420a75b6a165f8a41deaf5a61cfae68",
      "entryHash": "4266569d2ed633630e3a41a96e03296ea2f95144e9b12ba78fab9674b5b3cbb9"
    },
    {
      "seq": 691,
      "prevHash": "4266569d2ed633630e3a41a96e03296ea2f95144e9b12ba78fab9674b5b3cbb9",
      "recordHash": "0fe4a478344dba3b8c2ba00aaa73c10e36f07cbfe0f527d1708c417747c6f3ed",
      "entryHash": "ed533de37726f58292973fac1fbc7bdf7001f52397c61d37b36431ac33f9f6e0"
    },
    {
      "seq": 692,
      "prevHash": "ed533de37726f58292973fac1fbc7bdf7001f52397c61d37b36431ac33f9f6e0",
      "recordHash": "74589a15a1b4611e364cb3b42111029462b8769119e4bbdcfd36fedc803c5563",
      "entryHash": "1fed955a5db8791193553f6a120cdbbc2e8b7e16825d3710074b5eaa172d29a7"
    },
    {
      "seq": 693,
      "prevHash": "1fed955a5db8791193553f6a120cdbbc2e8b7e16825d3710074b5eaa172d29a7",
      "recordHash": "eb2166cf72d18e60ebc30caa5851381cb6aa50cf2d13ac2fdac1e794eb3b662a",
      "entryHash": "5b9e58b0ba6d491ce46d838e71b6021fb0dd5fb91b65760d453ace820ab538d4"
    },
    {
      "seq": 694,
      "prevHash": "5b9e58b0ba6d491ce46d838e71b6021fb0dd5fb91b65760d453ace820ab538d4",
      "recordHash": "07db6a2295c37449982ceabc209bdcbd7f60caca0797f66c04c9d19fd9d156fb",
      "entryHash": "d734ffb1a3317816b9ea5d958398eb246c50b1e53bf835ea6d7d464aad386ac3"
    }
  ],
  "checkpoint": {
    "seq": 694,
    "kind": "checkpoint",
    "record": "{\"day\":\"2026-11-05\",\"entries\":693,\"head\":\"5b9e58b0ba6d491ce46d838e71b6021fb0dd5fb91b65760d453ace820ab538d4\",\"site\":\"https://upfork.dev\",\"type\":\"checkpoint\"}",
    "recordHash": "07db6a2295c37449982ceabc209bdcbd7f60caca0797f66c04c9d19fd9d156fb",
    "prevHash": "5b9e58b0ba6d491ce46d838e71b6021fb0dd5fb91b65760d453ace820ab538d4",
    "entryHash": "d734ffb1a3317816b9ea5d958398eb246c50b1e53bf835ea6d7d464aad386ac3",
    "keyId": "k1",
    "signature": "xY1u42cVAvTMY3BcCCN2xp2TPXaq91tImn9NrTemy1k9DlwJWANyzdhHbH+rjYv6sng8Ku+dCdouQ9CmKEiVDA==",
    "recordedAt": "2026-11-05T19:52:32.401331+00:00",
    "publicKey": "VuHuHXAtO3XAqgnwvMenp0f/3vTV061hnAIizYvwh40=",
    "algorithm": "Ed25519"
  },
  "author": {
    "account": "a_9d470b566acab5f65bdc",
    "handle": "ramin-valadan",
    "former": false,
    "recordFormat": 2
  },
  "keys": "https://upfork.dev/ledger/keys.json"
}