From 7047cc4f31ccb58e1ca7ea08410c49cb3da1f49c Mon Sep 17 00:00:00 2001 From: "patrick.lafrance" Date: Wed, 25 Feb 2026 13:47:44 -0500 Subject: [PATCH 1/5] chore: update code structure for better maintainability --- .syncpackrc.js | 1 - package.json | 10 +- pnpm-lock.yaml | 405 +++++++++++++------------------------------------ 3 files changed, 110 insertions(+), 306 deletions(-) diff --git a/.syncpackrc.js b/.syncpackrc.js index cbdb0df1..f437cb38 100644 --- a/.syncpackrc.js +++ b/.syncpackrc.js @@ -2,7 +2,6 @@ /** @type {import("syncpack").RcFile} */ export default { - "lintFormatting": false, "semverGroups": [ { // Only the version "0.0.2" of the dependency seems to work. diff --git a/package.json b/package.json index b1319b3e..9554d09e 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,8 @@ "list-outdated-deps": "pnpm outdated -r --format list !eslint !@eslint/js !logrocket-fuzzy-search-sanitizer", "update-outdated-deps": "pnpm run --sequential \"/^update-outdated-deps:.*/\"", "update-outdated-deps:update-version": "pnpm update -r --latest !eslint !@eslint/js !logrocket-fuzzy-search-sanitizer", - "update-outdated-deps:update-peers": "syncpack fix-mismatches" + "update-outdated-deps:update-peers": "syncpack fix", + "update-outdated-deps:fix-pkg-json": "eslint --fix --no-cache --no-ignore package.json **/package.json" }, "devDependencies": { "@changesets/changelog-github": "0.5.2", @@ -51,13 +52,14 @@ "pkg-pr-new": "0.0.63", "prettier": "3.8.1", "retypeapp": "3.12.0-preview2", - "syncpack": "13.0.4", + "syncpack": "14.0.0", "turbo": "2.8.3", "typescript": "5.9.3", "typescript-eslint": "8.55.0" }, "engines": { - "node": ">=24.0.0" + "node": ">=24.0.0", + "pnpm": ">=10" }, - "packageManager": "pnpm@10.20.0" + "packageManager": "pnpm@10.30.1" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9318e289..dcb03880 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -45,8 +45,8 @@ importers: specifier: 3.12.0-preview2 version: 3.12.0-preview2 syncpack: - specifier: 13.0.4 - version: 13.0.4(typescript@5.9.3) + specifier: 14.0.0 + version: 14.0.0 turbo: specifier: 2.8.3 version: 2.8.3 @@ -1023,24 +1023,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@ast-grep/napi-linux-arm64-musl@0.37.0': resolution: {integrity: sha512-LF9sAvYy6es/OdyJDO3RwkX3I82Vkfsng1sqUBcoWC1jVb1wX5YVzHtpQox9JrEhGl+bNp7FYxB4Qba9OdA5GA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@ast-grep/napi-linux-x64-gnu@0.37.0': resolution: {integrity: sha512-TViz5/klqre6aSmJzswEIjApnGjJzstG/SE8VDWsrftMBMYt2PTu3MeluZVwzSqDao8doT/P+6U11dU05UOgxw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@ast-grep/napi-linux-x64-musl@0.37.0': resolution: {integrity: sha512-/BcCH33S9E3ovOAEoxYngUNXgb+JLg991sdyiNP2bSoYd30a9RHrG7CYwW6fMgua3ijQ474eV6cq9yZO1bCpXg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@ast-grep/napi-win32-arm64-msvc@0.37.0': resolution: {integrity: sha512-TjQA4cFoIEW2bgjLkaL9yqT4XWuuLa5MCNd0VCDhGRDMNQ9+rhwi9eLOWRaap3xzT7g+nlbcEHL3AkVCD2+b3A==} @@ -1565,24 +1569,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@napi-rs/image-linux-arm64-musl@1.12.0': resolution: {integrity: sha512-1laZq0DdtuI6DBFkGRB0tSv/STo11qjGKfCVUndt3JbP4nidO4enRDjofBTu5ROFcxqSdIq/t7hYhlS+BeJRnw==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@napi-rs/image-linux-x64-gnu@1.12.0': resolution: {integrity: sha512-TaaITRqJXnRip1kV2o+M6dMFyQdKs8zlPHyxZU6sfbjyPfgtkliKXR6kxxug2MVCr7jXuAFdrJRCdfyAeK4MIQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/image-linux-x64-musl@1.12.0': resolution: {integrity: sha512-QdvbOh+lekT+gKpYhINOHd4ruEYQHzEVhNqswZ2V3mWH5tbXS9ypjzzfZ1UVeHNfce3NooZ+XsZVGITX/KyUwQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@napi-rs/image-wasm32-wasi@1.12.0': resolution: {integrity: sha512-tlLe2+4RdJfkT8DOGJ7KpUGYxlhvqaYCKuPoyO2x/LPnfF2kSKO9nLIdBhCNw7v61gfxIEpr2ccz01Mw8FeSjA==} @@ -2343,66 +2351,79 @@ packages: resolution: {integrity: sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.57.1': resolution: {integrity: sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.57.1': resolution: {integrity: sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.57.1': resolution: {integrity: sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.57.1': resolution: {integrity: sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-loong64-musl@4.57.1': resolution: {integrity: sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==} cpu: [loong64] os: [linux] + libc: [musl] '@rollup/rollup-linux-ppc64-gnu@4.57.1': resolution: {integrity: sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-musl@4.57.1': resolution: {integrity: sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==} cpu: [ppc64] os: [linux] + libc: [musl] '@rollup/rollup-linux-riscv64-gnu@4.57.1': resolution: {integrity: sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.57.1': resolution: {integrity: sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.57.1': resolution: {integrity: sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.57.1': resolution: {integrity: sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.57.1': resolution: {integrity: sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openbsd-x64@4.57.1': resolution: {integrity: sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==} @@ -2492,21 +2513,25 @@ packages: resolution: {integrity: sha512-R7CO1crkJQLIQpJQzf+6DMHjvcvH/VxsatS5CG897IIT2aAfBeQuQAO+ERJko/UwSZam2K8Rxjuopcu5A2jsTQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@rspack/binding-linux-arm64-musl@1.7.5': resolution: {integrity: sha512-moDVFD06ISZi+wCIjJLzQSr8WO8paViacSHk+rOKQxwKI96cPoC4JFkz0+ibT2uks4i2ecs4Op48orsoguiXxw==} cpu: [arm64] os: [linux] + libc: [musl] '@rspack/binding-linux-x64-gnu@1.7.5': resolution: {integrity: sha512-LGtdsdhtA5IxdMptj2NDVEbuZF4aqM99BVn3saHp92A4Fn20mW9UtQ+19PtaOFdbQBUN1GcP+cosrJ1wY56hOg==} cpu: [x64] os: [linux] + libc: [glibc] '@rspack/binding-linux-x64-musl@1.7.5': resolution: {integrity: sha512-V1HTvuj0XF/e4Xnixqf7FrxdCtTkYqn26EKwH7ExUFuVBh4SsLGr29EK5SOXBG0xdy5TSEUokMup7cuONPb3Hw==} cpu: [x64] os: [linux] + libc: [musl] '@rspack/binding-wasm32-wasi@1.7.5': resolution: {integrity: sha512-rGNHrk2QuLFfwOTib91skuLh2aMYeTP4lgM4zanDhtt95DLDlwioETFY7FzY1WmS+Z3qnEyrgQIRp8osy0NKTw==} @@ -2554,10 +2579,6 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@sindresorhus/merge-streams@2.3.0': - resolution: {integrity: sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==} - engines: {node: '>=18'} - '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -3198,18 +3219,10 @@ packages: resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} engines: {node: '>=18'} - chalk-template@1.1.2: - resolution: {integrity: sha512-2bxTP2yUH7AJj/VAXfcA+4IcWGdQ87HwBANLt5XxGTeomo8yG0y95N1um9i5StvhT/Bl0/2cARA5v1PpPXUxUA==} - engines: {node: '>=14.16'} - chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chalk@5.6.2: - resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - change-case@5.4.4: resolution: {integrity: sha512-HRQyTk2/YPEkt9TnUPbOpr64Uw3KOicFWPVBb+xiHvd6eBx/qPr9xqfBFDT8P2vWsvvz4jbEkfDe71W3VyNu2w==} @@ -3234,14 +3247,6 @@ packages: cjs-module-lexer@2.2.0: resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} - cli-cursor@5.0.0: - resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} - engines: {node: '>=18'} - - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - cli-width@4.1.0: resolution: {integrity: sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==} engines: {node: '>= 12'} @@ -3268,10 +3273,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - commander@13.1.0: - resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==} - engines: {node: '>=18'} - commander@7.2.0: resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} engines: {node: '>= 10'} @@ -3335,15 +3336,6 @@ packages: typescript: optional: true - cosmiconfig@9.0.0: - resolution: {integrity: sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==} - engines: {node: '>=14'} - peerDependencies: - typescript: '>=4.9.5' - peerDependenciesMeta: - typescript: - optional: true - cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -3533,9 +3525,6 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - effect@3.19.16: - resolution: {integrity: sha512-7+XC3vGrbAhCHd8LTFHvnZjRpZKZ8YHRZqJTkpNoxcJ2mCyNs2SwI+6VkV/ij8Y3YW7wfBN4EbU06/F5+m/wkQ==} - electron-to-chromium@1.5.286: resolution: {integrity: sha512-9tfDXhJ4RKFNerfjdCcZfufu49vg620741MNs26a9+bhLThdB+plgMeou98CAaHu/WATj2iHOOHTp1hWtABj2A==} @@ -3570,10 +3559,6 @@ packages: resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} engines: {node: '>=0.12'} - env-paths@2.2.1: - resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} - engines: {node: '>=6'} - error-ex@1.3.4: resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} @@ -3837,10 +3822,6 @@ packages: extendable-error@0.1.7: resolution: {integrity: sha512-UOiS2in6/Q0FK0R0q6UY9vYpQ21mr/Qn1KOnte7vsACuNJf514WvCCUHSRCPcgjPT2bAhNIJdlE6bVap1GKmeg==} - fast-check@3.23.2: - resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==} - engines: {node: '>=8.0.0'} - fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -4017,10 +3998,6 @@ packages: resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} engines: {node: '>=10'} - globby@14.1.0: - resolution: {integrity: sha512-0Ia46fDOaT7k4og1PDW4YbodWWr3scS2vAr2lTbsplOt2WkKp0vQbkI9wKis/T5LV/dqPjO3bpS/z6GTJB82LA==} - engines: {node: '>=18'} - google-logging-utils@0.0.2: resolution: {integrity: sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==} engines: {node: '>=14'} @@ -4087,10 +4064,6 @@ packages: hermes-parser@0.25.1: resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} - hosted-git-info@8.1.0: - resolution: {integrity: sha512-Rw/B2DNQaPBICNXEm8balFz9a6WpZrkCGpcWFpy7nCj+NyhSdqXipmfvtmWt9xGfp0wZnBxB+iVpLmQMYt47Tw==} - engines: {node: ^18.17.0 || >=20.5.0} - html-encoding-sniffer@3.0.0: resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} engines: {node: '>=12'} @@ -4243,10 +4216,6 @@ packages: engines: {node: '>=14.16'} hasBin: true - is-interactive@2.0.0: - resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==} - engines: {node: '>=12'} - is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -4305,14 +4274,6 @@ packages: resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} engines: {node: '>= 0.4'} - is-unicode-supported@1.3.0: - resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==} - engines: {node: '>=12'} - - is-unicode-supported@2.1.0: - resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} - engines: {node: '>=18'} - is-weakmap@2.0.2: resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} engines: {node: '>= 0.4'} @@ -4416,9 +4377,6 @@ packages: resolution: {integrity: sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - jsonc-parser@3.3.1: - resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} - jsonfile@4.0.0: resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} @@ -4429,10 +4387,6 @@ packages: keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - kleur@3.0.3: - resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} - engines: {node: '>=6'} - language-subtag-registry@0.3.23: resolution: {integrity: sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==} @@ -4471,10 +4425,6 @@ packages: lodash@4.17.23: resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} - log-symbols@6.0.0: - resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==} - engines: {node: '>=18'} - logrocket-fuzzy-search-sanitizer@0.0.2: resolution: {integrity: sha512-fjMzBrn/6KqgA49gq5/drkUsZnnDBmoKREbbZZfrKG5aUa/ACCpx450IwBvvgdYEkI7zqpW7GLu0Tj3cuyeFuQ==} @@ -4494,9 +4444,6 @@ packages: lower-case@2.0.2: resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - lru-cache@5.1.1: resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} @@ -4546,10 +4493,6 @@ packages: engines: {node: '>=4'} hasBin: true - mimic-function@5.0.1: - resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} - engines: {node: '>=18'} - min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -4646,10 +4589,6 @@ packages: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} engines: {node: '>=0.10.0'} - npm-package-arg@12.0.2: - resolution: {integrity: sha512-f1NpFjNI9O4VbKMOlA5QoBq/vSQPORHcTZ2feJpFkTHJ9eQkdlmZEKSjcAhxTGInC7RlEyScT9ui67NaOsjFWA==} - engines: {node: ^18.17.0 || >=20.5.0} - nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -4695,10 +4634,6 @@ packages: once@1.4.0: resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - onetime@7.0.0: - resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} - engines: {node: '>=18'} - open@10.2.0: resolution: {integrity: sha512-YgBpdJHPyQ2UE5x+hlSXcnejzAvD0b22U2OuAP+8OnlJT+PjWPxtgmGqKKc+RgTM63U9gN0YzrYc71R2WT/hTA==} engines: {node: '>=18'} @@ -4711,10 +4646,6 @@ packages: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} - ora@8.2.0: - resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==} - engines: {node: '>=18'} - outdent@0.5.0: resolution: {integrity: sha512-/jHxFIzoMXdqPzTaCpFzAAWhpkSjZPF4Vsn6jAfNpmbH/ymsmd7Qc6VE9BGn0L6YMj6uwpQLxCECpus4ukKS9Q==} @@ -4798,10 +4729,6 @@ packages: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} - path-type@6.0.0: - resolution: {integrity: sha512-Vj7sf++t5pBD637NSfkxpHSMfWaeig5+DKWLhcqIYx6mWQz5hdJTGDVMQiJcw1ZYkhs7AazKDGpRVji1LJCZUQ==} - engines: {node: '>=18'} - pathe@2.0.3: resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} @@ -4888,17 +4815,9 @@ packages: resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} - proc-log@5.0.0: - resolution: {integrity: sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ==} - engines: {node: ^18.17.0 || >=20.5.0} - process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - prompts@2.4.2: - resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} - engines: {node: '>= 6'} - prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -4921,9 +4840,6 @@ packages: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} - pure-rand@6.1.0: - resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} - qs@6.14.1: resolution: {integrity: sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==} engines: {node: '>=0.6'} @@ -4994,10 +4910,6 @@ packages: resolution: {integrity: sha512-VIMnQi/Z4HT2Fxuwg5KrY174U1VdUIASQVWXXyqtNRtxSr9IYkn1rsI6Tb6HsrHCmB7gVpNwX6JxPTHcH6IoTA==} engines: {node: '>=6'} - read-yaml-file@2.1.0: - resolution: {integrity: sha512-UkRNRIwnhG+y7hpqnycCL/xbTk7+ia9VuVTC0S+zVbwd65DI9eUpRMfsWIGrCWxTU/mi+JW8cHQCrv+zfCbEPQ==} - engines: {node: '>=10.13'} - readable-stream@1.0.34: resolution: {integrity: sha512-ok1qVCJuRkNmvebYikljxJA/UEsKwLl2nI1OmaqAu4/UE+h0wKCHok4XkL/gvi39OacXvw59RJUOFUkDib2rHg==} @@ -5062,10 +4974,6 @@ packages: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true - restore-cursor@5.1.0: - resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} - engines: {node: '>=18'} - rettime@0.10.1: resolution: {integrity: sha512-uyDrIlUEH37cinabq0AX4QbgV4HbFZ/gqoiunWQ1UqBtRvTTytwhNYjE++pO/MjPTZL5KQCf2bEoJ/BJNVQ5Kw==} @@ -5210,17 +5118,10 @@ packages: resolution: {integrity: sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==} engines: {node: '>=10'} - sisteransi@1.0.5: - resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} - slash@3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - slash@5.1.0: - resolution: {integrity: sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==} - engines: {node: '>=14.16'} - snake-case@3.0.4: resolution: {integrity: sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==} @@ -5275,10 +5176,6 @@ packages: std-env@3.10.0: resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} - stdin-discarder@0.2.2: - resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==} - engines: {node: '>=18'} - stop-iteration-iterator@1.1.0: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} @@ -5348,10 +5245,6 @@ packages: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} - strip-bom@4.0.0: - resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} - engines: {node: '>=8'} - strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -5389,9 +5282,49 @@ packages: resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} - syncpack@13.0.4: - resolution: {integrity: sha512-kJ9VlRxNCsBD5pJAE29oXeBYbPLhEySQmK4HdpsLv81I6fcDDW17xeJqMwiU3H7/woAVsbgq25DJNS8BeiN5+w==} - engines: {node: '>=18.18.0'} + syncpack-darwin-arm64@14.0.0: + resolution: {integrity: sha512-mEcku9YkOHfM6JOxhq9McgeLvd4djsJvRwNONZXHeoJ6+yqC96DdxZwFjkw3e8Vn95wsxsrwY5ZjMExs5Gbacw==} + cpu: [arm64] + os: [darwin] + + syncpack-darwin-x64@14.0.0: + resolution: {integrity: sha512-qSj3bT3SIZA5NLM5PNVeExapyOrdmptlGSMu0SLVHj9hata/Vqh3xWgwq7wB9uajmlrHljeJ84R/NKAHyzFewA==} + cpu: [x64] + os: [darwin] + + syncpack-linux-arm64-musl@14.0.0: + resolution: {integrity: sha512-i57Chz0tHP7ybKElPLhoygl6Cab1fxyUKS9xRgezX5NDrNxKUgrvqBfYd7288/QKB4C8+IcYHugFPjZjoFlAIA==} + cpu: [arm64] + os: [linux] + + syncpack-linux-arm64@14.0.0: + resolution: {integrity: sha512-votlkb4P/0Bxd9yhBWCSUOjZwaii+LfFn1tZZVN5dfs8qcjcLeqfdG5zbnlJSzZhS3T+BfzejFW+Z95OxZag0A==} + cpu: [arm64] + os: [linux] + + syncpack-linux-x64-musl@14.0.0: + resolution: {integrity: sha512-RKyp+29UlLGE8oYkd3UfwgjWUHN4dLHDyarv0dS6gWVpWM6qHtHS339KBc9JcIS7FD1vNGnQ3GmSaAodxDlkMA==} + cpu: [x64] + os: [linux] + + syncpack-linux-x64@14.0.0: + resolution: {integrity: sha512-sZGy4+uL+P/+nI0yc9jwW/R22u9lw0+NXDYC/9ts9eYiWFyO4+luOeosvVKbED1OavUp/GQJqNV+9KHjMwq0Fw==} + cpu: [x64] + os: [linux] + + syncpack-windows-arm64@14.0.0: + resolution: {integrity: sha512-aNtr0F6HkA7M0azDuDkt//DlPWlplFa4kmvHXirwDmJQ9u3SAvN3ZItW4ZYS96ZbiV1DgO15jIKBI7rDkzcwrQ==} + cpu: [arm64] + os: [win32] + + syncpack-windows-x64@14.0.0: + resolution: {integrity: sha512-usMH61wlonssfh2Q8SJDiiAcsXVzuPzRl8XdHqdavR3XeCSGBIW9ieJpPfiKvDPWslekufWD+GhLNoH+8vV0Cg==} + cpu: [x64] + os: [win32] + + syncpack@14.0.0: + resolution: {integrity: sha512-OfAa3Oip5YC9Ad1jEs92Hw08Wy4JfdpdeequIwaJGsQG0tJtb8gpQfCdLuBefsk6n8WiUdt/5qmzcW+BDXtzbQ==} + engines: {node: '>=14.17.0'} hasBin: true tagged-tag@1.0.0: @@ -5405,10 +5338,6 @@ packages: through2@2.0.5: resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} - tightrope@0.2.0: - resolution: {integrity: sha512-Kw36UHxJEELq2VUqdaSGR2/8cAsPgMtvX8uGVU6Jk26O66PhXec0A5ZnRYs47btbtwPDpXXF66+Fo3vimCM9aQ==} - engines: {node: '>=16'} - tiny-invariant@1.3.3: resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} @@ -5470,9 +5399,6 @@ packages: peerDependencies: typescript: '>=4.8.4' - ts-toolbelt@9.6.0: - resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==} - tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -5587,10 +5513,6 @@ packages: resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==} engines: {node: '>=18.17'} - unicorn-magic@0.3.0: - resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} - engines: {node: '>=18'} - union@0.5.0: resolution: {integrity: sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==} engines: {node: '>= 0.8.0'} @@ -5652,10 +5574,6 @@ packages: resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - validate-npm-package-name@6.0.2: - resolution: {integrity: sha512-IUoow1YUtvoBBC06dXs8bR8B9vuA3aJfmQNKMoaPG/OFsPmoQvw8xh+6Ye25Gx9DQhoEom3Pcu9MKHerm/NpUQ==} - engines: {node: ^18.17.0 || >=20.5.0} - validate-npm-package-name@7.0.2: resolution: {integrity: sha512-hVDIBwsRruT73PbK7uP5ebUt+ezEtCmzZz3F59BSr2F6OVFnJ/6h8liuvdLrQ88Xmnk6/+xGGuq+pG9WwTuy3A==} engines: {node: ^20.17.0 || >=22.9.0} @@ -7736,8 +7654,6 @@ snapshots: '@rtsao/scc@1.1.0': {} - '@sindresorhus/merge-streams@2.3.0': {} - '@standard-schema/spec@1.1.0': {} '@storybook/global@5.0.0': {} @@ -8493,17 +8409,11 @@ snapshots: chai@6.2.2: {} - chalk-template@1.1.2: - dependencies: - chalk: 5.6.2 - chalk@4.1.2: dependencies: ansi-styles: 4.3.0 supports-color: 7.2.0 - chalk@5.6.2: {} - change-case@5.4.4: {} chardet@2.1.1: {} @@ -8528,12 +8438,6 @@ snapshots: cjs-module-lexer@2.2.0: {} - cli-cursor@5.0.0: - dependencies: - restore-cursor: 5.1.0 - - cli-spinners@2.9.2: {} - cli-width@4.1.0: optional: true @@ -8566,8 +8470,6 @@ snapshots: color-name@1.1.4: {} - commander@13.1.0: {} - commander@7.2.0: {} concat-map@0.0.1: {} @@ -8620,15 +8522,6 @@ snapshots: optionalDependencies: typescript: 5.9.3 - cosmiconfig@9.0.0(typescript@5.9.3): - dependencies: - env-paths: 2.2.1 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - parse-json: 5.2.0 - optionalDependencies: - typescript: 5.9.3 - cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -8805,11 +8698,6 @@ snapshots: ee-first@1.1.1: {} - effect@3.19.16: - dependencies: - '@standard-schema/spec': 1.1.0 - fast-check: 3.23.2 - electron-to-chromium@1.5.286: {} emoji-regex@10.6.0: {} @@ -8833,8 +8721,6 @@ snapshots: entities@6.0.1: {} - env-paths@2.2.1: {} - error-ex@1.3.4: dependencies: is-arrayish: 0.2.1 @@ -9293,10 +9179,6 @@ snapshots: extendable-error@0.1.7: {} - fast-check@3.23.2: - dependencies: - pure-rand: 6.1.0 - fast-deep-equal@3.1.3: {} fast-glob@3.3.3: @@ -9492,15 +9374,6 @@ snapshots: merge2: 1.4.1 slash: 3.0.0 - globby@14.1.0: - dependencies: - '@sindresorhus/merge-streams': 2.3.0 - fast-glob: 3.3.3 - ignore: 7.0.5 - path-type: 6.0.0 - slash: 5.1.0 - unicorn-magic: 0.3.0 - google-logging-utils@0.0.2: {} gopd@1.2.0: {} @@ -9559,10 +9432,6 @@ snapshots: dependencies: hermes-estree: 0.25.1 - hosted-git-info@8.1.0: - dependencies: - lru-cache: 10.4.3 - html-encoding-sniffer@3.0.0: dependencies: whatwg-encoding: 2.0.0 @@ -9745,8 +9614,6 @@ snapshots: dependencies: is-docker: 3.0.0 - is-interactive@2.0.0: {} - is-map@2.0.3: {} is-negative-zero@2.0.3: {} @@ -9799,10 +9666,6 @@ snapshots: dependencies: which-typed-array: 1.1.20 - is-unicode-supported@1.3.0: {} - - is-unicode-supported@2.1.0: {} - is-weakmap@2.0.2: {} is-weakref@1.1.1: @@ -9897,8 +9760,6 @@ snapshots: espree: 9.6.1 semver: 7.7.4 - jsonc-parser@3.3.1: {} - jsonfile@4.0.0: optionalDependencies: graceful-fs: 4.2.11 @@ -9914,8 +9775,6 @@ snapshots: dependencies: json-buffer: 3.0.1 - kleur@3.0.3: {} - language-subtag-registry@0.3.23: {} language-tags@1.0.9: @@ -9947,11 +9806,6 @@ snapshots: lodash@4.17.23: {} - log-symbols@6.0.0: - dependencies: - chalk: 5.6.2 - is-unicode-supported: 1.3.0 - logrocket-fuzzy-search-sanitizer@0.0.2: dependencies: deparam: 1.0.5 @@ -9970,8 +9824,6 @@ snapshots: dependencies: tslib: 2.8.1 - lru-cache@10.4.3: {} - lru-cache@5.1.1: dependencies: yallist: 3.1.1 @@ -10007,8 +9859,6 @@ snapshots: mime@1.6.0: {} - mimic-function@5.0.1: {} - min-indent@1.0.1: {} minimatch@3.0.8: @@ -10115,13 +9965,6 @@ snapshots: normalize-path@3.0.0: {} - npm-package-arg@12.0.2: - dependencies: - hosted-git-info: 8.1.0 - proc-log: 5.0.0 - semver: 7.7.4 - validate-npm-package-name: 6.0.2 - nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -10178,10 +10021,6 @@ snapshots: dependencies: wrappy: 1.0.2 - onetime@7.0.0: - dependencies: - mimic-function: 5.0.1 - open@10.2.0: dependencies: default-browser: 5.5.0 @@ -10200,18 +10039,6 @@ snapshots: type-check: 0.4.0 word-wrap: 1.2.5 - ora@8.2.0: - dependencies: - chalk: 5.6.2 - cli-cursor: 5.0.0 - cli-spinners: 2.9.2 - is-interactive: 2.0.0 - is-unicode-supported: 2.1.0 - log-symbols: 6.0.0 - stdin-discarder: 0.2.2 - string-width: 7.2.0 - strip-ansi: 7.1.2 - outdent@0.5.0: {} outvariant@1.4.3: @@ -10286,8 +10113,6 @@ snapshots: path-type@4.0.0: {} - path-type@6.0.0: {} - pathe@2.0.3: {} pathval@2.0.1: {} @@ -10366,15 +10191,8 @@ snapshots: ansi-styles: 5.2.0 react-is: 17.0.2 - proc-log@5.0.0: {} - process-nextick-args@2.0.1: {} - prompts@2.4.2: - dependencies: - kleur: 3.0.3 - sisteransi: 1.0.5 - prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -10420,8 +10238,6 @@ snapshots: punycode@2.3.1: {} - pure-rand@6.1.0: {} - qs@6.14.1: dependencies: side-channel: 1.1.0 @@ -10490,11 +10306,6 @@ snapshots: pify: 4.0.1 strip-bom: 3.0.0 - read-yaml-file@2.1.0: - dependencies: - js-yaml: 4.1.1 - strip-bom: 4.0.0 - readable-stream@1.0.34: dependencies: core-util-is: 1.0.3 @@ -10593,11 +10404,6 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 - restore-cursor@5.1.0: - dependencies: - onetime: 7.0.0 - signal-exit: 4.1.0 - rettime@0.10.1: optional: true @@ -10790,12 +10596,8 @@ snapshots: dependencies: semver: 7.7.4 - sisteransi@1.0.5: {} - slash@3.0.0: {} - slash@5.1.0: {} - snake-case@3.0.4: dependencies: dot-case: 3.0.4 @@ -10848,8 +10650,6 @@ snapshots: std-env@3.10.0: {} - stdin-discarder@0.2.2: {} - stop-iteration-iterator@1.1.0: dependencies: es-errors: 1.3.0 @@ -10961,8 +10761,6 @@ snapshots: strip-bom@3.0.0: {} - strip-bom@4.0.0: {} - strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -10997,27 +10795,40 @@ snapshots: dependencies: '@pkgr/core': 0.2.9 - syncpack@13.0.4(typescript@5.9.3): - dependencies: - chalk: 5.6.2 - chalk-template: 1.1.2 - commander: 13.1.0 - cosmiconfig: 9.0.0(typescript@5.9.3) - effect: 3.19.16 - enquirer: 2.4.1 - fast-check: 3.23.2 - globby: 14.1.0 - jsonc-parser: 3.3.1 - minimatch: 9.0.5 - npm-package-arg: 12.0.2 - ora: 8.2.0 - prompts: 2.4.2 - read-yaml-file: 2.1.0 - semver: 7.7.4 - tightrope: 0.2.0 - ts-toolbelt: 9.6.0 - transitivePeerDependencies: - - typescript + syncpack-darwin-arm64@14.0.0: + optional: true + + syncpack-darwin-x64@14.0.0: + optional: true + + syncpack-linux-arm64-musl@14.0.0: + optional: true + + syncpack-linux-arm64@14.0.0: + optional: true + + syncpack-linux-x64-musl@14.0.0: + optional: true + + syncpack-linux-x64@14.0.0: + optional: true + + syncpack-windows-arm64@14.0.0: + optional: true + + syncpack-windows-x64@14.0.0: + optional: true + + syncpack@14.0.0: + optionalDependencies: + syncpack-darwin-arm64: 14.0.0 + syncpack-darwin-x64: 14.0.0 + syncpack-linux-arm64: 14.0.0 + syncpack-linux-arm64-musl: 14.0.0 + syncpack-linux-x64: 14.0.0 + syncpack-linux-x64-musl: 14.0.0 + syncpack-windows-arm64: 14.0.0 + syncpack-windows-x64: 14.0.0 tagged-tag@1.0.0: optional: true @@ -11029,8 +10840,6 @@ snapshots: readable-stream: 2.3.8 xtend: 4.0.2 - tightrope@0.2.0: {} - tiny-invariant@1.3.3: {} tinybench@2.9.0: {} @@ -11077,8 +10886,6 @@ snapshots: dependencies: typescript: 5.9.3 - ts-toolbelt@9.6.0: {} - tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 @@ -11204,8 +11011,6 @@ snapshots: undici@6.23.0: {} - unicorn-magic@0.3.0: {} - union@0.5.0: dependencies: qs: 6.14.1 @@ -11252,8 +11057,6 @@ snapshots: validate-npm-package-name@5.0.1: {} - validate-npm-package-name@6.0.2: {} - validate-npm-package-name@7.0.2: {} vary@1.1.2: {} From 5e4eebfb771d5cde387e321cca9cdcd8ef3ef24a Mon Sep 17 00:00:00 2001 From: "patrick.lafrance" Date: Wed, 25 Feb 2026 14:16:11 -0500 Subject: [PATCH 2/5] feat: add automated workflows for auditing, code review, dependency updates, and skill synchronization - Implemented `audit-monorepo` workflow to automate auditing against best practices. - Created `code-review` workflow for automated code reviews on pull requests. - Added `update-dependencies` workflow to manage and validate dependency updates. - Developed `sync-agent-skill` workflow to keep agent skills in sync with documentation. - Introduced prompts for auditing, code review, dependency updates, and skill creation/updating. - Added new skills for Workleap Telemetry to enhance developer support and onboarding. --- .../workleap-react-best-practices/SKILL.md | 117 +++++ .../references/advanced-rules.md | 126 +++++ .../references/async-rules.md | 166 +++++++ .../references/bundle-rules.md | 191 ++++++++ .../references/js-rules.md | 462 ++++++++++++++++++ .../references/rendering-rules.md | 267 ++++++++++ .../references/rerender-rules.md | 444 +++++++++++++++++ .github/prompts/audit-monorepo.md | 94 ++++ .github/prompts/code-review.md | 29 ++ .github/prompts/sync-agent-skill.md | 144 ++++++ .github/prompts/update-dependencies.md | 191 ++++++++ .github/workflows/audit-monorepo.yml | 37 ++ .github/workflows/claude.yml | 73 +++ .github/workflows/code-review.yml | 45 ++ .github/workflows/sync-agent-skill.yml | 76 +++ .github/workflows/update-dependencies.yml | 62 +++ CONTRIBUTING.md | 29 ++ docs/introduction/use-with-agents.md | 2 +- skills-lock.json | 89 ++-- {prompts => user-prompts}/CREATE_SKILL.md | 0 {prompts => user-prompts}/UPDATE_SKILL.md | 2 +- 21 files changed, 2602 insertions(+), 44 deletions(-) create mode 100644 .agents/skills/workleap-react-best-practices/SKILL.md create mode 100644 .agents/skills/workleap-react-best-practices/references/advanced-rules.md create mode 100644 .agents/skills/workleap-react-best-practices/references/async-rules.md create mode 100644 .agents/skills/workleap-react-best-practices/references/bundle-rules.md create mode 100644 .agents/skills/workleap-react-best-practices/references/js-rules.md create mode 100644 .agents/skills/workleap-react-best-practices/references/rendering-rules.md create mode 100644 .agents/skills/workleap-react-best-practices/references/rerender-rules.md create mode 100644 .github/prompts/audit-monorepo.md create mode 100644 .github/prompts/code-review.md create mode 100644 .github/prompts/sync-agent-skill.md create mode 100644 .github/prompts/update-dependencies.md create mode 100644 .github/workflows/audit-monorepo.yml create mode 100644 .github/workflows/claude.yml create mode 100644 .github/workflows/code-review.yml create mode 100644 .github/workflows/sync-agent-skill.yml create mode 100644 .github/workflows/update-dependencies.yml rename {prompts => user-prompts}/CREATE_SKILL.md (100%) rename {prompts => user-prompts}/UPDATE_SKILL.md (82%) diff --git a/.agents/skills/workleap-react-best-practices/SKILL.md b/.agents/skills/workleap-react-best-practices/SKILL.md new file mode 100644 index 00000000..c601556e --- /dev/null +++ b/.agents/skills/workleap-react-best-practices/SKILL.md @@ -0,0 +1,117 @@ +--- +name: workleap-react-best-practices +description: React performance optimization guidelines for Single Page Applications (SPA) at Workleap. Use when writing, reviewing, or refactoring React SPA code to ensure optimal performance patterns. Triggers on tasks involving React components, state management, bundle optimization, re-render prevention, rendering performance, or JavaScript performance improvements. Covers async waterfall elimination, bundle size reduction, re-render optimization, rendering efficiency, JS micro-optimizations, and advanced React patterns. Does NOT cover server-side rendering (SSR), Next.js, or server components. +metadata: + version: 1.0 +--- + +# Workleap React Best Practices + +Performance optimization guide for React Single Page Applications (SPA), tailored for Workleap's SPA-first architecture. + +## When to Apply + +Reference these guidelines when: +- Writing new React components +- Reviewing code for performance issues +- Refactoring existing React code +- Optimizing bundle size or load times +- Debugging unnecessary re-renders + +## Rule Categories by Priority + +| Priority | Category | Impact | Reference | +|----------|----------|--------|-----------| +| 1 | Eliminating Waterfalls | CRITICAL | [async-rules.md](references/async-rules.md) | +| 2 | Bundle Size Optimization | CRITICAL | [bundle-rules.md](references/bundle-rules.md) | +| 3 | Re-render Optimization | MEDIUM | [rerender-rules.md](references/rerender-rules.md) | +| 4 | Rendering Performance | MEDIUM | [rendering-rules.md](references/rendering-rules.md) | +| 5 | JavaScript Performance | LOW-MEDIUM | [js-rules.md](references/js-rules.md) | +| 6 | Advanced Patterns | LOW | [advanced-rules.md](references/advanced-rules.md) | + +## Quick Reference + +### 1. Eliminating Waterfalls (CRITICAL) + +Waterfalls are the #1 performance killer. Each sequential await adds full network latency. + +- **Defer Await Until Needed** - Move await into branches where actually used +- **Promise.all() for Independent Operations** - Run independent async operations concurrently +- **Dependency-Based Parallelization** - Use promise chaining for partial dependencies +- **Prevent Waterfall Chains** - Start promises early, await late + +Read [references/async-rules.md](references/async-rules.md) for detailed rules and code examples. + +### 2. Bundle Size Optimization (CRITICAL) + +Reducing initial bundle size improves Time to Interactive and Largest Contentful Paint. + +- **Route-Based Code Splitting** - Lazy-load each route so users only download code they visit +- **Avoid Barrel File Imports** - Import directly from source files, not barrel re-exports +- **React.lazy for Heavy Components** - Lazy-load large components not needed on initial render +- **Conditional Module Loading** - Load modules only when feature is activated +- **Preload on User Intent** - Preload heavy bundles on hover/focus + +Read [references/bundle-rules.md](references/bundle-rules.md) for detailed rules and code examples. + +### 3. Re-render Optimization (MEDIUM) + +Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness. + +- **Defer State Reads** - Don't subscribe to state only used in callbacks +- **Extract to Memoized Components** - Enable early returns before computation +- **Hoist Default Non-primitive Props** - Extract default values to constants for stable memo +- **Narrow Effect Dependencies** - Use primitive dependencies in effects +- **Subscribe to Derived State** - Subscribe to derived booleans, not raw values +- **Calculate Derived State During Render** - Derive state during render, not effects +- **Functional setState** - Use functional setState for stable callbacks +- **Lazy State Initialization** - Pass function to useState for expensive values +- **Skip useMemo for Simple Expressions** - Avoid memo for simple primitives +- **Put Logic in Event Handlers** - Put interaction logic in event handlers, not effects +- **Transitions for Non-Urgent Updates** - Use startTransition for non-urgent updates +- **useRef for Transient Values** - Use refs for transient frequent values + +Read [references/rerender-rules.md](references/rerender-rules.md) for detailed rules and code examples. + +### 4. Rendering Performance (MEDIUM) + +Optimizing the rendering process reduces the work the browser needs to do. + +- **Animate SVG Wrapper** - Animate div wrapper, not SVG element +- **CSS content-visibility** - Use content-visibility for long lists +- **Hoist Static JSX** - Extract static JSX outside components +- **Optimize SVG Precision** - Reduce SVG coordinate precision +- **Activity Component (Experimental)** - Use Activity for show/hide with state preservation +- **Explicit Conditional Rendering** - Use ternary, not && for conditionals +- **useTransition for Loading** - Prefer useTransition over manual loading state + +Read [references/rendering-rules.md](references/rendering-rules.md) for detailed rules and code examples. + +### 5. JavaScript Performance (LOW-MEDIUM) + +Micro-optimizations for hot paths that can add up to meaningful improvements. + +- **Avoid Layout Thrashing** - Batch DOM writes, avoid interleaved reads +- **Build Index Maps** - Use Map for repeated lookups +- **Cache Property Access** - Cache object properties in loops +- **Cache Function Results** - Cache repeated function calls in module-level Map +- **Cache Storage API Calls** - Cache localStorage/sessionStorage reads +- **Combine Array Iterations** - Combine multiple filter/map into one loop +- **Early Length Check** - Check array length before expensive comparison +- **Early Return** - Return early from functions +- **Hoist RegExp** - Hoist RegExp creation outside loops +- **Loop for Min/Max** - Use loop instead of sort for min/max +- **Set/Map Lookups** - Use Set/Map for O(1) lookups +- **toSorted() Immutability** - Use toSorted() to prevent mutation bugs + +Read [references/js-rules.md](references/js-rules.md) for detailed rules and code examples. + +### 6. Advanced Patterns (LOW) + +Advanced patterns for specific cases that require careful implementation. + +- **Event Handler Refs** - Store event handlers in refs for stable subscriptions +- **Initialize Once** - Initialize app once per load, not per mount +- **Stable Callback Refs** - Stable callback refs for effects without re-runs + +Read [references/advanced-rules.md](references/advanced-rules.md) for detailed rules and code examples. diff --git a/.agents/skills/workleap-react-best-practices/references/advanced-rules.md b/.agents/skills/workleap-react-best-practices/references/advanced-rules.md new file mode 100644 index 00000000..0391fb91 --- /dev/null +++ b/.agents/skills/workleap-react-best-practices/references/advanced-rules.md @@ -0,0 +1,126 @@ +# Advanced Patterns + +**Impact:** LOW +**Description:** Advanced patterns for specific cases that require careful implementation. + +--- + +## Store Event Handlers in Refs + +Store callbacks in refs when used in effects that shouldn't re-subscribe on callback changes. + +**Incorrect (re-subscribes on every render):** + +```tsx +function useWindowEvent(event: string, handler: (e) => void) { + useEffect(() => { + window.addEventListener(event, handler) + return () => window.removeEventListener(event, handler) + }, [event, handler]) +} +``` + +**Correct (stable subscription):** + +```tsx +function useWindowEvent(event: string, handler: (e) => void) { + const handlerRef = useRef(handler) + useEffect(() => { + handlerRef.current = handler + }, [handler]) + + useEffect(() => { + const listener = (e) => handlerRef.current(e) + window.addEventListener(event, listener) + return () => window.removeEventListener(event, listener) + }, [event]) +} +``` + +**Future alternative:** When `useEffectEvent` ships in a stable React release, it will simplify this pattern. Until then, the ref-based approach above is the recommended production pattern. + +--- + +## Initialize App Once, Not Per Mount + +Do not put app-wide initialization that must run once per app load inside `useEffect([])` of a component. Components can remount and effects will re-run. Use a module-level guard instead. + +**Incorrect (runs twice in dev, re-runs on remount):** + +```tsx +function Comp() { + useEffect(() => { + loadFromStorage() + checkAuthToken() + }, []) +} +``` + +**Correct (once per app load):** + +```tsx +let didInit = false + +function Comp() { + useEffect(() => { + if (didInit) return + didInit = true + loadFromStorage() + checkAuthToken() + }, []) +} +``` + +Reference: [Initializing the application](https://react.dev/learn/you-might-not-need-an-effect#initializing-the-application) + +--- + +## Stable Callback Refs for Effects + +Access latest values in callbacks without adding them to dependency arrays. Prevents effect re-runs while avoiding stale closures. + +**Incorrect (effect re-runs on every callback change):** + +```tsx +function SearchInput({ onSearch }: { onSearch: (q: string) => void }) { + const [query, setQuery] = useState('') + + useEffect(() => { + const timeout = setTimeout(() => onSearch(query), 300) + return () => clearTimeout(timeout) + }, [query, onSearch]) +} +``` + +**Correct (ref-based stable callback):** + +```tsx +function SearchInput({ onSearch }: { onSearch: (q: string) => void }) { + const [query, setQuery] = useState('') + const onSearchRef = useRef(onSearch) + useEffect(() => { onSearchRef.current = onSearch }, [onSearch]) + + useEffect(() => { + const timeout = setTimeout(() => onSearchRef.current(query), 300) + return () => clearTimeout(timeout) + }, [query]) +} +``` + +**Future: `useEffectEvent` (experimental, not in stable React):** + +When `useEffectEvent` ships in a stable React release, it will provide a cleaner API: + +```tsx +import { useEffectEvent } from 'react' + +function SearchInput({ onSearch }: { onSearch: (q: string) => void }) { + const [query, setQuery] = useState('') + const onSearchEvent = useEffectEvent(onSearch) + + useEffect(() => { + const timeout = setTimeout(() => onSearchEvent(query), 300) + return () => clearTimeout(timeout) + }, [query]) +} +``` diff --git a/.agents/skills/workleap-react-best-practices/references/async-rules.md b/.agents/skills/workleap-react-best-practices/references/async-rules.md new file mode 100644 index 00000000..7c940e17 --- /dev/null +++ b/.agents/skills/workleap-react-best-practices/references/async-rules.md @@ -0,0 +1,166 @@ +# Eliminating Waterfalls + +**Impact:** CRITICAL +**Description:** Waterfalls are the #1 performance killer. Each sequential await adds full network latency. Eliminating them yields the largest gains. + +--- + +## Defer Await Until Needed + +Move `await` operations into the branches where they're actually used to avoid blocking code paths that don't need them. + +**Incorrect (blocks both branches):** + +```typescript +async function handleRequest(userId: string, skipProcessing: boolean) { + const userData = await fetchUserData(userId) + + if (skipProcessing) { + return { skipped: true } + } + + return processUserData(userData) +} +``` + +**Correct (only blocks when needed):** + +```typescript +async function handleRequest(userId: string, skipProcessing: boolean) { + if (skipProcessing) { + return { skipped: true } + } + + const userData = await fetchUserData(userId) + return processUserData(userData) +} +``` + +**Another example (early return optimization):** + +```typescript +// Incorrect: always fetches permissions +async function updateResource(resourceId: string, userId: string) { + const permissions = await fetchPermissions(userId) + const resource = await getResource(resourceId) + + if (!resource) { + return { error: 'Not found' } + } + + if (!permissions.canEdit) { + return { error: 'Forbidden' } + } + + return await updateResourceData(resource, permissions) +} + +// Correct: fetches only when needed +async function updateResource(resourceId: string, userId: string) { + const resource = await getResource(resourceId) + + if (!resource) { + return { error: 'Not found' } + } + + const permissions = await fetchPermissions(userId) + + if (!permissions.canEdit) { + return { error: 'Forbidden' } + } + + return await updateResourceData(resource, permissions) +} +``` + +This optimization is especially valuable when the skipped branch is frequently taken, or when the deferred operation is expensive. + +--- + +## Promise.all() for Independent Operations + +When async operations have no interdependencies, execute them concurrently using `Promise.all()`. + +**Incorrect (sequential execution, 3 round trips):** + +```typescript +const user = await fetchUser() +const posts = await fetchPosts() +const comments = await fetchComments() +``` + +**Correct (parallel execution, 1 round trip):** + +```typescript +const [user, posts, comments] = await Promise.all([ + fetchUser(), + fetchPosts(), + fetchComments() +]) +``` + +--- + +## Dependency-Based Parallelization + +For operations with partial dependencies, create all promises upfront and use `Promise.all()` to maximize parallelism. + +**Incorrect (profile waits for config unnecessarily):** + +```typescript +const [user, config] = await Promise.all([ + fetchUser(), + fetchConfig() +]) +const profile = await fetchProfile(user.id) +``` + +**Correct (config and profile run in parallel):** + +```typescript +const userPromise = fetchUser() +const profilePromise = userPromise.then(user => fetchProfile(user.id)) + +const [user, config, profile] = await Promise.all([ + userPromise, + fetchConfig(), + profilePromise +]) +``` + +`config` and `profile` fetch concurrently. `profile` starts as soon as `user` resolves, without waiting for `config`. + + +--- + +## Prevent Waterfall Chains in Async Functions + +In async functions that perform multiple operations, start independent operations immediately, even if you don't await them yet. + +**Incorrect (config waits for auth, data waits for both):** + +```typescript +async function loadDashboard() { + const session = await auth() + const config = await fetchConfig() + const data = await fetchData(session.user.id) + return { data, config } +} +``` + +**Correct (auth and config start immediately):** + +```typescript +async function loadDashboard() { + const sessionPromise = auth() + const configPromise = fetchConfig() + const session = await sessionPromise + const [config, data] = await Promise.all([ + configPromise, + fetchData(session.user.id) + ]) + return { data, config } +} +``` + +For operations with more complex dependency chains, see the Dependency-Based Parallelization pattern above. diff --git a/.agents/skills/workleap-react-best-practices/references/bundle-rules.md b/.agents/skills/workleap-react-best-practices/references/bundle-rules.md new file mode 100644 index 00000000..b217d358 --- /dev/null +++ b/.agents/skills/workleap-react-best-practices/references/bundle-rules.md @@ -0,0 +1,191 @@ +# Bundle Size Optimization + +**Impact:** CRITICAL +**Description:** Reducing initial bundle size improves Time to Interactive and Largest Contentful Paint. + +--- + +## Route-Based Code Splitting + +Split your application by route so each page loads only the code it needs. This is the highest-impact bundle optimization for SPAs. + +**Incorrect (all routes in main bundle):** + +```tsx +import Dashboard from './pages/Dashboard' +import Settings from './pages/Settings' +import Profile from './pages/Profile' + +function AppRoutes() { + return ( + + } /> + } /> + } /> + + ) +} +``` + +**Correct (each route lazy-loaded):** + +```tsx +import { lazy, Suspense } from 'react' + +const Dashboard = lazy(() => import('./pages/Dashboard')) +const Settings = lazy(() => import('./pages/Settings')) +const Profile = lazy(() => import('./pages/Profile')) + +function AppRoutes() { + return ( + }> + + } /> + } /> + } /> + + + ) +} +``` + +Every route becomes its own chunk. Users only download code for the page they visit. This can reduce initial bundle size by 50-80% for large applications. + +--- + +## Avoid Barrel File Imports + +Import directly from source files instead of barrel files to avoid loading thousands of unused modules. **Barrel files** are entry points that re-export multiple modules (e.g., `index.js` that does `export * from './module'`). + +Popular icon and component libraries can have **up to 10,000 re-exports** in their entry file. For many React packages, **it takes 200-800ms just to import them**, affecting both development speed and production cold starts. + +**Why tree-shaking doesn't help:** When a library is marked as external (not bundled), the bundler can't optimize it. If you bundle it to enable tree-shaking, builds become substantially slower analyzing the entire module graph. + +**Incorrect (imports entire library via barrel file):** + +```tsx +import { IconCheck, IconX, IconMenu } from 'icon-library' +// Loads thousands of modules, adds 200-800ms to cold starts + +import { Button, TextField } from 'component-library' +// Same problem with component libraries +``` + +**Correct (imports only what you need):** + +```tsx +import IconCheck from 'icon-library/icons/check' +import IconX from 'icon-library/icons/x' +import IconMenu from 'icon-library/icons/menu' + +import Button from 'component-library/Button' +import TextField from 'component-library/TextField' +``` + +Direct imports provide 15-70% faster dev boot, 28% faster builds, 40% faster cold starts, and significantly faster HMR. + +--- + +## React.lazy for Heavy Components + +Use `React.lazy` with `Suspense` to lazy-load large components not needed on initial render. + +**Incorrect (Monaco bundles with main chunk ~300KB):** + +```tsx +import { MonacoEditor } from './monaco-editor' + +function CodePanel({ code }: { code: string }) { + return +} +``` + +**Correct (Monaco loads on demand):** + +```tsx +import { lazy, Suspense } from 'react' + +const MonacoEditor = lazy(() => + import('./monaco-editor').then(m => ({ default: m.MonacoEditor })) +) + +function CodePanel({ code }: { code: string }) { + return ( + Loading editor...}> + + + ) +} +``` + +--- + +## Conditional Module Loading + +Load large data or modules only when a feature is activated. + +**Example (lazy-load animation frames):** + +```tsx +function AnimationPlayer({ enabled, setEnabled }: { + enabled: boolean + setEnabled: React.Dispatch> +}) { + const [frames, setFrames] = useState(null) + + useEffect(() => { + if (enabled && !frames) { + import('./animation-frames.js') + .then(mod => setFrames(mod.frames)) + .catch(() => setEnabled(false)) + } + }, [enabled, frames, setEnabled]) + + if (!frames) return + return +} +``` + +--- + +## Preload Based on User Intent + +Preload heavy bundles before they're needed to reduce perceived latency. + +**Example (preload on hover/focus):** + +```tsx +function EditorButton({ onClick }: { onClick: () => void }) { + const preload = () => { + void import('./monaco-editor') + } + + return ( + + ) +} +``` + +**Example (preload when feature flag is enabled):** + +```tsx +function FlagsProvider({ children, flags }: Props) { + useEffect(() => { + if (flags.editorEnabled) { + void import('./monaco-editor').then(mod => mod.init()) + } + }, [flags.editorEnabled]) + + return ( + + {children} + + ) +} +``` diff --git a/.agents/skills/workleap-react-best-practices/references/js-rules.md b/.agents/skills/workleap-react-best-practices/references/js-rules.md new file mode 100644 index 00000000..9e651df9 --- /dev/null +++ b/.agents/skills/workleap-react-best-practices/references/js-rules.md @@ -0,0 +1,462 @@ +# JavaScript Performance + +**Impact:** LOW-MEDIUM +**Description:** Micro-optimizations for hot paths can add up to meaningful improvements. + +--- + +## Avoid Layout Thrashing + +Avoid interleaving style writes with layout reads. When you read a layout property (like `offsetWidth`, `getBoundingClientRect()`, or `getComputedStyle()`) between style changes, the browser is forced to trigger a synchronous reflow. + +**Incorrect (interleaved reads and writes force reflows):** + +```typescript +function layoutThrashing(element: HTMLElement) { + element.style.width = '100px' + const width = element.offsetWidth // Forces reflow + element.style.height = '200px' + const height = element.offsetHeight // Forces another reflow +} +``` + +**Correct (batch writes, then read once):** + +```typescript +function updateElementStyles(element: HTMLElement) { + element.style.width = '100px' + element.style.height = '200px' + + const { width, height } = element.getBoundingClientRect() +} +``` + +**Better: use CSS classes** + +```css +.highlighted-box { + width: 100px; + height: 200px; + background-color: blue; + border: 1px solid black; +} +``` + +```typescript +function updateElementStyles(element: HTMLElement) { + element.classList.add('highlighted-box') + const { width, height } = element.getBoundingClientRect() +} +``` + +**React example:** + +```tsx +// Incorrect: interleaving style changes with layout queries +function Box({ isHighlighted }: { isHighlighted: boolean }) { + const ref = useRef(null) + + useEffect(() => { + if (ref.current && isHighlighted) { + ref.current.style.width = '100px' + const width = ref.current.offsetWidth // Forces layout + ref.current.style.height = '200px' + } + }, [isHighlighted]) + + return
Content
+} + +// Correct: toggle class +function Box({ isHighlighted }: { isHighlighted: boolean }) { + return ( +
+ Content +
+ ) +} +``` + +Prefer CSS classes over inline styles when possible. + +--- + +## Build Index Maps for Repeated Lookups + +Multiple `.find()` calls by the same key should use a Map. + +**Incorrect (O(n) per lookup):** + +```typescript +function processOrders(orders: Order[], users: User[]) { + return orders.map(order => ({ + ...order, + user: users.find(u => u.id === order.userId) + })) +} +``` + +**Correct (O(1) per lookup):** + +```typescript +function processOrders(orders: Order[], users: User[]) { + const userById = new Map(users.map(u => [u.id, u])) + + return orders.map(order => ({ + ...order, + user: userById.get(order.userId) + })) +} +``` + +Build map once (O(n)), then all lookups are O(1). + +--- + +## Cache Property Access in Loops + +Cache object property lookups in hot paths. + +**Incorrect (3 lookups x N iterations):** + +```typescript +for (let i = 0; i < arr.length; i++) { + process(obj.config.settings.value) +} +``` + +**Correct (1 lookup total):** + +```typescript +const value = obj.config.settings.value +const len = arr.length +for (let i = 0; i < len; i++) { + process(value) +} +``` + +--- + +## Cache Repeated Function Calls + +Use a module-level Map to cache function results when the same function is called repeatedly with the same inputs. + +**Incorrect (redundant computation):** + +```typescript +function ProjectList({ projects }: { projects: Project[] }) { + return ( +
+ {projects.map(project => { + const slug = slugify(project.name) // called 100+ times for same names + return + })} +
+ ) +} +``` + +**Correct (cached results):** + +```typescript +const slugifyCache = new Map() + +function cachedSlugify(text: string): string { + if (slugifyCache.has(text)) { + return slugifyCache.get(text)! + } + const result = slugify(text) + slugifyCache.set(text, result) + return result +} + +function ProjectList({ projects }: { projects: Project[] }) { + return ( +
+ {projects.map(project => { + const slug = cachedSlugify(project.name) + return + })} +
+ ) +} +``` + +Use a Map (not a hook) so it works everywhere: utilities, event handlers, not just React components. + +--- + +## Cache Storage API Calls + +`localStorage`, `sessionStorage`, and `document.cookie` are synchronous and expensive. Cache reads in memory. + +**Incorrect (reads storage on every call):** + +```typescript +function getTheme() { + return localStorage.getItem('theme') ?? 'light' +} +``` + +**Correct (Map cache):** + +```typescript +const storageCache = new Map() + +function getLocalStorage(key: string) { + if (!storageCache.has(key)) { + storageCache.set(key, localStorage.getItem(key)) + } + return storageCache.get(key) +} + +function setLocalStorage(key: string, value: string) { + localStorage.setItem(key, value) + storageCache.set(key, value) +} +``` + +**Important (invalidate on external changes):** + +```typescript +window.addEventListener('storage', (e) => { + if (e.key) storageCache.delete(e.key) +}) + +document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'visible') { + storageCache.clear() + } +}) +``` + +--- + +## Combine Multiple Array Iterations + +Multiple `.filter()` or `.map()` calls iterate the array multiple times. Combine into one loop. + +**Incorrect (3 iterations):** + +```typescript +const admins = users.filter(u => u.isAdmin) +const testers = users.filter(u => u.isTester) +const inactive = users.filter(u => !u.isActive) +``` + +**Correct (1 iteration):** + +```typescript +const admins: User[] = [] +const testers: User[] = [] +const inactive: User[] = [] + +for (const user of users) { + if (user.isAdmin) admins.push(user) + if (user.isTester) testers.push(user) + if (!user.isActive) inactive.push(user) +} +``` + +--- + +## Early Length Check for Array Comparisons + +When comparing arrays with expensive operations, check lengths first. + +**Incorrect (always runs expensive comparison):** + +```typescript +function hasChanges(current: string[], original: string[]) { + return current.sort().join() !== original.sort().join() +} +``` + +**Correct (O(1) length check first):** + +```typescript +function hasChanges(current: string[], original: string[]) { + if (current.length !== original.length) { + return true + } + const currentSorted = current.toSorted() + const originalSorted = original.toSorted() + for (let i = 0; i < currentSorted.length; i++) { + if (currentSorted[i] !== originalSorted[i]) { + return true + } + } + return false +} +``` + +--- + +## Early Return from Functions + +Return early when result is determined to skip unnecessary processing. + +**Incorrect (processes all items even after finding answer):** + +```typescript +function validateUsers(users: User[]) { + let hasError = false + let errorMessage = '' + + for (const user of users) { + if (!user.email) { + hasError = true + errorMessage = 'Email required' + } + if (!user.name) { + hasError = true + errorMessage = 'Name required' + } + } + + return hasError ? { valid: false, error: errorMessage } : { valid: true } +} +``` + +**Correct (returns immediately on first error):** + +```typescript +function validateUsers(users: User[]) { + for (const user of users) { + if (!user.email) { + return { valid: false, error: 'Email required' } + } + if (!user.name) { + return { valid: false, error: 'Name required' } + } + } + + return { valid: true } +} +``` + +--- + +## Hoist RegExp Creation + +Don't create RegExp inside render. Hoist to module scope or memoize with `useMemo()`. + +**Incorrect (new RegExp every render):** + +```tsx +function Highlighter({ text, query }: Props) { + const regex = new RegExp(`(${query})`, 'gi') + const parts = text.split(regex) + return <>{parts.map((part, i) => ...)} +} +``` + +**Correct (memoize or hoist):** + +```tsx +const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ + +function Highlighter({ text, query }: Props) { + const regex = useMemo( + () => new RegExp(`(${escapeRegex(query)})`, 'gi'), + [query] + ) + const parts = text.split(regex) + return <>{parts.map((part, i) => ...)} +} +``` + +**Warning:** Global regex (`/g`) has mutable `lastIndex` state: + +```typescript +const regex = /foo/g +regex.test('foo') // true, lastIndex = 3 +regex.test('foo') // false, lastIndex = 0 +``` + +--- + +## Use Loop for Min/Max Instead of Sort + +Finding the smallest or largest element only requires a single pass. + +**Incorrect (O(n log n) - sort to find latest):** + +```typescript +function getLatestProject(projects: Project[]) { + const sorted = [...projects].sort((a, b) => b.updatedAt - a.updatedAt) + return sorted[0] +} +``` + +**Correct (O(n) - single loop):** + +```typescript +function getLatestProject(projects: Project[]) { + if (projects.length === 0) return null + + let latest = projects[0] + + for (let i = 1; i < projects.length; i++) { + if (projects[i].updatedAt > latest.updatedAt) { + latest = projects[i] + } + } + + return latest +} +``` + +--- + +## Use Set/Map for O(1) Lookups + +Convert arrays to Set/Map for repeated membership checks. + +**Incorrect (O(n) per check):** + +```typescript +const allowedIds = ['a', 'b', 'c', ...] +items.filter(item => allowedIds.includes(item.id)) +``` + +**Correct (O(1) per check):** + +```typescript +const allowedIds = new Set(['a', 'b', 'c', ...]) +items.filter(item => allowedIds.has(item.id)) +``` + +--- + +## Use toSorted() Instead of sort() for Immutability + +`.sort()` mutates the array in place, which can cause bugs with React state and props. Use `.toSorted()` to create a new sorted array. + +**Incorrect (mutates original array):** + +```typescript +function UserList({ users }: { users: User[] }) { + const sorted = useMemo( + () => users.sort((a, b) => a.name.localeCompare(b.name)), + [users] + ) + return
{sorted.map(renderUser)}
+} +``` + +**Correct (creates new array):** + +```typescript +function UserList({ users }: { users: User[] }) { + const sorted = useMemo( + () => users.toSorted((a, b) => a.name.localeCompare(b.name)), + [users] + ) + return
{sorted.map(renderUser)}
+} +``` + +**Why this matters in React:** Props/state mutations break React's immutability model and cause stale closure bugs. + +**Other immutable array methods:** `.toSorted()`, `.toReversed()`, `.toSpliced()`, `.with()` diff --git a/.agents/skills/workleap-react-best-practices/references/rendering-rules.md b/.agents/skills/workleap-react-best-practices/references/rendering-rules.md new file mode 100644 index 00000000..e76f5e00 --- /dev/null +++ b/.agents/skills/workleap-react-best-practices/references/rendering-rules.md @@ -0,0 +1,267 @@ +# Rendering Performance + +**Impact:** MEDIUM +**Description:** Optimizing the rendering process reduces the work the browser needs to do. + +--- + +## Animate SVG Wrapper Instead of SVG Element + +Many browsers don't have hardware acceleration for CSS3 animations on SVG elements. Wrap SVG in a `
` and animate the wrapper instead. + +**Incorrect (animating SVG directly - no hardware acceleration):** + +```tsx +function LoadingSpinner() { + return ( + + + + ) +} +``` + +**Correct (animating wrapper div - hardware accelerated):** + +```tsx +function LoadingSpinner() { + return ( +
+ + + +
+ ) +} +``` + +This applies to all CSS transforms and transitions (`transform`, `opacity`, `translate`, `scale`, `rotate`). + +--- + +## CSS content-visibility for Long Lists + +Apply `content-visibility: auto` to defer off-screen rendering. + +**CSS:** + +```css +.message-item { + content-visibility: auto; + contain-intrinsic-size: 0 80px; +} +``` + +**Example:** + +```tsx +function MessageList({ messages }: { messages: Message[] }) { + return ( +
+ {messages.map(msg => ( +
+ +
{msg.content}
+
+ ))} +
+ ) +} +``` + +For 1000 messages, browser skips layout/paint for ~990 off-screen items (10x faster initial render). + +--- + +## Hoist Static JSX Elements + +Extract static JSX outside components to avoid re-creation. + +**Incorrect (recreates element every render):** + +```tsx +function LoadingSkeleton() { + return
+} + +function Container() { + return ( +
+ {loading && } +
+ ) +} +``` + +**Correct (reuses same element):** + +```tsx +const loadingSkeleton = ( +
+) + +function Container() { + return ( +
+ {loading && loadingSkeleton} +
+ ) +} +``` + +This is especially helpful for large and static SVG nodes, which can be expensive to recreate on every render. + +**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, the compiler automatically hoists static JSX elements, making manual hoisting unnecessary. + +--- + +## Optimize SVG Precision + +Reduce SVG coordinate precision to decrease file size. + +**Incorrect (excessive precision):** + +```svg + +``` + +**Correct (1 decimal place):** + +```svg + +``` + + +--- + +## Use Activity Component for Show/Hide (Experimental) + +**Note:** `` is an experimental React API not yet available in stable releases. Use it only if your project uses React Canary/experimental builds. For stable React, use CSS `display: none` or conditional rendering with key-based state preservation. + +**Stable React approach:** + +```tsx +function Dropdown({ isOpen }: Props) { + return ( +
+ +
+ ) +} +``` + +**With React experimental builds:** + +```tsx +import { Activity } from 'react' + +function Dropdown({ isOpen }: Props) { + return ( + + + + ) +} +``` + +Both approaches preserve state/DOM and avoid expensive re-renders on toggle. + +--- + +## Use Explicit Conditional Rendering + +Use explicit ternary operators (`? :`) instead of `&&` for conditional rendering when the condition can be `0`, `NaN`, or other falsy values that render. + +**Incorrect (renders "0" when count is 0):** + +```tsx +function Badge({ count }: { count: number }) { + return ( +
+ {count && {count}} +
+ ) +} +// When count = 0, renders:
0
+``` + +**Correct (renders nothing when count is 0):** + +```tsx +function Badge({ count }: { count: number }) { + return ( +
+ {count > 0 ? {count} : null} +
+ ) +} +``` + +--- + +## Use useTransition Over Manual Loading States + +Use `useTransition` instead of manual `useState` for loading states. + +**Incorrect (manual loading state):** + +```tsx +function SearchResults() { + const [query, setQuery] = useState('') + const [results, setResults] = useState([]) + const [isLoading, setIsLoading] = useState(false) + + const handleSearch = async (value: string) => { + setIsLoading(true) + setQuery(value) + const data = await fetchResults(value) + setResults(data) + setIsLoading(false) + } + + return ( + <> + handleSearch(e.target.value)} /> + {isLoading && } + + + ) +} +``` + +**Correct (useTransition with built-in pending state):** + +```tsx +import { useTransition, useState } from 'react' + +function SearchResults() { + const [query, setQuery] = useState('') + const [results, setResults] = useState([]) + const [isPending, startTransition] = useTransition() + + const handleSearch = (value: string) => { + setQuery(value) + + startTransition(async () => { + const data = await fetchResults(value) + setResults(data) + }) + } + + return ( + <> + handleSearch(e.target.value)} /> + {isPending && } + + + ) +} +``` + +**Benefits:** +- Automatic pending state management +- Error resilience: pending state correctly resets even if the transition throws +- Better responsiveness: keeps the UI responsive during updates +- New transitions automatically cancel pending ones + +Reference: [useTransition](https://react.dev/reference/react/useTransition) diff --git a/.agents/skills/workleap-react-best-practices/references/rerender-rules.md b/.agents/skills/workleap-react-best-practices/references/rerender-rules.md new file mode 100644 index 00000000..8ac1b2e8 --- /dev/null +++ b/.agents/skills/workleap-react-best-practices/references/rerender-rules.md @@ -0,0 +1,444 @@ +# Re-render Optimization + +**Impact:** MEDIUM +**Description:** Reducing unnecessary re-renders minimizes wasted computation and improves UI responsiveness. + +--- + +## Defer State Reads to Usage Point + +Don't subscribe to dynamic state (searchParams, localStorage) if you only read it inside callbacks. + +**Incorrect (subscribes to all searchParams changes):** + +```tsx +function ShareButton({ chatId }: { chatId: string }) { + const searchParams = useSearchParams() + + const handleShare = () => { + const ref = searchParams.get('ref') + shareChat(chatId, { ref }) + } + + return +} +``` + +**Correct (reads on demand, no subscription):** + +```tsx +function ShareButton({ chatId }: { chatId: string }) { + const handleShare = () => { + const params = new URLSearchParams(window.location.search) + const ref = params.get('ref') + shareChat(chatId, { ref }) + } + + return +} +``` + +--- + +## Extract to Memoized Components + +Extract expensive work into memoized components to enable early returns before computation. + +**Incorrect (computes avatar even when loading):** + +```tsx +function Profile({ user, loading }: Props) { + const avatar = useMemo(() => { + const id = computeAvatarId(user) + return + }, [user]) + + if (loading) return + return
{avatar}
+} +``` + +**Correct (skips computation when loading):** + +```tsx +const UserAvatar = memo(function UserAvatar({ user }: { user: User }) { + const id = useMemo(() => computeAvatarId(user), [user]) + return +}) + +function Profile({ user, loading }: Props) { + if (loading) return + return ( +
+ +
+ ) +} +``` + +**Note:** If your project has [React Compiler](https://react.dev/learn/react-compiler) enabled, manual memoization with `memo()` and `useMemo()` is not necessary. The compiler automatically optimizes re-renders. + +--- + +## Extract Default Non-primitive Props to Constants + +When a memoized component has a default value for a non-primitive optional parameter (array, function, object), calling the component without that parameter breaks memoization because new value instances are created on every re-render. + +**Incorrect (`onClick` has different values on every re-render):** + +```tsx +const UserAvatar = memo(function UserAvatar({ onClick = () => {} }: { onClick?: () => void }) { + // ... +}) + + +``` + +**Correct (stable default value):** + +```tsx +const NOOP = () => {}; + +const UserAvatar = memo(function UserAvatar({ onClick = NOOP }: { onClick?: () => void }) { + // ... +}) + + +``` + +--- + +## Narrow Effect Dependencies + +Specify primitive dependencies instead of objects to minimize effect re-runs. + +**Incorrect (re-runs on any user field change):** + +```tsx +useEffect(() => { + console.log(user.id) +}, [user]) +``` + +**Correct (re-runs only when id changes):** + +```tsx +useEffect(() => { + console.log(user.id) +}, [user.id]) +``` + +**For derived state, compute outside effect:** + +```tsx +// Incorrect: runs on width=767, 766, 765... +useEffect(() => { + if (width < 768) { + enableMobileMode() + } +}, [width]) + +// Correct: runs only on boolean transition +const isMobile = width < 768 +useEffect(() => { + if (isMobile) { + enableMobileMode() + } +}, [isMobile]) +``` + +--- + +## Subscribe to Derived State + +Subscribe to derived boolean state instead of continuous values to reduce re-render frequency. + +**Incorrect (re-renders on every pixel change):** + +```tsx +function Sidebar() { + const width = useWindowWidth() // updates continuously + const isMobile = width < 768 + return