{"_id":"@artstorefronts/arthelper-nav","_rev":"13-3d23b439074c204635a53d525c0bb267","name":"@artstorefronts/arthelper-nav","dist-tags":{"latest":"0.1.0","alpha":"0.1.0-alpha.8"},"versions":{"0.1.0-alpha.0":{"name":"@artstorefronts/arthelper-nav","version":"0.1.0-alpha.0","license":"ISC","_id":"@artstorefronts/arthelper-nav@0.1.0-alpha.0","maintainers":[{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"}],"dist":{"shasum":"aa2dc96264785f8cd2d5750e8e8cf123a2f8e8ca","tarball":"https://registry.npmjs.org/@artstorefronts/arthelper-nav/-/arthelper-nav-0.1.0-alpha.0.tgz","fileCount":51,"integrity":"sha512-bMWenBZjowNjbsyAFz2aU085eliXwkTEFkjwFDdeLTE5d6cWQsxM0C3g4eQ0giFEFS07n+g2wGEN5raBp2qH2Q==","signatures":[{"sig":"MEQCIBXgLf92GHfulvgxsL4IlEEGOsKkTw6pVHvnQf1aOc6RAiB3ybjaH/oGw/PQBLFCmfIBxonkd1xNSW4oHIY6A208GA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":85841},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","gitHead":"fda9f12b9538d92efc1aa96ec95b3c6dbc51f493","scripts":{"test":"vitest --watch=false","build":"tsc && node --import tsx build.ts"},"_npmUser":{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"},"_npmVersion":"11.17.0","description":"The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher.","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"tsx":"^4.0.0","jsdom":"^26.1.0","react":"19.1.2","vitest":"^3.2.4","react-dom":"19.1.2","typescript":"^5.6.2","@types/react":"^19","lucide-react":"^0.446.0","@types/react-dom":"^19","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0","lucide-react":">=0.446.0"},"_npmOperationalInternal":{"tmp":"tmp/arthelper-nav_0.1.0-alpha.0_1787931975800_0.13552001863592755","host":"s3://npm-registry-packages-npm-production"}},"0.1.0-alpha.1":{"name":"@artstorefronts/arthelper-nav","version":"0.1.0-alpha.1","license":"ISC","_id":"@artstorefronts/arthelper-nav@0.1.0-alpha.1","maintainers":[{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"}],"dist":{"shasum":"4c73de562f850774594b2973885bd85071884240","tarball":"https://registry.npmjs.org/@artstorefronts/arthelper-nav/-/arthelper-nav-0.1.0-alpha.1.tgz","fileCount":51,"integrity":"sha512-t5WiiSwanK/REFCBwJtuJoXFcE8NwHiCmD1SkGlyLh5AfOlK80Qt+d1jZEIEXZqRpzRq++GmJEYrZEr4iJZhvw==","signatures":[{"sig":"MEUCIF5D2x9vzRVzq3M4Nnl6g/9/MquFcYxthXf9rKoHMPkGAiEAr026LsPwX+Nr2Bkk7+EazzDFdOkpbpBypqUM8lvG3Hk=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":90026},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","gitHead":"7fc39fcc7d5656a06909231045ef256bda01afd4","scripts":{"test":"vitest --watch=false","build":"tsc && node --import tsx build.ts"},"_npmUser":{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"},"_npmVersion":"11.17.0","description":"The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher.","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"tsx":"^4.0.0","jsdom":"^26.1.0","react":"19.1.2","vitest":"^3.2.4","react-dom":"19.1.2","typescript":"^5.6.2","@types/react":"^19","lucide-react":"^0.446.0","@types/react-dom":"^19","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0","lucide-react":">=0.446.0"},"_npmOperationalInternal":{"tmp":"tmp/arthelper-nav_0.1.0-alpha.1_1787938515590_0.5775183339263557","host":"s3://npm-registry-packages-npm-production"}},"0.1.0-alpha.2":{"name":"@artstorefronts/arthelper-nav","version":"0.1.0-alpha.2","license":"ISC","_id":"@artstorefronts/arthelper-nav@0.1.0-alpha.2","maintainers":[{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"}],"dist":{"shasum":"35470af90515dfa6aa6d2fab543e338c45625e3a","tarball":"https://registry.npmjs.org/@artstorefronts/arthelper-nav/-/arthelper-nav-0.1.0-alpha.2.tgz","fileCount":55,"integrity":"sha512-Wig4CTV0+B3ACH3zvm8fLg80Ozsx4qUO+tBg7LUszGP1R30BSz/q++TGCBkZoHVIuO/CB/dgw4eAq1iSHSsTGQ==","signatures":[{"sig":"MEQCIE65LR2gzrurAlUzgGJjXfla0hg3GaoClwWxQKUpBCCiAiAbm3zAHtWi/1768h1iXeMAFBlC6XDYSGxmHoN+URUfnQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":116161},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","gitHead":"7c07df67f11863aa8442c5279d84ed5ecc4c05b9","scripts":{"test":"vitest --watch=false","build":"tsc && node --import tsx build.ts"},"_npmUser":{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"},"_npmVersion":"11.17.0","description":"The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher.","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"tsx":"^4.0.0","jsdom":"^26.1.0","react":"19.1.2","vitest":"^3.2.4","react-dom":"19.1.2","typescript":"^5.6.2","@types/react":"^19","lucide-react":"^0.446.0","@types/react-dom":"^19","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0","lucide-react":">=0.446.0"},"_npmOperationalInternal":{"tmp":"tmp/arthelper-nav_0.1.0-alpha.2_1787951158897_0.6150155550478158","host":"s3://npm-registry-packages-npm-production"}},"0.1.0-alpha.3":{"name":"@artstorefronts/arthelper-nav","version":"0.1.0-alpha.3","license":"ISC","_id":"@artstorefronts/arthelper-nav@0.1.0-alpha.3","maintainers":[{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"}],"dist":{"shasum":"c0d222033de27354888ac9ad60baefa1f736004b","tarball":"https://registry.npmjs.org/@artstorefronts/arthelper-nav/-/arthelper-nav-0.1.0-alpha.3.tgz","fileCount":55,"integrity":"sha512-YlsNeGrnvddYBvSv1+TSBUS3QrQMSdu4RHMN7z0ucO/8J1D61MK9lwSanV+IE04afNm91I0LEMKQ+ie84JaM3w==","signatures":[{"sig":"MEUCIEH996fCYIKWJaYoSVsNNuVzTHU9Kblv32IPu+ToIpLdAiEAjf5VNTTxA8RZ1FpcshmnhAhO+Ws8s3nfT9vjSxZPTjI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":118075},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","gitHead":"0c3dd49aea6886c9d1f74212ce11c8ac8ebfa160","scripts":{"test":"vitest --watch=false","build":"tsc && node --import tsx build.ts"},"_npmUser":{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"},"_npmVersion":"11.17.0","description":"The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher.","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"tsx":"^4.0.0","jsdom":"^26.1.0","react":"19.1.2","vitest":"^3.2.4","react-dom":"19.1.2","typescript":"^5.6.2","@types/react":"^19","lucide-react":"^0.446.0","@types/react-dom":"^19","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0","lucide-react":">=0.446.0"},"_npmOperationalInternal":{"tmp":"tmp/arthelper-nav_0.1.0-alpha.3_1787951781720_0.1391610170948281","host":"s3://npm-registry-packages-npm-production"}},"0.1.0-alpha.4":{"name":"@artstorefronts/arthelper-nav","version":"0.1.0-alpha.4","license":"ISC","_id":"@artstorefronts/arthelper-nav@0.1.0-alpha.4","maintainers":[{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"}],"dist":{"shasum":"6faad1a31bb06aedda0c87d673472c26f25cf0a6","tarball":"https://registry.npmjs.org/@artstorefronts/arthelper-nav/-/arthelper-nav-0.1.0-alpha.4.tgz","fileCount":55,"integrity":"sha512-eVBwz7wUba04cTeQE3Xs+89kgQZqfWT3JFWnnXi4kJhmhwy8hCPhCCiCqT9rdz6C5hFoZ9prbgqQxlT4emLb2w==","signatures":[{"sig":"MEUCIQDyCyyqpHSP/gq8GcF/WTXYblMEPH9Ry4XRCmcLHqj6JQIgQbnZ9IteY06tEEAIsduhHXS8M0TGoE0jOe1PIiN9rHs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":119155},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","gitHead":"f4e64835ae697909e68ab3e5a508ab96bb54cfb0","scripts":{"test":"vitest --watch=false","build":"tsc && node --import tsx build.ts"},"_npmUser":{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"},"_npmVersion":"11.17.0","description":"The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher.","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"tsx":"^4.0.0","jsdom":"^26.1.0","react":"19.1.2","vitest":"^3.2.4","react-dom":"19.1.2","typescript":"^5.6.2","@types/react":"^19","lucide-react":"^0.446.0","@types/react-dom":"^19","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0","lucide-react":">=0.446.0"},"_npmOperationalInternal":{"tmp":"tmp/arthelper-nav_0.1.0-alpha.4_1787952927746_0.4661468364922776","host":"s3://npm-registry-packages-npm-production"}},"0.1.0-alpha.5":{"name":"@artstorefronts/arthelper-nav","version":"0.1.0-alpha.5","license":"ISC","_id":"@artstorefronts/arthelper-nav@0.1.0-alpha.5","maintainers":[{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"}],"dist":{"shasum":"5eb328d48ab719010c1c25eec1e5c88bee90a823","tarball":"https://registry.npmjs.org/@artstorefronts/arthelper-nav/-/arthelper-nav-0.1.0-alpha.5.tgz","fileCount":55,"integrity":"sha512-ZqiUtdcxYPXh8Nxg9QSFaqamxq+t88pR6GgM7Ad3Ivthplh/UqLFlhjWIqbVvuUIpOgC8R+0F+aYFcrThM7Akg==","signatures":[{"sig":"MEUCIDdBbh+r4Bse0KpwiICSr9eCXj3UDzKFZQZbfvKPTdQNAiEA1oGTXd/T8hPh1zNy05Cx2bSZHLxPTJTdbcRHyqQpe78=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":118807},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","gitHead":"fd49c0d504d29eb151a41bf8fb3f02f90901f610","scripts":{"test":"vitest --watch=false","build":"tsc && node --import tsx build.ts"},"_npmUser":{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"},"_npmVersion":"11.17.0","description":"The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher.","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"tsx":"^4.0.0","jsdom":"^26.1.0","react":"19.1.2","vitest":"^3.2.4","react-dom":"19.1.2","typescript":"^5.6.2","@types/react":"^19","lucide-react":"^0.446.0","@types/react-dom":"^19","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0","lucide-react":">=0.446.0"},"_npmOperationalInternal":{"tmp":"tmp/arthelper-nav_0.1.0-alpha.5_1787953274798_0.45755785743108723","host":"s3://npm-registry-packages-npm-production"}},"0.1.0-alpha.6":{"name":"@artstorefronts/arthelper-nav","version":"0.1.0-alpha.6","license":"ISC","_id":"@artstorefronts/arthelper-nav@0.1.0-alpha.6","maintainers":[{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"}],"dist":{"shasum":"02d9992ce3b58b211ce0ef411280fab0e1d1fe7c","tarball":"https://registry.npmjs.org/@artstorefronts/arthelper-nav/-/arthelper-nav-0.1.0-alpha.6.tgz","fileCount":55,"integrity":"sha512-Q0uJ8Sqvv2pC3dHilYpBe+NirDs/v8nymHC1AawO0nyN10fFZxL8OjxZJv4j0+vaFJh5w21jYSmNFE70syNpgw==","signatures":[{"sig":"MEUCIQCioIp1+A4N0zH6CgxN1wdiRvKB8kN4zpUxBBoJZKTWdAIgbRpt1N2LgcOCPfYWv+WXAew6eh+gArDrdFaWJXSyvys=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":118677},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","gitHead":"0e6b20b5b440e3229792d59a1d1a36074ca62a3f","scripts":{"test":"vitest --watch=false","build":"tsc && node --import tsx build.ts"},"_npmUser":{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"},"_npmVersion":"11.17.0","description":"The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher.","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"tsx":"^4.0.0","jsdom":"^26.1.0","react":"19.1.2","vitest":"^3.2.4","react-dom":"19.1.2","typescript":"^5.6.2","@types/react":"^19","lucide-react":"^0.446.0","@types/react-dom":"^19","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0","lucide-react":">=0.446.0"},"_npmOperationalInternal":{"tmp":"tmp/arthelper-nav_0.1.0-alpha.6_1787957841234_0.17138764131367168","host":"s3://npm-registry-packages-npm-production"}},"0.1.0-alpha.7":{"name":"@artstorefronts/arthelper-nav","version":"0.1.0-alpha.7","license":"ISC","_id":"@artstorefronts/arthelper-nav@0.1.0-alpha.7","maintainers":[{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"}],"dist":{"shasum":"66a56f5b78893cabc858a57165adeed7f0e14ecd","tarball":"https://registry.npmjs.org/@artstorefronts/arthelper-nav/-/arthelper-nav-0.1.0-alpha.7.tgz","fileCount":59,"integrity":"sha512-uQNe0F7JlFQ/USXvnH/N/FSfVzuxdWD3kiMGrvp/ASGqqyMul3IdOEVvhns1gyboyd1C/Ojtgwd3ST7RtCRBDw==","signatures":[{"sig":"MEUCICCpXQDeHvt+r9yZaQC0zWsy4Tx90aMzG1458vct/6tfAiEA7ZQvzh7y6FiRMVoY+GZFUkcJ79xjQ7wt762WnKcwLR4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":125069},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","gitHead":"3e189dc37a23f0c8c3d3dca566ec193d1ea2d0e1","scripts":{"test":"vitest --watch=false","build":"tsc && node --import tsx build.ts"},"_npmUser":{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"},"_npmVersion":"11.17.0","description":"The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher.","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"tsx":"^4.0.0","jsdom":"^26.1.0","react":"19.1.2","vitest":"^3.2.4","react-dom":"19.1.2","typescript":"^5.6.2","@types/react":"^19","lucide-react":"^0.446.0","@types/react-dom":"^19","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0","lucide-react":">=0.446.0"},"_npmOperationalInternal":{"tmp":"tmp/arthelper-nav_0.1.0-alpha.7_1788015359515_0.5906922966969954","host":"s3://npm-registry-packages-npm-production"}},"0.1.0-alpha.8":{"name":"@artstorefronts/arthelper-nav","version":"0.1.0-alpha.8","license":"ISC","_id":"@artstorefronts/arthelper-nav@0.1.0-alpha.8","maintainers":[{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"}],"dist":{"shasum":"ec0040a71006eb12d05916041ab2a51aaf759c54","tarball":"https://registry.npmjs.org/@artstorefronts/arthelper-nav/-/arthelper-nav-0.1.0-alpha.8.tgz","fileCount":59,"integrity":"sha512-Ruyu0bUUn0IF6ocY6vvdpQBbMw6SlR6eWkKr97Da4ca26lvbyHzsCaROyOdqKRTHPAYUgNAYBU0P6sbPbIEGLg==","signatures":[{"sig":"MEQCIDNDNQZHj5JJSNnc3TD8Blq4mCrdrrcjvf6jpcgUVfp0AiBpffgWtrPxE4Cm5hx5qq1Zq0JqQMjrn0Jpl3813uGtIQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":128209},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","module":"dist/index.js","gitHead":"d3cb63f8af7d9dde8ec6ef5ddf40c43160f51a8a","scripts":{"test":"vitest --watch=false","build":"tsc && node --import tsx build.ts"},"_npmUser":{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"},"_npmVersion":"11.17.0","description":"The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher.","directories":{},"_nodeVersion":"24.19.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"readmeFilename":"README.md","devDependencies":{"tsx":"^4.0.0","jsdom":"^26.1.0","react":"19.1.2","vitest":"^3.2.4","react-dom":"19.1.2","typescript":"^5.6.2","@types/react":"^19","lucide-react":"^0.446.0","@types/react-dom":"^19","@testing-library/dom":"^10.4.0","@testing-library/react":"^16.3.2"},"peerDependencies":{"react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0","lucide-react":">=0.446.0"},"_npmOperationalInternal":{"tmp":"tmp/arthelper-nav_0.1.0-alpha.8_1788445219114_0.1476647587035138","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@artstorefronts/arthelper-nav","version":"0.1.0","description":"The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher.","license":"ISC","type":"module","main":"dist/index.js","module":"dist/index.js","types":"dist/index.d.ts","publishConfig":{"access":"public"},"scripts":{"build":"tsc && node --import tsx build.ts","test":"vitest --watch=false"},"peerDependencies":{"lucide-react":">=0.446.0","react":"^18.0.0 || ^19.0.0","react-dom":"^18.0.0 || ^19.0.0"},"devDependencies":{"@testing-library/dom":"^10.4.0","@testing-library/react":"^16.3.2","@types/react":"^19","@types/react-dom":"^19","jsdom":"^26.1.0","lucide-react":"^0.446.0","react":"19.1.2","react-dom":"19.1.2","tsx":"^4.0.0","typescript":"^5.6.2","vitest":"^3.2.4"},"gitHead":"202ef79fb59f3bb4a0026538340dbd83aac5f0c2","_id":"@artstorefronts/arthelper-nav@0.1.0","_nodeVersion":"24.20.0","_npmVersion":"11.19.0","dist":{"integrity":"sha512-y3Zb21HJu0OKayXkH9t08/a33wQdkPo9H37FE4RJ0KBQ5k7AbyfiXpzh4CFK36SyxX8X26bZ8fJofuttkKQSUg==","shasum":"6fe70a10e8b1de95b46d20269ae5a519f011b1ab","tarball":"https://registry.npmjs.org/@artstorefronts/arthelper-nav/-/arthelper-nav-0.1.0.tgz","fileCount":55,"unpackedSize":126090,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCRUhfl2w35kAaqQidIiwpeUbuK+NiuYk5ZVZbv3Y+KagIhAPrh/h0Yy//cjqmxcEvsDmU/sEuewEIz9IBjXZ3PqzXL"}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:fa0c3918-e1f0-4988-9c1e-c53da35be255"}},"directories":{},"maintainers":[{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/arthelper-nav_0.1.0_1788480497021_0.9745673548829878"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-28T15:46:15.623Z","modified":"2026-09-04T00:08:17.322Z","0.1.0-alpha.0":"2026-08-28T15:46:15.959Z","0.1.0-alpha.1":"2026-08-28T17:35:15.753Z","0.1.0-alpha.2":"2026-08-28T21:05:59.072Z","0.1.0-alpha.3":"2026-08-28T21:16:21.872Z","0.1.0-alpha.4":"2026-08-28T21:35:27.897Z","0.1.0-alpha.5":"2026-08-28T21:41:14.921Z","0.1.0-alpha.6":"2026-08-28T22:57:21.360Z","0.1.0-alpha.7":"2026-08-29T14:55:59.662Z","0.1.0-alpha.8":"2026-09-03T14:20:19.257Z","0.1.0":"2026-09-04T00:08:17.156Z"},"license":"ISC","description":"The ArtHelper suite navigation bar — a config-driven, cross-origin app switcher.","maintainers":[{"name":"mattmillsxyz","email":"mattmillsxyz@gmail.com"}],"readme":"# @artstorefronts/arthelper-nav\n\nThe ArtHelper suite navigation bar — a config-driven, cross-origin app switcher for the\nsatellite apps in the ArtHelper suite.\n\nThis document is for an engineer wiring the bar into a host app that lives **outside**\nthis monorepo. If you're working inside this repo, see `docs/llms/suite-nav.md` for how\nthe pieces fit together end to end.\n\n## Agent implementation prompt\n\nCopy the block below and hand it to a coding agent working in the host app. It is written\nto be self-contained apart from this README, which it tells the agent to read — the agent\nwill have it locally once the package is installed.\n\n````markdown\n# Task: mount the ArtHelper suite nav bar in this app\n\nThis app is part of the ArtHelper suite. Add the shared suite bar so the suite reads as one\nproduct. The bar is a published package — you are wiring it in, not building it.\n\n## First\n\n```bash\nnpm install @artstorefronts/arthelper-nav\n```\n\nThen read `node_modules/@artstorefronts/arthelper-nav/README.md` in full before writing any\ncode. It is the authoritative API reference and explains the layout contract, the collapsed\ncookie, persistence and degradation behaviour. Peer dependencies are `react`, `react-dom`\nand `lucide-react` — install them yourself if this app doesn't already have them.\n\n## Find this app's catalog id\n\n`activeAppId` must be the id the shared catalog uses for this app — not this repo's name.\nList them:\n\n```bash\ncurl -s https://api.arthelper.ai/rest/nav-config | jq -r '.apps[].id'\n```\n\nPick the one that is this app and use it verbatim. A wrong value means the bar cannot\nindicate where the user is and the collapse control has no chip to dock. If none of the ids\nis obviously this app, stop and ask — an id is added by an operator, not by you.\n\n## Wire it in\n\n1. **Import the stylesheet** by deep path in your global CSS entry:\n   `@import '@artstorefronts/arthelper-nav/dist/index.css';`\n   Under Tailwind v4 every `@import` must precede other rules. The CSS is scoped with an\n   `ahsuite-` prefix and uses no Tailwind utilities, so it needs no `@source` entry and\n   cannot be purged.\n\n2. **Wrap the app root in `NavProvider`**, passing `configUrl` and `activeAppId`.\n\n3. **Render `NavBar` as the first child of the app shell, in normal document flow.** It\n   occupies real layout space; the page should shift down exactly once, at first paint.\n   Never wrap it in a fixed/absolute container and never portal it.\n\n4. **Put `NavTrigger` in this app's own header, beside its logo.** This is not optional:\n   when the bar is collapsed, `NavTrigger` is the only way to restore it. Ship without it\n   and a user who collapses the bar can never get it back. It hides itself while the bar is\n   expanded, so you just place it.\n\n5. **If this app server-renders**, read the collapsed cookie on the server and pass it as\n   `initialCollapsed`. Import the cookie's name from the package rather than retyping it.\n   Without this, a user who collapsed the bar gets an expanded bar in the first byte and a\n   56px jump after hydration.\n\n6. **Pass `onAppClick`** so this app's clicks reach the fleet-wide chart. The package ships\n   no analytics SDK — transport, identity and consent stay with you — but it does export the\n   event's shape, so use it rather than hand-writing the payload:\n\n   ```ts\n   import { suiteNavClickEvent } from '@artstorefronts/arthelper-nav';\n\n   onAppClick={(args) => yourTracker.track(...suiteNavClickEvent(args, '<this-app-id>'))}\n   ```\n\n   Hand-writing it is how a satellite ends up sending `app_id` instead of `target_app_id`,\n   which does not fail — it silently splits the chart. One callback covers both the desktop\n   tiles and the mobile dropdown. If your tracker batches, flush on the way out: a tile is a\n   real cross-app `<a href>`, so the document is replaced and a queued batch dies with it.\n\n## `configUrl` — the mistake to avoid\n\nIt is the ArtHelper API's **REST** endpoint, ending `/rest/nav-config`:\n\n- production: `https://api.arthelper.ai/rest/nav-config`\n- local: your ArtHelper stack's API port, same path\n\nRead it from this app's environment config with a sensible default; do not hardcode one\nvalue. **It is not the GraphQL endpoint.** Pointing it at `/graphql` returns 404 and the bar\nfalls back _silently_ to its compiled-in catalog — no console error, no visible failure,\njust a bar that never reflects the admin screen. If tiles never update, curl the URL before\ndebugging anything else.\n\nThe endpoint is public, unauthenticated and CORS-open. **Do not send credentials** — the\nresponse carries a literal `Access-Control-Allow-Origin: *`, which browsers reject for\ncredentialed requests.\n\n## Sticky, and the audit that goes with it\n\nDecide whether the bar is sticky:\n\n- **This app's document scrolls** (a normal page): use `<NavBar sticky />`, and offset your\n  own sticky header's `top` by the bar's height. **That offset must react to collapse** —\n  read `collapsed` from `useNav()` and use `collapsed ? 0 : <bar height>`. A constant offset\n  leaves a visible void at scroll-top once the bar collapses.\n- **The shell is a fixed-height flex column** that never scrolls the document: do not pass\n  `sticky` and change no offsets.\n\n**Then audit the whole app for surfaces that assume the header's bottom edge, or that they\ncan occupy the full viewport height.** Grep for `top-14`, `top: 3.5rem`, `h-screen`,\n`h-svh`, `h-dvh`, `100vh`, `100svh`, `100dvh`, and `calc(100vh - N)` patterns. Each one is\nnow short by the bar's height while the bar is expanded, and will sit _behind_ your header\nor run off the bottom of the screen.\n\nThis is the single largest source of breakage when adopting the bar. The two apps inside the\nArtHelper monorepo needed **nine** such fixes between them, including a `position: fixed`\nsidebar rail, two pages subtracting a hardcoded pixel count, and an overlay panel rendered\nthrough a portal that could not inherit a CSS variable at all. Derive them all from one\nshared value rather than sprinkling `collapsed ?` checks through components, and remember\nthat a portalled surface may need the value passed to it another way.\n\nJudge each hit: a surface inside a scroll container that never touches the viewport edge is\nunaffected, and a modal meant to cover the chrome should keep covering it. Say which you\nchanged and which you deliberately did not.\n\n## What is not configurable\n\nThe bar's **look** — colour, alignment, accent, the wordmark — is compiled into the package,\nnot served in the payload. Do not look for theme props or try to restyle it from the host;\nchanging it is a package release. Only the app catalog is configurable.\n\n## Expected behaviour, so you don't chase non-bugs\n\n- **Only some apps appear.** An app renders only once an operator has both enabled it and\n  given it a destination in ArtHelper's admin screen.\n- **This app's own chip always renders**, even if its catalog entry is disabled or has no\n  destination. With no destination it is a non-navigating indicator chip. That is correct —\n  it tells the user where they are.\n- **The bar survives an endpoint outage** by falling back to a catalog compiled into the\n  package, so it renders at full height rather than blank and never blocks first paint.\n- **The catalog is persisted** after the first successful fetch, so repeat loads paint the\n  last known catalog rather than the compiled-in one.\n\n## Verify in a real browser, not by reasoning\n\n1. The bar renders above your header; measure its height with `getBoundingClientRect()` and\n   confirm it matches the documented height exactly.\n2. Collapse it: height 0, your header rises to the very top with no gap, chip appears beside\n   your logo. Restore it: back to full height.\n3. Reload while collapsed — it stays collapsed.\n4. Click another app's tile — the address bar becomes that destination.\n5. If sticky: scroll a few hundred pixels in both bar states and confirm your header is\n   neither covered nor floating above a void.\n6. Below the `md` breakpoint the tiles become a labelled dropdown; every app is reachable and\n   it dismisses without leaving the page.\n7. Block the config endpoint in devtools — the bar still renders at full height with this\n   app's own chip, not blank.\n8. If this app server-renders, check the console for hydration warnings. There must be none.\n\n## Do not\n\n- Do not fork, vendor or patch the package. Report upstream problems instead.\n- Do not hardcode any app's destination — destinations are config.\n- Do not infer `activeAppId` from the URL; the host declares it.\n- Do not send credentials with the config request.\n- Do not render a second bar if this app is ever framed inside another ArtHelper surface.\n- Avoid `yarn link` unless necessary; if you use it, make sure the bundler resolves a single\n  copy of React or you will hit the duplicate-React hooks error. `npm pack` plus installing\n  the tarball avoids that and resolves more like the real thing.\n````\n\n## Architecture in one line\n\nConfigure the catalog in data, ship everything else in code. The **app catalog** lives in\na single database row, served as JSON over a public HTTP endpoint. Every mounted host\nfetches that JSON on load, so a catalog edit made in the admin editor is live for every\napp in the suite on their next page load — no deploy, no version bump, no coordinated\nrelease across repos.\n\nThe payload is `{ schema_version, apps }` and nothing else. The bar's look and its\nwordmark are compiled in, on purpose — see [Theming and the wordmark](#theming-and-the-wordmark).\nThe last good catalog is persisted per browser so a return visit paints it rather than the\ncompiled-in fallback — see [Persistence](#persistence).\n\n## Install\n\n```bash\nnpm install @artstorefronts/arthelper-nav\n```\n\nPeer dependencies (from `package.json` — install these yourself, they are not bundled):\n\n```json\n{\n  \"lucide-react\": \">=0.446.0\",\n  \"react\": \"^18.0.0 || ^19.0.0\",\n  \"react-dom\": \"^18.0.0 || ^19.0.0\"\n}\n```\n\n> **Need a change to the bar itself?** Its look, layout and behaviour ship in this package\n> rather than being configurable — see [Theming and the wordmark](#theming-and-the-wordmark)\n> — so changing them means a new release. Ask the ArtHelper team; releases are automatic and\n> quick. What you _can_ change without one is which apps appear, in what order and pointing\n> where: that lives in the config row, editable from ArtHelper's admin screen and picked up\n> on next load.\n\n## Minimal mounting example\n\n```tsx\n// Import the stylesheet by its deep path — it is not re-exported from the package root.\nimport '@artstorefronts/arthelper-nav/dist/index.css';\nimport { NavProvider, NavBar, NavTrigger } from '@artstorefronts/arthelper-nav';\n\nfunction AppRoot() {\n  return (\n    <NavProvider activeAppId=\"my-app\" configUrl=\"https://api.arthelper.com/rest/nav-config\">\n      {/* NavBar first, so it occupies real layout space at the top of the shell. */}\n      <NavBar />\n      <AppHeader />\n      <AppContent />\n    </NavProvider>\n  );\n}\n\nfunction AppHeader() {\n  return (\n    <header>\n      <Logo />\n      {/* NavTrigger is the RESTORE control. Omit it and a viewer who collapses the\n          bar has no way to bring it back. */}\n      <NavTrigger />\n    </header>\n  );\n}\n```\n\n`NavProvider` renders no DOM of its own — `NavBar` and whatever else you nest inside it\nstay direct children of your own layout, so the 56px the bar reserves comes purely from\n`NavBar` itself, not from a wrapper.\n\n## Components and props\n\n### `NavProvider`\n\nThe context host. Fetches config, resolves the app list, and owns collapsed state. Must\nwrap `NavBar`, `NavTrigger`, and any component calling `useNav()`.\n\n| Prop               | Type                              | Required | Description                                                                                                                                                                    |\n| ------------------ | --------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `configUrl`        | `string`                          | yes      | Full URL of the public nav-config endpoint. See [Deriving `configUrl`](#deriving-configurl) below.                                                                             |\n| `activeAppId`      | `string`                          | yes      | The catalog id of the app the host itself is. Never inferred from the URL or the payload — the host always declares it.                                                        |\n| `onAppClick`       | `(args: NavAppClickArgs) => void` | no       | Fired immediately before navigation. The package's only analytics seam — see [`onAppClick`](#onappclick-the-analytics-seam).                                                   |\n| `fallbackConfig`   | `NavConfigPayload`                | no       | Overrides the package's compiled-in fallback, e.g. for a satellite with a different first-party destination set.                                                               |\n| `initialCollapsed` | `boolean`                         | no       | The collapsed cookie value a server-rendered host already read, so the first byte paints the correct state. See [SSR and the collapsed cookie](#ssr-and-the-collapsed-cookie). |\n| `children`         | `ReactNode`                       | no       | —                                                                                                                                                                              |\n\n### `NavBar`\n\nRenders the bar itself: waffle toggle, wordmark, the desktop tile row (or, below the\n768px breakpoint, a labelled dropdown trigger and panel with the same entries as rows).\n\n| Prop        | Type      | Default | Description                                                                            |\n| ----------- | --------- | ------- | -------------------------------------------------------------------------------------- |\n| `sticky`    | `boolean` | `false` | Opt in only for a host whose document actually scrolls. See [`sticky`](#sticky) below. |\n| `className` | `string`  | —       | Appended to the bar's root class list.                                                 |\n\n### `NavTrigger`\n\nThe docked waffle-plus-active-app chip a host places in its own header, next to its own\nlogo. This is the **restore** control: while the bar is collapsed (`NavBar` reserves\nnothing and is out of the accessibility tree), `NavTrigger` is the only way back. It is\n`display: none` while the bar is expanded. A host that mounts `NavBar` without also\nmounting `NavTrigger` ships a bar a viewer can collapse and never recover.\n\n| Prop        | Type     | Default | Description                                |\n| ----------- | -------- | ------- | ------------------------------------------ |\n| `className` | `string` | —       | Appended to the trigger's root class list. |\n\n### Exported types\n\n`NavContextValue`, `NavBarProps`, `NavTriggerProps`, `NavProviderProps`, `NavApp`,\n`NavAppClickArgs`, `NavConfigPayload`, `ResolvedNavApp` — all exported from the package root (`src/index.ts`), also useful if\nyou're typing `fallbackConfig` or `onAppClick` yourself.\n\n`useNav()` is also exported, for a component nested under `NavProvider` that needs\n`collapsed`, `toggle`, `activeApp`, or `apps` directly — the two host mounts in this\nmonorepo use it, for example, to publish the bar's live height as a CSS variable.\n\n## `activeAppId`\n\nThe host is always the one source of truth for which app it is. `NavProvider` never\ninfers this from the current URL and never reads it from the config payload — you pass\nit explicitly, and it must match one of the `id`s in the nav-config app catalog (ask the\nArtHelper team for your app's id, or check the payload at `GET /rest/nav-config`).\n\nThat entry is always rendered and indicated (its tile carries the active styling) even if\nthe payload has it disabled or omits a destination for it. If your own app has no `href`\nin the catalog yet, its tile renders as an inert indicator instead of a link — it never\ndisappears from the bar.\n\n## Deriving `configUrl`\n\nNo new environment variable is needed. Derive it from whatever ArtHelper API base URL\nyour app already has, by swapping the `graphql` path segment for `rest` and appending\n`/nav-config`:\n\n```ts\nconst NAV_CONFIG_URL = `${ARTHELPER_API_BASE.replace('graphql', 'rest')}/nav-config`;\n// e.g. https://api.arthelper.com/graphql -> https://api.arthelper.com/rest/nav-config\n```\n\nThis is the same derivation both in-monorepo hosts (`client/`, `town-square/`) use.\n\n## `onAppClick`: the analytics seam\n\n`onAppClick` fires immediately before navigation, from one shared handler behind both the\ndesktop tile row and the mobile dropdown — so it covers both surfaces and cannot\ndouble-fire.\n\n**The package ships no analytics SDK, deliberately.** Bundling one would run a second\ninstance alongside yours, racing it on device id and session, and would emit without your\nidentity or consent handling — producing clicks that cannot be joined to users. Transport,\nidentity and consent are yours.\n\nWhat the package does own is the event's shape, because that is the part that drifts:\n\n```ts\nimport { suiteNavClickEvent } from '@artstorefronts/arthelper-nav';\n\n<NavProvider\n  configUrl={NAV_CONFIG_URL}\n  activeAppId=\"art-books\"\n  onAppClick={(args) => amplitude.track(...suiteNavClickEvent(args, 'art_books'))}\n>\n```\n\nIt returns a `[name, properties]` pair, so the two cannot be paired wrongly. Properties are\n`target_app_id` (the app clicked — the dimension click-throughs group on), `source_app`\n(this app, passed in because hosts sharing a product surface share an `activeAppId` and so\ncannot be told apart by it), and `collapsed`.\n\n`SUITE_NAV_CLICK_EVENT` is exported too if your tracker needs the name separately.\n\n**If your tracker batches, flush on the way out.** A tile is a real cross-app `<a href>`, so\nthe document is replaced and a queued batch dies with it — both ArtHelper hosts use their\ntracker's on-exit variant for exactly this reason.\n\n## SSR and the collapsed cookie\n\nThe collapsed flag lives in a **cookie** (`arthelper_suite_nav_collapsed`), not\n`localStorage`. That's deliberate: a server-rendered host can read the cookie during SSR\nand pass its value in as `initialCollapsed`, so the very first byte the browser paints\nalready has the bar in the right state — no expanded-then-collapsed jump after\nhydration.\n\n```tsx\n// Next.js App Router example\nimport { cookies } from 'next/headers';\nimport { COLLAPSED_COOKIE } from '@artstorefronts/arthelper-nav';\n\nexport default async function RootLayout({ children }: { children: React.ReactNode }) {\n  const cookieStore = await cookies();\n  const initialCollapsed = cookieStore.get(COLLAPSED_COOKIE)?.value === 'true';\n\n  return (\n    <NavProvider activeAppId=\"my-app\" configUrl={NAV_CONFIG_URL} initialCollapsed={initialCollapsed}>\n      {children}\n    </NavProvider>\n  );\n}\n```\n\nThe cookie is **per-origin**. `app.example.com` and `www.example.com` are different\nregistrable domains and share no cookie scope, so collapsing the bar on one origin does\nnot sync to another — each origin's viewer sets their own collapsed state independently.\n\n## Theming and the wordmark\n\nThere is no theming, deliberately. The bar ships one look — dark \"Forest\", tiles centred\non the bar — baked into `dist/index.css`, and the wordmark (`arthelper`, linking to\n`https://www.arthelper.com`) is baked into the component. No part of either can be set by\na host or by the payload.\n\nBoth used to be configurable: the payload carried a `theme` block (`mode`,\n`accent_color`, `alignment`) and a `logo` block (`label`, `href`) that an admin edited in\nthe nav editor. That was a mistake, and the reason generalizes to every configurable\nfield. The bar paints the package's compiled-in fallback before the fetch resolves and\nswaps in the served config afterwards, so **any** configured value that disagrees with the\nfallback's guess produces a visible change on every page load — a dark bar turning light,\ntiles jumping from left to centre, a wordmark rewriting itself. Colour, layout and the\nwordmark are release decisions, reviewed once and then stable. The app catalog is the one\ngenuine operational switch, and it is all the payload carries now.\n\nThe wordmark's href is the site root, not the resolved `home` catalog entry. Deriving it\nwas considered and rejected: it would put a static piece of chrome back on config, and\n`home` points at `/community` while the wordmark has always gone to the root.\n\nChanging the look or the wordmark now means a package release. A host that wants the bar\non differently-coloured chrome should change its own surrounding chrome, not the bar.\n\nA payload that still carries `theme` or `logo` has them dropped during normalization like\nany other unrecognized field, and an older renderer that still expects them degrades to\nits own compiled-in defaults rather than throwing.\n\n## Persistence\n\nThe catalog is the one field that can still differ from the compiled-in fallback, and\nbecause the tile row is centred on the bar, that difference is expensive: every tile the\nfetch adds or removes shifts the whole row by half a tile, so a two-tile fallback resolving\nto a seven-tile config moves the Home tile 230px.\n\nSo a successful fetch persists the normalized payload to `localStorage` under\n`ahsuite-nav-config-v1`, and on mount the bar prefers that over the compiled-in fallback.\nThe fallback becomes the first-ever-visit case.\n\nThings worth knowing if you are wiring this into a host:\n\n- **The persisted blob is re-validated on read**, through the same `normalize` a network\n  body goes through. A blob written months ago by an older renderer is untrusted input.\n- **Entries expire after 30 days**, so a long-dormant browser revalidates rather than\n  painting something ancient.\n- **Every storage access is wrapped**, including the `localStorage` property read itself —\n  Safari's private mode and Chrome with site data blocked throw on _access_, not just on\n  write. An unreachable store simply means the fallback paints.\n- **It cannot help a server-rendered host's first frame.** The server has no access to the\n  viewer's `localStorage`, so its HTML is always the compiled-in fallback. The read runs in\n  a client-only layout effect — before the browser paints, after hydration has committed —\n  which removes the hydration-time swap but not the SSR one. If that first frame matters to\n  you, pass the catalog you want server-rendered as `fallbackConfig`.\n\n## The layout contract\n\n`NavBar` reserves exactly 56px from first paint, using the package's compiled-in\nfallback config while the real payload is in flight — so config arrival never shifts the\npage. Collapsing the bar (`toggle()`, or the waffle button, or `NavTrigger`) removes it\nfrom layout **entirely**: height goes to 0 and the 56px is handed back to the page, not\njust visually hidden.\n\n## `sticky`\n\nPass `sticky` only when your host's own document actually scrolls (e.g. a normal page\nlayout) — in that case the bar should stick to the top of the viewport as the page\nscrolls beneath it. Leave it `false` (the default) for a host that is a fixed-height flex\ncolumn whose document itself never scrolls (only an inner pane does) — sticky\npositioning there is dead weight.\n\nIf your host is `sticky` and also has its own sticky header below the bar, that header's\ntop offset has to account for the bar's height — **and that offset has to react to\ncollapse**, or a viewer who collapses the bar is left with a 56px void at the top of the\npage where your header no longer starts. Read `collapsed` from `useNav()` and publish the\nlive height (0 collapsed, 56px expanded) as a CSS variable your header's `top` reads,\nrather than hardcoding 56px once.\n\n## Degradation\n\nThe bar is designed to never render blank and never block first paint:\n\n- **Endpoint outage or timeout** — the fetch has a hard 2.5s timeout; on any failure\n  (network error, timeout, non-2xx, unparseable body) the bar falls back to its\n  compiled-in config and keeps whatever it was already showing. No visible error, no\n  console noise by default.\n- **Unknown fields** — anything in the payload the renderer doesn't recognize is dropped\n  during normalization, never carried through and never thrown on.\n- **A higher `schema_version` than this renderer understands** — ignored the same way;\n  the renderer keeps working on the fields it does know.\n- **A corrupt, stale or unreachable `localStorage`** — treated as \"nothing persisted\": the\n  compiled-in fallback paints and the fetch revalidates. Never throws.\n\n## The `schema_version` contract\n\nChanges to the wire payload are **additive within a version**: fields are only ever\nadded, never removed or repurposed, for a given `schema_version`. A renderer must ignore\nfields it doesn't recognize rather than throw. That's what lets the suite's\nasynchronously-deployed satellite fleet run mixed renderer versions against one shared\npayload — an older `@artstorefronts/arthelper-nav` keeps working after a newer field ships on the\nconfig row. The version number itself is bumped only for a genuinely breaking change.\n\n**The one exception so far:** `theme` was _removed_ from the payload without bumping\n`schema_version` off 1. Removal is breaking under this contract, and it was allowed only\nbecause no stable version had ever been released against the old shape — the two\n`0.1.0-alpha.*` publishes both tolerate the field's absence and degrade rather than\nthrow, which was verified against the actual tarballs. After the first stable publish the\nsame removal would require a version bump and a transition period.\n","readmeFilename":"README.md"}