{"_id":"@cuongdc1989/design-contract","_rev":"13-7babb898ea36c5336cbe6dae0e408aab","name":"@cuongdc1989/design-contract","dist-tags":{"latest":"1.4.0"},"versions":{"1.0.0":{"name":"@cuongdc1989/design-contract","version":"1.0.0","keywords":["design-contract","figma","storybook","visual-testing"],"license":"MIT","_id":"@cuongdc1989/design-contract@1.0.0","maintainers":[{"name":"cuongdc1989","email":"cuongdc@solashi.com"}],"bin":{"design-contract":"bin/cli.mjs"},"dist":{"shasum":"adf639750fcec20559b184de1329945ea48500c7","tarball":"https://registry.npmjs.org/@cuongdc1989/design-contract/-/design-contract-1.0.0.tgz","fileCount":10,"integrity":"sha512-i9zhyuzwk2Pc6lR3VtdHoqhFA+bG0oP/PTcMKQnLe4nSM2TBNpkBnv2Z8MXd4IqqmVkRcwPB4D2AdKqPvxlTIg==","signatures":[{"sig":"MEQCIGbjGE+vSsuj3iESvoZrqSWoYGnfZazihVRSN6UbYRxQAiBGygJhkyt1LaM3nhoGQD00rnTpy0T9SH/wvX+JcQA09g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":28505},"main":"index.mjs","type":"module","engines":{"node":">=18"},"gitHead":"8af36d450779eed4f624c85723767029dd408fb7","_npmUser":{"name":"cuongdc1989","email":"cuongdc@solashi.com"},"_npmVersion":"10.8.2","description":"Design contract testing engine: compare Storybook CSS against Figma specs","directories":{},"_nodeVersion":"20.19.1","dependencies":{"dotenv":"^16.0.0","playwright":"^1.40.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/design-contract_1.0.0_1778568559779_0.6751196479972337","host":"s3://npm-registry-packages-npm-production"}},"1.3.0":{"name":"@cuongdc1989/design-contract","version":"1.3.0","keywords":["design-contract","figma","storybook","visual-testing"],"license":"MIT","_id":"@cuongdc1989/design-contract@1.3.0","maintainers":[{"name":"cuongdc1989","email":"cuongdc@solashi.com"}],"bin":{"design-contract":"bin/cli.mjs"},"dist":{"shasum":"c3d0c34ae2a99560f5dc77118d397bd47edaf904","tarball":"https://registry.npmjs.org/@cuongdc1989/design-contract/-/design-contract-1.3.0.tgz","fileCount":12,"integrity":"sha512-zgy621Nj2u2U3b82otZZ/4sa6dPh0yRCosai2GluDB++7icrOOgj39LC/2v3TVmlnpE3Q6R1nb0I7AZRCCCIpw==","signatures":[{"sig":"MEUCIHxtVMKGLGNujiI/1SIJqPa0xQK3XQ4R38o+OkxrZehqAiEA+8u47bzQ7pTFEodqSX643TC842UrtQ9vb4IERhf+XQs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":56194},"main":"index.mjs","type":"module","engines":{"node":">=18"},"gitHead":"5e842eaf2691983145defeaea78ede10511d033c","_npmUser":{"name":"cuongdc1989","email":"cuongdc@solashi.com"},"_npmVersion":"10.8.2","description":"Design contract testing engine: compare Storybook CSS against Figma specs","directories":{},"_nodeVersion":"20.19.1","dependencies":{"dotenv":"^16.0.0","playwright":"^1.40.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/design-contract_1.3.0_1778573089491_0.8652338265602779","host":"s3://npm-registry-packages-npm-production"}},"1.3.1":{"name":"@cuongdc1989/design-contract","version":"1.3.1","keywords":["design-contract","figma","storybook","visual-testing"],"license":"MIT","_id":"@cuongdc1989/design-contract@1.3.1","maintainers":[{"name":"cuongdc1989","email":"cuongdc@solashi.com"}],"bin":{"design-contract":"bin/cli.mjs"},"dist":{"shasum":"6a2f145476d2aeb925e1e5b61606ffccef3e7a28","tarball":"https://registry.npmjs.org/@cuongdc1989/design-contract/-/design-contract-1.3.1.tgz","fileCount":12,"integrity":"sha512-GPZ9mMy+bMXWZ7wyVDAxegmjUC8SXpWP3Uss6PWZ5mrFyrOz8sFFwphH9Zr8Q7HtoptbtUE3evHkR5rlbTAsUw==","signatures":[{"sig":"MEUCIB9r4ZlnryUj6J3SAhPB8GIDSXyjAdG8bMNJvzSvYjMeAiEAu2IN7SVXh2EFNLbZIKCgHqJRFYkj7+uiti7Ak6FrxPw=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":75438},"main":"index.mjs","type":"module","engines":{"node":">=18"},"gitHead":"2884155aa34555ff15d370414ac754b6e20fb2a5","_npmUser":{"name":"cuongdc1989","email":"cuongdc@solashi.com"},"_npmVersion":"10.8.2","description":"Design contract testing engine: compare Storybook CSS against Figma specs","directories":{},"_nodeVersion":"20.19.1","dependencies":{"dotenv":"^16.0.0","playwright":"^1.40.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/design-contract_1.3.1_1778637234412_0.9066040137250106","host":"s3://npm-registry-packages-npm-production"}},"1.3.2":{"name":"@cuongdc1989/design-contract","version":"1.3.2","keywords":["design-contract","figma","storybook","visual-testing"],"license":"MIT","_id":"@cuongdc1989/design-contract@1.3.2","maintainers":[{"name":"cuongdc1989","email":"cuongdc@solashi.com"}],"bin":{"design-contract":"bin/cli.mjs"},"dist":{"shasum":"b42f35745e31a59a3baf85129f2725df2ba6013f","tarball":"https://registry.npmjs.org/@cuongdc1989/design-contract/-/design-contract-1.3.2.tgz","fileCount":12,"integrity":"sha512-ake+UN5g4/qRwlPgY+oHOXvWIUmvpwzwC4cyLLdpvJHfPI4ENr69zUTFGVaZssBWfx1sYr1aleqzrTytxjQEdQ==","signatures":[{"sig":"MEUCIQDg/dELQSigWwZTqFJ5+QIgd5lpAbIzlTwEo2dr58C+dAIgUGR09//v9bQ0UwMlnDQjU/hB2g2fnZToCMioVoQDa+A=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":75651},"main":"index.mjs","type":"module","engines":{"node":">=18"},"gitHead":"01bc5039aa6c6fa9315d77d2f480bfd9046ddaef","_npmUser":{"name":"cuongdc1989","email":"cuongdc@solashi.com"},"_npmVersion":"10.8.2","description":"Design contract testing engine: compare Storybook CSS against Figma specs","directories":{},"_nodeVersion":"20.19.1","dependencies":{"dotenv":"^16.0.0","playwright":"^1.40.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/design-contract_1.3.2_1778640376240_0.9748323346542074","host":"s3://npm-registry-packages-npm-production"}},"1.3.3":{"name":"@cuongdc1989/design-contract","version":"1.3.3","keywords":["design-contract","figma","storybook","visual-testing"],"license":"MIT","_id":"@cuongdc1989/design-contract@1.3.3","maintainers":[{"name":"cuongdc1989","email":"cuongdc@solashi.com"}],"bin":{"design-contract":"bin/cli.mjs"},"dist":{"shasum":"9a78a580cff238fa833f89e80f48bd500c32018e","tarball":"https://registry.npmjs.org/@cuongdc1989/design-contract/-/design-contract-1.3.3.tgz","fileCount":12,"integrity":"sha512-EX6uiCIfFDvhbq0bZin1o7Y8rEgEBTGFWafXMawpgH4kpINBmr8onDAGGQP3cQN0VUpt1XwfEEG7HPoPhSEhMg==","signatures":[{"sig":"MEUCIQCa0LSSwBl1QfGmmpxAwGiyVyPpcJN1yc+/gMv5xs7jygIgek/mPRdoRW5tyu80olDL76jOLqRthGFANDOT7/itgbc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":79387},"main":"index.mjs","type":"module","engines":{"node":">=18"},"gitHead":"654574c0802faf01b7930846653ae537ed306fe9","_npmUser":{"name":"cuongdc1989","email":"cuongdc@solashi.com"},"_npmVersion":"10.8.2","description":"Design contract testing engine: compare Storybook CSS against Figma specs","directories":{},"_nodeVersion":"20.19.1","dependencies":{"dotenv":"^16.0.0","playwright":"^1.40.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/design-contract_1.3.3_1778652703985_0.24338576444755256","host":"s3://npm-registry-packages-npm-production"}},"1.3.4444444444444444":{"name":"@cuongdc1989/design-contract","version":"1.3.4444444444444444","keywords":["design-contract","figma","storybook","visual-testing"],"license":"MIT","_id":"@cuongdc1989/design-contract@1.3.4444444444444444","maintainers":[{"name":"cuongdc1989","email":"cuongdc@solashi.com"}],"bin":{"design-contract":"bin/cli.mjs"},"dist":{"shasum":"13a090ea8e38392d16e82c97d1aee3f284e00ae4","tarball":"https://registry.npmjs.org/@cuongdc1989/design-contract/-/design-contract-1.3.4444444444444444.tgz","fileCount":13,"integrity":"sha512-sR7BPu8ryfc/1uGDGYhLcseAF8BRlTBMWZUMXDaIlZKh5jTY7B76eYCF+1P+ofSsTktsikPIKuwVmc9dkz4AhA==","signatures":[{"sig":"MEUCIQDXJpVjp9Xp3+26S5ZBQce2y1upyslEE5PGelQ8izM7egIgP9mGDdW18uu2Pj+Rb9wkaHgMUmwumDBYCXvW4Qecuh0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":103074},"main":"index.mjs","type":"module","engines":{"node":">=18"},"gitHead":"f0b47cc45c2b7067b391fa5683ae40f64c4503b4","_npmUser":{"name":"cuongdc1989","email":"cuongdc@solashi.com"},"_npmVersion":"10.8.2","description":"Design contract testing engine: compare Storybook CSS against Figma specs","directories":{},"_nodeVersion":"20.19.1","dependencies":{"dotenv":"^16.0.0","playwright":"^1.40.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/design-contract_1.3.4444444444444444_1778666273400_0.1517273448010812","host":"s3://npm-registry-packages-npm-production"}},"1.3.5":{"name":"@cuongdc1989/design-contract","version":"1.3.5","keywords":["design-contract","figma","storybook","visual-testing"],"license":"MIT","_id":"@cuongdc1989/design-contract@1.3.5","maintainers":[{"name":"cuongdc1989","email":"cuongdc@solashi.com"}],"bin":{"design-contract":"bin/cli.mjs"},"dist":{"shasum":"7f5b3c8520c11d2e8657c667a9871ce7d492677b","tarball":"https://registry.npmjs.org/@cuongdc1989/design-contract/-/design-contract-1.3.5.tgz","fileCount":17,"integrity":"sha512-OtZ4B8cM6u5EVTZuf443WvhvZxKAJjTJ4Qy26X3cA7bTl3Q5/GCSRnJrFqIQYKfwJ6K6hTXRUH6ezG9KCMVtog==","signatures":[{"sig":"MEYCIQD+Zp607sOWT65fMBti+JxCVWWBgVUjpR+yMPChhtHY8QIhANYRg2C9dvO44m5ax29u/XVsg52BKLFYk+ISAGE/wpbY","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":133992},"main":"index.mjs","type":"module","engines":{"node":">=18"},"gitHead":"e2c13f8b6a66319f9a5ef20cee20a8e934c60fe9","_npmUser":{"name":"cuongdc1989","email":"cuongdc@solashi.com"},"_npmVersion":"10.8.2","description":"Design contract testing engine: compare Storybook CSS against Figma specs","directories":{},"_nodeVersion":"20.19.1","dependencies":{"dotenv":"^16.0.0","playwright":"^1.40.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/design-contract_1.3.5_1778728163839_0.3512135219746073","host":"s3://npm-registry-packages-npm-production"}},"1.3.6":{"name":"@cuongdc1989/design-contract","version":"1.3.6","keywords":["design-contract","figma","storybook","visual-testing"],"license":"MIT","_id":"@cuongdc1989/design-contract@1.3.6","maintainers":[{"name":"cuongdc1989","email":"cuongdc@solashi.com"}],"bin":{"design-contract":"bin/cli.mjs"},"dist":{"shasum":"d4a3f636602236fd4696a81a5855d5ffc6589a37","tarball":"https://registry.npmjs.org/@cuongdc1989/design-contract/-/design-contract-1.3.6.tgz","fileCount":17,"integrity":"sha512-As33pXQCEqXIrL5Mm5mlFyIXe99rWS85rnuiyKlqE6BOEqPdiA2+fO54gtNYWKzrF5OYVNZTs1+1/7TNUo9RWw==","signatures":[{"sig":"MEUCIEabJfPbaVzUSzzuyu0gPERjJjn2rrQd3xenEj6FQy68AiEA0IjeP+klPWFrLhqSnOoVcLbrhIAl3wnaLrc89nxEgls=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":134741},"main":"index.mjs","type":"module","engines":{"node":">=18"},"gitHead":"4aef9058b85a772188c8c65c72cc42aeb1c6e034","_npmUser":{"name":"cuongdc1989","email":"cuongdc@solashi.com"},"_npmVersion":"10.8.2","description":"Design contract testing engine: compare Storybook CSS against Figma specs","directories":{},"_nodeVersion":"20.19.1","dependencies":{"dotenv":"^16.0.0","playwright":"^1.40.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/design-contract_1.3.6_1778729647879_0.443282647106902","host":"s3://npm-registry-packages-npm-production"}},"1.3.7":{"name":"@cuongdc1989/design-contract","version":"1.3.7","keywords":["design-contract","figma","storybook","visual-testing"],"license":"MIT","_id":"@cuongdc1989/design-contract@1.3.7","maintainers":[{"name":"cuongdc1989","email":"cuongdc@solashi.com"}],"bin":{"design-contract":"bin/cli.mjs"},"dist":{"shasum":"a08be33e68c4535e917ed92e5a7e7d0eda64c9ab","tarball":"https://registry.npmjs.org/@cuongdc1989/design-contract/-/design-contract-1.3.7.tgz","fileCount":17,"integrity":"sha512-AdN360f+LusnULNK83rgWaw2jcxeUVKktt/4bGwsLvCUAmWSY+qTTPWRXpeghZXTeKwKtpasIvwhvTocyB9MhQ==","signatures":[{"sig":"MEUCIHxBUBa5MXGvkOG2azrjpeSq7xYbHpi0wfmg9hv6h286AiEAz8C2U4vylnyK+3IuCIfMVo8wzCW8H7RyebE9veuYpGc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":134804},"main":"index.mjs","type":"module","engines":{"node":">=18"},"gitHead":"5d18d6e31cf3320afd5f3ebd0abbe9407f662e83","_npmUser":{"name":"cuongdc1989","email":"cuongdc@solashi.com"},"_npmVersion":"10.8.2","description":"Design contract testing engine: compare Storybook CSS against Figma specs","directories":{},"_nodeVersion":"20.19.1","dependencies":{"dotenv":"^16.0.0","playwright":"^1.40.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/design-contract_1.3.7_1778808623412_0.3164471866564751","host":"s3://npm-registry-packages-npm-production"}},"1.3.8":{"name":"@cuongdc1989/design-contract","version":"1.3.8","keywords":["design-contract","figma","storybook","visual-testing"],"license":"MIT","_id":"@cuongdc1989/design-contract@1.3.8","maintainers":[{"name":"cuongdc1989","email":"cuongdc@solashi.com"}],"bin":{"design-contract":"bin/cli.mjs"},"dist":{"shasum":"2ed7ff6b998a9d8aa40f34c5d3c113f4755eb976","tarball":"https://registry.npmjs.org/@cuongdc1989/design-contract/-/design-contract-1.3.8.tgz","fileCount":17,"integrity":"sha512-dvjO+gjGoQ0IBzZufqg98JaY2cYVmc/vvJHcmntKMcLIeXDYUNc+bk207up46053/v8Bf2DJv45plkM0HacoYg==","signatures":[{"sig":"MEUCIEkM2SgL1fUOErTU1rJIOQlDrwH8laOTHocEuGiFzSfDAiEA753KSvs1llXlG6tdRazHi4sEtFA8/VUp/bW28dibYzc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":134328},"main":"index.mjs","type":"module","engines":{"node":">=18"},"gitHead":"fb23202367a1fa63230a1e6cfd78393afd69f113","_npmUser":{"name":"cuongdc1989","email":"cuongdc@solashi.com"},"_npmVersion":"10.8.2","description":"Design contract testing engine: compare Storybook CSS against Figma specs","directories":{},"_nodeVersion":"20.19.1","dependencies":{"dotenv":"^16.0.0","playwright":"^1.40.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/design-contract_1.3.8_1778813492666_0.410875222891097","host":"s3://npm-registry-packages-npm-production"}},"1.3.9":{"name":"@cuongdc1989/design-contract","version":"1.3.9","keywords":["design-contract","figma","storybook","visual-testing"],"license":"MIT","_id":"@cuongdc1989/design-contract@1.3.9","maintainers":[{"name":"solashi2026","email":"cuongdc@solashi.com"}],"bin":{"design-contract":"bin/cli.mjs"},"dist":{"shasum":"9e80b590cf05c2edb5271d1494b6f169cf91705f","tarball":"https://registry.npmjs.org/@cuongdc1989/design-contract/-/design-contract-1.3.9.tgz","fileCount":17,"integrity":"sha512-KpV/HSPoRLIr0QPOlQDE3jd7vjha/ky8aU2XXwqt41/HWGLMI3YthfFpmjzHz5qlatLajI6yRwq/M8/9x95QAQ==","signatures":[{"sig":"MEYCIQCJgYwg7Ko7pacBqzmRzG9I0sYURKZ/FQPOgx0o4D8i2QIhAJ/Of1i/8Dotay8N2kzXA5qTripIdQEm6cnLba2zUdFA","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":144143},"main":"index.mjs","type":"module","engines":{"node":">=18"},"gitHead":"b517a245f118d57c0a651d52a79420097cb854f9","_npmUser":{"name":"solashi2026","email":"cuongdc@solashi.com"},"_npmVersion":"10.8.2","description":"Design contract testing engine: compare Storybook CSS against Figma specs","directories":{},"_nodeVersion":"20.19.1","dependencies":{"dotenv":"^16.0.0","playwright":"^1.40.0"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/design-contract_1.3.9_1778829306541_0.3558948509374913","host":"s3://npm-registry-packages-npm-production"}},"1.4.0":{"name":"@cuongdc1989/design-contract","version":"1.4.0","description":"Design contract testing engine: compare Storybook CSS against Figma specs","type":"module","main":"index.mjs","bin":{"design-contract":"bin/cli.mjs"},"dependencies":{"dotenv":"^16.0.0","playwright":"^1.40.0"},"engines":{"node":">=18"},"keywords":["design-contract","figma","storybook","visual-testing"],"license":"MIT","_id":"@cuongdc1989/design-contract@1.4.0","gitHead":"5c34195ddb05d0ff85daa4851010c882c9d7fe13","_nodeVersion":"20.19.1","_npmVersion":"10.8.2","dist":{"integrity":"sha512-P8uu4CVAYcDBTiqDaRxDKK/4wG/eYZMHPs7FI9C2j1Ynwzw5wdwOX807+A1EO28wsN09aa7hez7MDm8zx8rWtw==","shasum":"970528ff0b0307a937679f3b5f9b22159396bcaf","tarball":"https://registry.npmjs.org/@cuongdc1989/design-contract/-/design-contract-1.4.0.tgz","fileCount":17,"unpackedSize":147722,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBHwAUpkvyLvsozAB59iJ7mZmZw0IYnpw9EkNFY52XigAiEA+YvfB4nwQYxWfU0UQVxHVY32U8PjB7v0+YMrRYRuo9w="}]},"_npmUser":{"name":"solashi2026","email":"cuongdc@solashi.com"},"directories":{},"maintainers":[{"name":"solashi2026","email":"cuongdc@solashi.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/design-contract_1.4.0_1779078045460_0.17811458037190975"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-12T06:49:19.657Z","modified":"2026-05-18T04:20:45.787Z","1.0.0":"2026-05-12T06:49:19.928Z","1.3.0":"2026-05-12T08:04:49.628Z","1.3.1":"2026-05-13T01:53:54.550Z","1.3.2":"2026-05-13T02:46:16.385Z","1.3.3":"2026-05-13T06:11:44.234Z","1.3.4444444444444444":"2026-05-13T09:57:53.574Z","1.3.5":"2026-05-14T03:09:23.983Z","1.3.6":"2026-05-14T03:34:08.020Z","1.3.7":"2026-05-15T01:30:23.580Z","1.3.8":"2026-05-15T02:51:32.819Z","1.3.9":"2026-05-15T07:15:06.690Z","1.4.0":"2026-05-18T04:20:45.656Z"},"license":"MIT","keywords":["design-contract","figma","storybook","visual-testing"],"description":"Design contract testing engine: compare Storybook CSS against Figma specs","maintainers":[{"name":"solashi2026","email":"cuongdc@solashi.com"}],"readme":"# @cuongdc1989/design-contract\n\nDesign contract testing engine: compare CSS computed styles in Storybook against Figma design specs.\n\nFetches node properties from Figma, renders your stories in a headless browser, and checks that computed CSS values match — automatically generating an HTML report.\n\n---\n\n## Install\n\n```bash\nnpm install @cuongdc1989/design-contract\nnpx design-contract init\n```\n\n`init` creates `design-contract.config.mjs` and adds npm scripts to `package.json`. If Claude Code is installed, it also installs a `/create-story` skill that automates wiring up new components.\n\n---\n\n## Quick start\n\n1. Add `FIGMA_TOKEN` and `FIGMA_FILE_KEY` to `.env`\n2. Fill in `design-contract.config.mjs`\n3. Start Storybook: `npm run storybook`\n4. Run the full pipeline: `npm run test:design:full`\n\n---\n\n## CLI\n\n| Command | Description |\n|---|---|\n| `design-contract init` | Create config template + add scripts to package.json |\n| `design-contract fetch-spec` | Fetch Figma nodes → write design-spec.json |\n| `design-contract test` | Run Playwright tests against Storybook |\n| `design-contract run` | fetch-spec + test (full pipeline) |\n\n---\n\n## Config reference\n\n```js\n// design-contract.config.mjs\nimport { CHECKS_STRICT, CHECKS_LAYOUT } from '@cuongdc1989/design-contract'\n\nexport default {\n  figmaFileKey:   process.env.FIGMA_FILE_KEY,\n  figmaToken:     process.env.FIGMA_TOKEN,\n  storybookUrl:   process.env.STORYBOOK_URL ?? 'http://127.0.0.1:6006',\n  specOutputPath: './design-spec.json',\n  reportOutputPath: './design-contract-report.html',\n\n  cases: [\n    {\n      name:        'users-statusbadge--active',\n      storyId:     'users-statusbadge--active',\n      figmaNodeId: '2397-46466',\n      figmaScale:  2,\n      viewport:    { width: 250, height: 80 },\n    },\n  ],\n\n  contractCases: [\n    {\n      name:     'users-statusbadge--active',\n      checks:   CHECKS_STRICT,\n      selector: '[data-testid=\"status-badge\"]',\n    },\n  ],\n}\n```\n\n### Top-level fields\n\n| Field | Type | Description |\n|---|---|---|\n| `figmaFileKey` | `string` | Figma file key — from the URL: `figma.com/file/<KEY>/...` |\n| `figmaToken` | `string` | Figma personal access token with read scope |\n| `storybookUrl` | `string` | Base URL of the running Storybook instance. Default: `http://127.0.0.1:6006` |\n| `specOutputPath` | `string` | Where to write the fetched Figma spec JSON. Default: `./design-spec.json` |\n| `reportOutputPath` | `string` | Where to write the HTML report. Default: `./design-contract-report.html` |\n\n---\n\n### `cases[]` — Figma snapshot config\n\nEach entry in `cases` pairs a Storybook story with a Figma node. The engine fetches the node's design properties (size, color, spacing, typography…) and saves them as the expected spec.\n\n| Field | Type | Description |\n|---|---|---|\n| `name` | `string` | Unique identifier. Must match the corresponding entry in `contractCases`. |\n| `storyId` | `string` | Storybook story ID used to navigate to the iframe URL. See [Story ID format](#story-id-format). |\n| `figmaNodeId` | `string` | ID of the Figma node to fetch. See [Finding node IDs](#finding-node-ids). |\n| `figmaScale` | `number` | Screenshot scale. `1` for full-page / large frames, `2` for small components. Does not affect spec values — only the screenshot resolution in the HTML report. |\n| `viewport` | `{ width, height }` | Browser viewport when rendering the story. Should match the Figma frame's canvas size. |\n\n#### `figmaScale`\n\nControls only the resolution of the screenshot that appears in the HTML report — it has no effect on the CSS comparison values.\n\n| Value | When to use |\n|---|---|\n| `1` | Full page layouts, large containers (≥ 400px wide) |\n| `2` | Small components: badges, avatars, inputs, table rows (< 400px wide) |\n\nUse `2` for small elements so the screenshot is readable in the report.\n\n#### `viewport`\n\nSets the Playwright browser viewport when rendering the story. The Figma node's `absoluteBoundingBox` is fetched at whatever size it is in Figma — viewport only affects how the **browser** renders the story, not what size Figma reports.\n\n| Scenario | Recommended viewport |\n|---|---|\n| Full-page story | `{ width: 1200, height: 900 }` |\n| Component rendered in isolation | Match the component's natural size, e.g. `{ width: 250, height: 80 }` |\n| Component inside a larger page | Use the page viewport so the layout context is correct |\n\nIf the component uses `min-width: 100%` or stretches to fill its container, use the page viewport even for a small element — otherwise the browser will collapse it.\n\n---\n\n### `contractCases[]` — CSS check config\n\nEach entry in `contractCases` defines which CSS properties to compare for a given case.\n\n| Field | Type | Description |\n|---|---|---|\n| `name` | `string` | Must match a `name` in `cases[]`. |\n| `checks` | `string[]` | Which CSS property groups to compare. Use a named constant or a custom array. |\n| `selector` | `string` | CSS selector to locate the element inside the Storybook iframe. Optional — defaults to the first visible child of `#storybook-root`. |\n| `typographySelector` | `string` | Secondary CSS selector pointing to the text element for typography checks. Optional. |\n\n#### `checks`\n\nDefines which groups of CSS properties are compared against the Figma spec. Use the exported constants or build a custom array from the tokens below.\n\n**Named constants:**\n\n| Constant | Checks included |\n|---|---|\n| `CHECKS_STRICT` | `exists` `size` `radius` `background` `border` `shadow` `opacity` `layout` `typography` `text` `overflow` `blend` |\n| `CHECKS_CONTAINER` | `exists` `size` `radius` `background` `shadow` `layout` `overflow` |\n| `CHECKS_LAYOUT` | `exists` `size` `layout` |\n| `CHECKS_SHAPE` | `exists` `size` `radius` `background` |\n\n**Individual check tokens and what they verify:**\n\n| Token | CSS properties checked | Figma source |\n|---|---|---|\n| `exists` | Element is found and visible in the DOM | — |\n| `size` | `width`, `height`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight` | `absoluteBoundingBox`, `minWidth`, `maxWidth`, `minHeight`, `maxHeight` |\n| `background` | `backgroundColor` (as `rgba(…)`) | First solid `fills` paint |\n| `border` | `borderWidth`, `borderColor`, `borderStyle` | `strokes`, `strokeWeight`, `strokeDashes` |\n| `radius` | `borderRadius` | `cornerRadius` / `rectangleCornerRadii` |\n| `shadow` | `boxShadow` — presence, offset X/Y, blur radius, and color | First `DROP_SHADOW` effect |\n| `opacity` | `opacity` | `opacity` |\n| `blend` | `mixBlendMode` | `blendMode` |\n| `layout` | `gap`, `paddingTop/Right/Bottom/Left`, `flexDirection`, `alignItems`, `justifyContent`, `flexWrap` | `layoutMode`, `itemSpacing`, `padding*`, `*AxisAlignItems`, `layoutWrap` |\n| `typography` | `fontFamily`, `fontWeight`, `fontSize`, `lineHeight`, `letterSpacing`, `textAlign`, `color`, `textDecoration`, `textTransform`, `fontStyle` | First `TEXT` node found inside the Figma node (`style` object) |\n| `text` | `innerText` contains the Figma text value | `characters` of the first text node |\n| `overflow` | `overflow` | `clipsContent` |\n\n#### Bidirectional verification\n\nSeveral check types verify in **both directions** — they catch mismatches whether the property is present or absent:\n\n| Check | Figma HAS property | Figma has NO property |\n|---|---|---|\n| `background` | Browser `backgroundColor` must match Figma fill color | Browser must have transparent background (`rgba(0,0,0,0)`) |\n| `border` | Browser `borderWidth`/color/style must match Figma stroke | Browser must have no border (`borderWidth: 0px`) |\n| `shadow` | Browser `boxShadow` must match offset, blur, and color | Browser must have no `box-shadow` |\n| `radius` | Browser `borderRadius` must match Figma `cornerRadius` | Browser must have no border radius |\n\nThis means you can **use a check to guarantee absence**, not just presence. For example, if you want to verify that a component has no drop shadow:\n\n```js\n// Figma node has no shadow effect.\n// Including 'shadow' will fail if the browser has any box-shadow.\n{ name: 'my-card', checks: ['exists', 'size', 'background', 'shadow'], selector: '[data-testid=\"my-card\"]' }\n```\n\nThis is useful for catching accidental CSS leaking in from parent styles or utility classes.\n\n**How to choose:**\n\n1. Fetch the Figma node's properties (see [Choosing checks from Figma data](#choosing-checks-from-figma-data)).\n2. Pick the strictest named constant that is fully covered by the node's data.\n3. Only use a custom array when the named constants don't fit — for example, a table row that needs size + layout + typography but has no background or border.\n\n```js\n// Custom subset example\n{ name: 'users-tablerow--default', checks: ['exists', 'size', 'layout', 'typography'] }\n```\n\n#### `selector`\n\nA CSS selector used by Playwright to locate the target element within the Storybook iframe (`http://localhost:6006/iframe.html`).\n\n| Scenario | Selector |\n|---|---|\n| Story renders only the component | Omit — defaults to `#storybook-root > *:visible` |\n| Component has a `data-testid` | `'[data-testid=\"my-component\"]'` |\n| Table row / grid cell | `'#storybook-root table tbody tr:first-child td:nth-child(2)'` |\n| Nested element inside a page story | `'[data-testid=\"card-header\"] .title'` |\n\nAdd `data-testid` to the component's root element so the selector is stable across DOM changes:\n\n```tsx\n// In your component\n<div data-testid=\"status-badge\" className=\"...\">\n```\n\n```js\n// In contractCases\n{ name: '...', checks: CHECKS_STRICT, selector: '[data-testid=\"status-badge\"]' }\n```\n\n#### `typographySelector`\n\nBy default, the typography check reads computed styles from the **first text leaf** found inside the target element. If that heuristic picks the wrong element (e.g. a label instead of the main title), use `typographySelector` to specify an explicit CSS selector relative to the Storybook root.\n\n```js\n// Read typography from the truncated span, not the outer div\n{\n  name: 'users-avatarcell--default',\n  checks: ['exists', 'size', 'layout', 'typography'],\n  selector: '#storybook-root table tbody tr:first-child td:nth-child(1)',\n  typographySelector: 'span.truncate',\n}\n```\n\nUse `typographySelector` when:\n- The component has multiple text elements and the first one is not the one Figma describes\n- The root element has no text content itself (typography is inherited by a child)\n- You want to verify a specific heading or label, not all text\n\n---\n\n## Story ID format\n\nStorybook derives the story ID from the `title` and the export name:\n\n```\ntitle: 'Users/UserDetailDrawer'  +  export const Default\n  → story ID: users-userdetaildrawer--default\n```\n\nRules:\n- Lowercase everything\n- `/` → `-`\n- Spaces → `-`\n- Remove non-alphanumeric characters (no underscores, no dots)\n- Export name appended with `--`\n\n| Title | Export | Story ID |\n|---|---|---|\n| `Users/UsersPage` | `Default` | `users-userspage--default` |\n| `UI/DataTable` | `Default` | `ui-datatable--default` |\n| `Auth/LoginPage` | `Default` | `auth-loginpage--default` |\n| `Dashboard/KPICard` | `Revenue` | `dashboard-kpicard--revenue` |\n\nVerify with (Storybook must be running):\n\n```bash\ncurl -s http://127.0.0.1:6006/index.json \\\n  | node -e \"\n    const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));\n    Object.keys(d.stories || d.entries || {}).forEach(id => console.log(id));\n  \"\n```\n\n---\n\n## Finding node IDs\n\nIn Figma: right-click a frame or component → **Copy link** → the URL contains `node-id=XXXX-YYYY`.  \nUse `XXXX-YYYY` as the `figmaNodeId` value (hyphens, not colons).\n\nAlternatively, use the Figma REST API to browse the file tree:\n\n```bash\n# List pages\ncurl -s -H \"X-Figma-Token: $FIGMA_TOKEN\" \\\n  \"https://api.figma.com/v1/files/$FIGMA_FILE_KEY?depth=1\" \\\n  | node -e \"\n    const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));\n    d.document.children.forEach(p => console.log(p.id, p.name));\n  \"\n\n# List top-level frames on a page (replace PAGE_ID)\ncurl -s -H \"X-Figma-Token: $FIGMA_TOKEN\" \\\n  \"https://api.figma.com/v1/files/$FIGMA_FILE_KEY/nodes?ids=PAGE_ID&depth=2\" \\\n  | node -e \"\n    const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));\n    Object.values(d.nodes)[0].document.children\n      .forEach(n => console.log(n.id, n.name, n.type));\n  \"\n```\n\n---\n\n## Choosing checks from Figma data\n\nFetch the node's raw properties to determine which checks are applicable:\n\n```bash\ncurl -s -H \"X-Figma-Token: $FIGMA_TOKEN\" \\\n  \"https://api.figma.com/v1/files/$FIGMA_FILE_KEY/nodes?ids=NODE_ID\" \\\n  | node -e \"\n    const d = JSON.parse(require('fs').readFileSync('/dev/stdin','utf8'));\n    const n = Object.values(d.nodes)[0].document;\n    console.log({\n      hasFill:     (n.fills||[]).some(f => f.type!=='IMAGE' && f.opacity!==0),\n      hasStroke:   (n.strokes||[]).length > 0,\n      hasShadow:   (n.effects||[]).some(e => e.type==='DROP_SHADOW'),\n      hasRadius:    n.cornerRadius > 0 || (n.rectangleCornerRadii||[]).some(r=>r>0),\n      hasOpacity:   n.opacity != null && n.opacity !== 1,\n      hasLayout:    n.layoutMode != null && n.layoutMode !== 'NONE',\n      hasTypography:(n.type==='TEXT') || !!(n.children||[]).some(c=>c.type==='TEXT'),\n      hasOverflow:  n.clipsContent === true,\n    });\n  \"\n```\n\nDecision:\n\n```\nhasFill + hasRadius + hasLayout + hasTypography  →  CHECKS_STRICT\nhasFill + hasRadius + hasLayout                  →  CHECKS_CONTAINER\nhasFill + hasRadius                              →  CHECKS_SHAPE\nhasLayout only                                   →  CHECKS_LAYOUT\nsubset needed                                    →  custom array\n```\n\n---\n\n## Component & Story Guide\n\n### 1 — Add `data-testid` to your component\n\nAdd `data-testid` to the root element and any sub-elements that map to separate Figma nodes.\n\n**Naming:** `[feature]-[component-name]` in kebab-case.\n\n```tsx\n<div data-testid=\"table-card-header\">\n  <input data-testid=\"table-search\" />\n</div>\n```\n\n### 2 — Create a Storybook story\n\nPlace stories in a `stories/` subfolder next to the component file:\n\n```\nsrc/features/users/\n  UsersPage.tsx\n  stories/\n    UsersPage.stories.tsx\n```\n\nAlways declare `component` in meta. Do not use `render` without `component` — this causes story load failures in Storybook v10.\n\n```tsx\nimport type { Meta, StoryObj } from '@storybook/react'\nimport { MyComponent } from '../MyComponent'\n\nconst meta: Meta<typeof MyComponent> = {\n  title: 'Feature/MyComponent',\n  component: MyComponent,           // required\n}\nexport default meta\ntype Story = StoryObj<typeof MyComponent>\n\nexport const Default: Story = {\n  args: { /* all required props */ },\n}\n```\n\nDo not use external image URLs (picsum, via.placeholder, etc.) in mock data — Playwright's page load will time out waiting for the CDN. Use SVG data URLs instead:\n\n```ts\nconst AVATAR = 'data:image/svg+xml;utf8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"64\" height=\"64\"><rect width=\"64\" height=\"64\" fill=\"%23CBD5E1\"/></svg>'\n```\n\n### 3 — Add to config\n\n```js\ncases: [\n  {\n    name:        'feature-mycomponent--default',\n    storyId:     'feature-mycomponent--default',\n    figmaNodeId: '1234-5678',\n    figmaScale:  2,\n    viewport:    { width: 250, height: 80 },\n  },\n],\ncontractCases: [\n  {\n    name:     'feature-mycomponent--default',\n    checks:   CHECKS_STRICT,\n    selector: '[data-testid=\"my-component\"]',\n  },\n],\n```\n\n---\n\n## AI assistant (Claude Code)\n\nRunning `npx design-contract init` installs a `/create-story` skill into `.claude/commands/`.\n\nIn Claude Code, type `/create-story` — the AI will:\n- Scan the codebase for components without stories\n- Browse the Figma file via API to match components to node IDs\n- Add `data-testid` to the component\n- Create the story file with correct providers and no external URLs\n- Fetch the node's Figma properties to determine the strictest applicable `checks`\n- Update `design-contract.config.mjs`\n- Run TypeScript and verify the story ID before finishing\n","readmeFilename":"README.md"}