From 762b0d8d66756f439650eb1492221bcd5fbc4343 Mon Sep 17 00:00:00 2001 From: Simon Heimlicher Date: Tue, 3 Mar 2026 00:35:07 +0100 Subject: [PATCH 1/4] fix: add missing eslint-plugin-react-hooks dependency spx validation was failing because eslint-plugin-react-hooks was referenced by eslint-config-next but not installed as a direct dependency. --- package.json | 1 + pnpm-lock.yaml | 293 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 290 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 21961ff..032a4aa 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "eslint": "^9.0.0", "eslint-config-next": "^15.1.0", "eslint-config-prettier": "^10.1.8", + "eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-sonarjs": "^4.0.0", "tailwindcss": "^4.0.0", "typescript": "^5.7.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c558ff5..c7d2356 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -23,7 +23,7 @@ importers: version: 4.0.3 next: specifier: ^15.1.0 - version: 15.5.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + version: 15.5.12(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) next-mdx-remote: specifier: ^6.0.0 version: 6.0.0(@types/react@19.2.14)(react@19.2.4) @@ -61,6 +61,9 @@ importers: eslint-config-prettier: specifier: ^10.1.8 version: 10.1.8(eslint@9.39.3(jiti@2.6.1)) + eslint-plugin-react-hooks: + specifier: ^7.0.1 + version: 7.0.1(eslint@9.39.3(jiti@2.6.1)) eslint-plugin-sonarjs: specifier: ^4.0.0 version: 4.0.0(eslint@9.39.3(jiti@2.6.1)) @@ -84,10 +87,69 @@ packages: resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + '@emnapi/core@1.8.1': resolution: {integrity: sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==} @@ -1105,6 +1167,11 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} + hasBin: true + brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} @@ -1119,6 +1186,11 @@ packages: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + builtin-modules@3.3.0: resolution: {integrity: sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==} engines: {node: '>=6'} @@ -1188,6 +1260,9 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + cross-spawn@7.0.6: resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} engines: {node: '>= 8'} @@ -1260,6 +1335,9 @@ packages: resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} engines: {node: '>= 0.4'} + electron-to-chromium@1.5.302: + resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -1313,6 +1391,10 @@ packages: engines: {node: '>=18'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1391,6 +1473,12 @@ packages: peerDependencies: eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-react-hooks@7.0.1: + resolution: {integrity: sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==} + engines: {node: '>=18'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 + eslint-plugin-react@7.37.5: resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} engines: {node: '>=4'} @@ -1569,6 +1657,10 @@ packages: resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} engines: {node: '>= 0.4'} + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1651,6 +1743,12 @@ packages: hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + ignore@5.3.2: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} @@ -1822,6 +1920,11 @@ packages: resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} hasBin: true + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} @@ -1835,6 +1938,11 @@ packages: resolution: {integrity: sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==} hasBin: true + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + jsx-ast-utils-x@0.1.0: resolution: {integrity: sha512-eQQBjBnsVtGacsG9uJNB8qOr3yA8rga4wAaGG1qRcBzSIvfhERLrWxMAM1hp5fcS6Abo8M4+bUBTekYR0qTPQw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -1945,6 +2053,9 @@ packages: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + magic-string@0.30.21: resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} @@ -2145,6 +2256,9 @@ packages: resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} engines: {node: '>= 0.4'} + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -2610,6 +2724,12 @@ packages: unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} @@ -2726,6 +2846,9 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yaml@2.8.2: resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} engines: {node: '>= 14.6'} @@ -2735,6 +2858,15 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} + zod-validation-error@4.0.2: + resolution: {integrity: sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==} + engines: {node: '>=18.0.0'} + peerDependencies: + zod: ^3.25.0 || ^4.0.0 + + zod@4.3.6: + resolution: {integrity: sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==} + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -2748,8 +2880,100 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@emnapi/core@1.8.1': dependencies: '@emnapi/wasi-threads': 1.1.0 @@ -3624,6 +3848,8 @@ snapshots: balanced-match@4.0.4: {} + baseline-browser-mapping@2.10.0: {} + brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 @@ -3641,6 +3867,14 @@ snapshots: dependencies: fill-range: 7.1.1 + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001770 + electron-to-chromium: 1.5.302 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + builtin-modules@3.3.0: {} bytes@3.1.2: {} @@ -3697,6 +3931,8 @@ snapshots: concat-map@0.0.1: {} + convert-source-map@2.0.0: {} + cross-spawn@7.0.6: dependencies: path-key: 3.1.1 @@ -3769,6 +4005,8 @@ snapshots: es-errors: 1.3.0 gopd: 1.2.0 + electron-to-chromium@1.5.302: {} + emoji-regex@9.2.2: {} enhanced-resolve@5.19.0: @@ -3922,6 +4160,8 @@ snapshots: '@esbuild/win32-ia32': 0.27.3 '@esbuild/win32-x64': 0.27.3 + escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} eslint-config-next@15.5.12(eslint@9.39.3(jiti@2.6.1))(typescript@5.9.3): @@ -4034,6 +4274,17 @@ snapshots: dependencies: eslint: 9.39.3(jiti@2.6.1) + eslint-plugin-react-hooks@7.0.1(eslint@9.39.3(jiti@2.6.1)): + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + eslint: 9.39.3(jiti@2.6.1) + hermes-parser: 0.25.1 + zod: 4.3.6 + zod-validation-error: 4.0.2(zod@4.3.6) + transitivePeerDependencies: + - supports-color + eslint-plugin-react@7.37.5(eslint@9.39.3(jiti@2.6.1)): dependencies: array-includes: 3.1.9 @@ -4260,6 +4511,8 @@ snapshots: generator-function@2.0.1: {} + gensync@1.0.0-beta.2: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -4383,6 +4636,12 @@ snapshots: dependencies: '@types/hast': 3.0.4 + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + ignore@5.3.2: {} ignore@7.0.5: {} @@ -4555,6 +4814,8 @@ snapshots: dependencies: argparse: 2.0.1 + jsesc@3.1.0: {} + json-buffer@3.0.1: {} json-schema-traverse@0.4.1: {} @@ -4565,6 +4826,8 @@ snapshots: dependencies: minimist: 1.2.8 + json5@2.2.3: {} + jsx-ast-utils-x@0.1.0: {} jsx-ast-utils@3.3.5: @@ -4652,6 +4915,10 @@ snapshots: dependencies: js-tokens: 4.0.0 + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + magic-string@0.30.21: dependencies: '@jridgewell/sourcemap-codec': 1.5.5 @@ -5018,7 +5285,7 @@ snapshots: - '@types/react' - supports-color - next@15.5.12(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + next@15.5.12(@babel/core@7.29.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): dependencies: '@next/env': 15.5.12 '@swc/helpers': 0.5.15 @@ -5026,7 +5293,7 @@ snapshots: postcss: 8.4.31 react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - styled-jsx: 5.1.6(react@19.2.4) + styled-jsx: 5.1.6(@babel/core@7.29.0)(react@19.2.4) optionalDependencies: '@next/swc-darwin-arm64': 15.5.12 '@next/swc-darwin-x64': 15.5.12 @@ -5048,6 +5315,8 @@ snapshots: object.entries: 1.1.9 semver: 6.3.1 + node-releases@2.0.27: {} + object-assign@4.1.1: {} object-inspect@1.13.4: {} @@ -5540,10 +5809,12 @@ snapshots: dependencies: inline-style-parser: 0.2.7 - styled-jsx@5.1.6(react@19.2.4): + styled-jsx@5.1.6(@babel/core@7.29.0)(react@19.2.4): dependencies: client-only: 0.0.1 react: 19.2.4 + optionalDependencies: + '@babel/core': 7.29.0 supports-color@7.2.0: dependencies: @@ -5702,6 +5973,12 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + uri-js@4.4.1: dependencies: punycode: 2.3.1 @@ -5825,8 +6102,16 @@ snapshots: word-wrap@1.2.5: {} + yallist@3.1.1: {} + yaml@2.8.2: {} yocto-queue@0.1.0: {} + zod-validation-error@4.0.2(zod@4.3.6): + dependencies: + zod: 4.3.6 + + zod@4.3.6: {} + zwitch@2.0.4: {} From 80f742bfc137125469744ec3d8449b49a7d68479 Mon Sep 17 00:00:00 2001 From: Simon Heimlicher Date: Tue, 3 Mar 2026 00:52:39 +0100 Subject: [PATCH 2/4] fix: align landing page copy to output/outcome/impact distinction MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolve output/outcome conflation across the landing page: - Hero: replace "prove" with "show", "claims" with "assertions", fix Leaves label that implied tree tracks customer behavior - WhyOutcomesSection: distinguish outcome specs from enabler specs instead of claiming every spec begins with an outcome hypothesis - WhatChangesSection: "Assertions have evidence" not "Goals have evidence" — lock state is assertion-level, not outcome-level - story-data: fix "outcomes express testable hypotheses" heading (outcomes need real users, only output assertions are testable), separate assertion evidence from outcome validation - NodeAnatomySection: replace status-rollup example (output masquerading as outcome) with billing domain example that cleanly separates output, outcome, and impact --- src/components/HeroSection.tsx | 8 ++++---- src/components/NodeAnatomySection.tsx | 22 +++++++++------------- src/components/WhatChangesSection.tsx | 2 +- src/components/WhyOutcomesSection.tsx | 7 ++++--- src/lib/story-data.tsx | 10 +++++----- 5 files changed, 23 insertions(+), 26 deletions(-) diff --git a/src/components/HeroSection.tsx b/src/components/HeroSection.tsx index 2b6d612..3b78b8b 100644 --- a/src/components/HeroSection.tsx +++ b/src/components/HeroSection.tsx @@ -22,8 +22,8 @@ export default function HeroSection() { outcomes

- Start with business goals and customer insight at the roots, express outcome hypotheses as testable specs, - and let the structure prove what works. + Start with business goals and customer insight at the roots, express outcome hypotheses with testable + assertions, and let the structure show what holds.

@@ -35,13 +35,13 @@ export default function HeroSection() {
Branches
- Outcome hypotheses — structured, testable paths forward. + Outcome hypotheses, each with assertions the code must satisfy.
Leaves
- Thriving outcomes that prove the structure works. + Where output meets evidence — assertions the code must satisfy today.
diff --git a/src/components/NodeAnatomySection.tsx b/src/components/NodeAnatomySection.tsx index cdcba0b..8533322 100644 --- a/src/components/NodeAnatomySection.tsx +++ b/src/components/NodeAnatomySection.tsx @@ -1,25 +1,21 @@ import CodeExample from "@/components/CodeExample"; import Section from "@/components/Section"; -const specContent = `## Outcome - -We believe that aggregating child node states into a parent status -will let developers identify stale subtrees without inspecting -each node individually. +const specContent = + `## We believe that, by showing itemized charges per service, we will reduce billing support tickets by 40%, resulting in reduced support costs ### Assertions -- A parent with all valid children reports valid - ([test](tests/status.unit.test.ts)) -- A parent with any stale child reports stale - ([test](tests/status.unit.test.ts)) -- A parent with any needs-work child needs work - ([test](tests/status.unit.test.ts))`; +- A multi-service invoice shows service name, quantity, + and unit price per line + ([test](tests/line-items.unit.test.ts)) +- Invoice total equals the sum of all line items + ([test](tests/line-items.unit.test.ts))`; const lockContent = `schema: spx-lock/v1 blob: a3b7c12 tests: - - path: tests/status.unit.test.ts + - path: tests/line-items.unit.test.ts blob: 9d4e5f2`; export default function NodeAnatomySection() { @@ -32,7 +28,7 @@ export default function NodeAnatomySection() {
- +
diff --git a/src/components/WhatChangesSection.tsx b/src/components/WhatChangesSection.tsx index a64b6bc..df3bbca 100644 --- a/src/components/WhatChangesSection.tsx +++ b/src/components/WhatChangesSection.tsx @@ -2,7 +2,7 @@ import Section from "@/components/Section"; const changes = [ { - title: "Goals have evidence", + title: "Assertions have evidence", description: "Every assertion either has a passing test bound by a current lock file, or it doesn\u2019t. You always know which goals have evidence behind them and which don\u2019t.", }, diff --git a/src/components/WhyOutcomesSection.tsx b/src/components/WhyOutcomesSection.tsx index bfc3493..4db0e66 100644 --- a/src/components/WhyOutcomesSection.tsx +++ b/src/components/WhyOutcomesSection.tsx @@ -9,9 +9,10 @@ export default function WhyOutcomesSection() { to do, expressed as testable assertions.

- Every spec begins with an outcome hypothesis: the reason this node exists, expressed as a belief about what - change it will produce. Assertions define what must be true about the output for the hypothesis to become - verifiable. There is no outcome without an output. + Outcome specs begin with a hypothesis: by [output], [outcome], resulting in [impact]. Enabler specs begin with + what they enable. The output is what the software does — testable locally by assertions. The outcome is + the change in customer behavior we expect — measurable only with real users. The impact is the business + value: increase revenue, sustain revenue, reduce costs, or avoid costs. There is no outcome without an output.

Product managers and UX researchers already have the material — business goals, customer research, diff --git a/src/lib/story-data.tsx b/src/lib/story-data.tsx index d1fbe27..3b6fa98 100644 --- a/src/lib/story-data.tsx +++ b/src/lib/story-data.tsx @@ -111,7 +111,7 @@ export const specTreeSteps: StoryStepData[] = [ heading: "The product node anchors everything", body: [ "Every Spec Tree starts with a single product file at its root. This file captures what the product is and why it exists \u2014 the value function that every other node must trace back to.", - "When product learning changes, the spec still contains why it exists. Every outcome has evidence because it traces back to this root.", + "When product learning changes, the spec still contains why it exists. Every outcome traces to this root. Assertions validate outputs locally; outcome hypotheses are validated only through real customer behavior.", ], visual: tree([ { chrome: "", name: "spx/", nameClass: "st-product" }, @@ -125,7 +125,7 @@ export const specTreeSteps: StoryStepData[] = [ id: "decision-records", heading: "Decision records capture choices upfront", body: [ - "ADRs and PDRs sit alongside the specs they affect. Architecture decisions (ADR) and product decisions (PDR) are captured before implementation begins.", + "ADRs and PDRs sit alongside the specs they affect. ADRs capture architecture choices that constrain how outputs are produced; PDRs capture product constraints \u2014 pricing, compliance, retention \u2014 that must hold across a subtree.", "When an agent needs context, it doesn\u2019t guess \u2014 it reads the decision record on the path from root to its current node. Context is deterministic.", ], visual: tree([ @@ -141,7 +141,7 @@ export const specTreeSteps: StoryStepData[] = [ id: "enablers", heading: "Enablers build infrastructure bottom-up", body: [ - "Enabler nodes (marked with .enabler) are infrastructure \u2014 shared utilities, parsers, test harnesses. The numeric prefix encodes dependency order: lower numbers are dependencies for higher ones.", + "Enabler nodes (marked with .enabler) exist because at least one outcome node needs them \u2014 infrastructure that would be removed if all its dependents were retired. The numeric prefix encodes dependency order: lower numbers are dependencies for higher ones.", "Index 21 (test-harness) must be valid before index 32 (parse-directory-tree) can be worked on. The tree encodes this constraint in the filename.", ], visual: tree([ @@ -167,9 +167,9 @@ export const specTreeSteps: StoryStepData[] = [ }, { id: "outcomes", - heading: "Outcomes express testable hypotheses", + heading: "Outcomes state hypotheses about customer change", body: [ - "Outcome nodes (marked with .outcome) each begin with a belief about what change the software will produce \u2014 an outcome hypothesis with testable assertions.", + "Outcome nodes (marked with .outcome) each state an outcome hypothesis \u2014 by [output], [outcome], resulting in [impact]. Assertions test the output. The outcome is a change in customer behavior that only real users can validate. The impact is the business value \u2014 increase revenue, sustain revenue, reduce costs, or avoid costs.", "When a spec changes, its lock file hash breaks. Parent nodes inherit the worst child state. Staleness bubbles up to the root \u2014 nothing hides. Three states \u2014 valid, stale, needs work \u2014 tell you exactly where the product stands.", ], visual: tree([ From 9a46e059748e7c7afa4a4a3c5b1b533f447ee635 Mon Sep 17 00:00:00 2001 From: Simon Heimlicher Date: Tue, 3 Mar 2026 12:51:57 +0100 Subject: [PATCH 3/4] build: add Claude plugin config and refine gitignore rules Track .claude/settings.json for plugin enablement. Narrow .spx/ ignore to .spx/sessions/ so spec tree structure is tracked. Add .claude/settings.local.json to gitignore. --- .claude/settings.json | 6 ++++++ .gitignore | 9 +++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) create mode 100644 .claude/settings.json diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..68b737c --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,6 @@ +{ + "enabledPlugins": { + "frontend@spx-claude": true, + "typescript@spx-claude": true + } +} diff --git a/.gitignore b/.gitignore index a7c20c8..57f7b64 100644 --- a/.gitignore +++ b/.gitignore @@ -43,5 +43,10 @@ next-env.d.ts # tooling .playwright-mcp/ -.spx/ -/dist/ \ No newline at end of file +/dist/ + +# SPX +.spx/sessions/ + +# Claude Code +.claude/settings.local.json From bd164f9b40d614599cfdb02ea6b251029c2a276f Mon Sep 17 00:00:00 2001 From: Simon Heimlicher Date: Tue, 3 Mar 2026 13:06:44 +0100 Subject: [PATCH 4/4] fix: align WhatChangesSection description with title terminology Replace "goals" with "assertions" in the description body to match the updated "Assertions have evidence" title. --- src/components/WhatChangesSection.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/WhatChangesSection.tsx b/src/components/WhatChangesSection.tsx index df3bbca..36ff57e 100644 --- a/src/components/WhatChangesSection.tsx +++ b/src/components/WhatChangesSection.tsx @@ -4,7 +4,7 @@ const changes = [ { title: "Assertions have evidence", description: - "Every assertion either has a passing test bound by a current lock file, or it doesn\u2019t. You always know which goals have evidence behind them and which don\u2019t.", + "Every assertion either has a passing test bound by a current lock file, or it doesn\u2019t. You always know which assertions have evidence behind them and which don\u2019t.", }, { title: "Change propagates",