diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09331e1..4d5af3f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,20 +22,27 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.33.0 + run_install: false + - name: Setup Node uses: actions/setup-node@v4 with: node-version-file: .nvmrc - cache: npm + cache: pnpm + cache-dependency-path: pnpm-lock.yaml - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Run lint - run: npm run lint + run: pnpm lint - name: Run unit tests - run: npm run test:run + run: pnpm test:run - name: Build - run: npm run build + run: pnpm build diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index fb3132a..73fa78d 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -21,23 +21,30 @@ jobs: - name: Checkout uses: actions/checkout@v4 + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + version: 10.33.0 + run_install: false + - name: Setup Node uses: actions/setup-node@v4 with: node-version-file: .nvmrc - cache: npm + cache: pnpm + cache-dependency-path: pnpm-lock.yaml - name: Install dependencies - run: npm ci + run: pnpm install --frozen-lockfile - name: Run lint - run: npm run lint + run: pnpm lint - name: Run unit tests - run: npm run test:run + run: pnpm test:run - name: Build - run: npm run build + run: pnpm build - name: Add SPA fallback run: cp dist/index.html dist/404.html diff --git a/.gitignore b/.gitignore index bce5ca2..8077003 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,10 @@ lerna-debug.log* node_modules dist dist-ssr +benchmark-results +playwright-report +test-results +dataset/private/ *.local # Editor directories and files @@ -22,4 +26,4 @@ dist-ssr *.njsproj *.sln *.sw? -Puzzlink_Assistance.js \ No newline at end of file +Puzzlink_Assistance.js diff --git a/README.md b/README.md index 8e81d05..4db34b3 100644 --- a/README.md +++ b/README.md @@ -40,29 +40,35 @@ Execution order is centrally managed in `src/domain/rules/slither/rules.ts`: det ### 2.1 Requirements -- Node.js 18+ (latest LTS recommended) -- npm 9+ +- Node.js 20 (see `.nvmrc`) +- Corepack with pnpm 10.33.0 ### 2.2 Install and Start ```bash -npm install -npm run dev +corepack enable +pnpm install +pnpm dev ``` This starts the local Vite development server. -![](https://cdn.jsdelivr.net/gh/SmilingWayne/picsrepo/20260427202121944.png) +![](https://cdn.jsdelivr.net/gh/SmilingWayne/picsrepo/20260514010419047.png) ### 2.3 Common Commands ```bash -npm run lint # ESLint -npm run test:run # Vitest unit tests -npm run build # TypeScript + Vite production build -npm run test:e2e # Playwright end-to-end tests +pnpm lint # ESLint +pnpm test:run # Vitest unit tests +pnpm build # TypeScript + Vite production build +pnpm test:e2e # Playwright end-to-end tests ``` +### 2.4 Release and Deployment + +GitHub Pages deployment is handled by GitHub Actions. Push a `v*` tag to run +linting, unit tests, and the production build, then publish `dist/` to Pages. + --- ## 3) Features Implemented So Far @@ -138,4 +144,3 @@ The long-term direction is to build a puzzle reasoning tool that is: This repo is inspired by the browser plugin [Puzzlink_Assistance](https://github.com/LeavingLeaves/Puzzlink_Assistance), which helps with trivial inference for puzz.link-style puzzles. The detailed inference techniques can be found in [How slitherlink should be solved](https://jonathanolson.net/slitherlink/). - diff --git a/dataset/public/slitherlink.example.json b/dataset/public/slitherlink.example.json new file mode 100644 index 0000000..4a46d5a --- /dev/null +++ b/dataset/public/slitherlink.example.json @@ -0,0 +1,512 @@ +{ + "schemaVersion": 1, + "id": "slitherlink-example", + "title": "Slitherlink Example Dataset", + "puzzleType": "slitherlink", + "items": [ + { + "id": "slitherlink-10x10-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/372d23djdh738adl72882dj18538ald838dhaj21d272c", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0002", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/q2111221ch6212b212611b61262cg1c6bb2121c2bcc621112bo", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0003", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/6dcbg8228cn23836b50bgdjbg26c71812cn8308dgdd6d", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0004", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/811817djb3dgdgcclc7882587ccldcgdg2cbj628228b", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-6x6-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/6/6/1bg688cgc121186dgbg2b", + "width": 6, + "height": 6, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-6x6-0002", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/6/6/hch36dg11671176dgbg2b", + "width": 6, + "height": 6, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0005", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/cdi6d5dhajd7835d8dh122dh7d6188ccjbh8c8didc", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-18x10-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/18/10/gagd26bg3c68di7dkcgdi6113dh75326bh6267cm1dm6537bh71166ch1218diagbk6di66d1cg71bbgc", + "width": 18, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0006", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/gdai8dg83aidh5b731278cn763336d6chci38bg6diab", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-19x10-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/19/10/y13c22d32c1186b8c8b8631d31b13c32czx32c22b21d3376d8d8c7612d32b23b31cw", + "width": 19, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0007", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/673cgcdg33dgag7bg8dg67ccg2agc75bg8dg7agbg32dgdcg178d", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0008", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/g0bh2a37063bi2dr0b1c122d0d0dr0ci28183b0bh2a", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0009", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/l2bg2bd8c6d6bc3cg2dr0cg1cd6d5b8dd2ag3bj", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-18x10-0002", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/18/10/c82chcdgcbgd63c173ah6aibi81b71cdjcdcb123ddbcbjb37d16didi8dh161c36cdgcagdbh28bb", + "width": 18, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0010", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/h8dd32d8112dnbbg7a78ch78d8dgcdn1315d32bd8d", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0011", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/g13d225dgacbl3d338dm518cm831d2blbcbg822a22d", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0012", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/68638b6dh2cjd536858dgdg2bgcg788838dbj3ch8b83888d", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0013", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/c7db3862bpbg8bg5ccg1bgb7ag8agbp3882dc6cc", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-18x10-0003", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/18/10/i61ch28cg16dg122cg63bi3ah1di2dcg0bgb1bc6c8bchd8b6cd1cbg2cgb3ci1dh3ci18dg132bg72bg82bh36dg", + "width": 18, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-20x20-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/20/20/d362275132cjbgb3bb2231121ddi261d81c2cdh8c281ccgcg732117bg222b8ch32781262d1bh272122dg2b31b3321ccd213c083162223bg17cdg3371321bcgcmcg261762dd2122358767572cgcc2223212b2cbcccg233128151d8ci211bibh73c3cg77c1bb6d223c22531132a23cbi0", + "width": 20, + "height": 20, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-25x15-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/25/15/gdgbhbgdhagbg31d0c03c3b32bcibcidbi0aiccibdic33d2d03c1d22dgcgchcgbhdg8bicciadi0dabcacba1cidcibbi7bgbhdgdhbgcg11b1d23b1b23dcidcibdi0aidcidcib02a3c33d1d23dgbgbhagbhagc", + "width": 25, + "height": 15, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0014", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/i33328837ai3bdc223cgc3c333ccg216621ci23733chd2c27817c31d", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0015", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/h582068cjdi20bg20bn2adp82a20bp25871dja", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0016", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/320c02djdj1ag18ah0bibiaaibi2ah53dg3cjdj32d232a", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-13x13-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/13/13/g2azg1czv3aziapaodpcldh1bg386ah2cjb", + "width": 13, + "height": 13, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-15x15-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/15/15/ah232557702bg2cg3173c7dg12di1a0djbi22781272bdbh2721bi82cgdcg0832722512cic23d22d717di21881811bg2b1cg2c632dgd2bc03cg71d33381d2", + "width": 15, + "height": 15, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-18x18-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/18/18/c27c1761833222222212cgai3283c5cgb2866ag67ci23150136c3dj7c2772d32cci12272bg22716d1277271d3152832dach2dh2bg322chbi167232c872327db232612bj28dg223c61218chd2778211b8bibi317c3028122323cg326d", + "width": 18, + "height": 18, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-30x20-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/30/20/g228bh262c3223322221011db122ag2bhbcc7166dic31c212a332b38063b3118atbddhcg12dd161d7cb2271d6206d82bibyd06710000000000000a73ahb8ar57cg2312bar577211877ara0122bg78ar5cdh36d00000000000000681dcybi28bd28c2611dc7b27273c10cgdhddbt8311a25182a230c223d22ddi7817bcbbh1bg322bc1122222012311c272bh512d", + "width": 30, + "height": 20, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-30x30-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/30/30/3271b622817228d18123cdg8dibh1bhb2b62bh8a7d636b718b3cg0a3bg016817c8c32b06d20cha18cb77ah75c377d1c7b1ah1a6cgbidgdg253cbgdg87211012c327bc2d223617bagbg7373ccgc7cg1acgdh0cb3cg37bg062d16ah22bg82d63c1aca28005bb23cgc00a01bbg00a3bi8bh1d87cbg2cgc86b3bh7di2b00agb32a00abg23bd60053bab0b26d17dg11bh52d371ag72bg3cb1ahcgb0b26cbgd1628cgbgahd332b1dc713c1210687bgdgc370bgcg6cgcg7b0ch0b8d2c783b75ch57cb72dch10c62a03c8c726612ag3b0bg1c636d817c8b5ch17c3ccgdbgci8bgd21282c632616328c1622b", + "width": 30, + "height": 30, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-7x7-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/7/7/h182cl60bb273dg6c7bcj", + "width": 7, + "height": 7, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-31x45-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/31/45/i8327bdhahc723cgdjc7dhab87bg6133dhcgahag712cgdi028diacdjbp12aia6021bg2020dhah2020bgdi8ci23bpamcai632cibg2201agchagch3016ddi22bah5ccjbbahdhahb8206cgch517ahad886ccah0bgahcg6227bbhagbiadajd7aha02aic7122bhcgal2023bgai218bi5cnbp20di10bibg1120bhbh0113bg3218adi30dpdjcabi611bi65126cgdhdgch01367767dcdh7bcj65126ddhahd81168cjc7dhab65657311dhcgdhbg71167bi118biddbjap30did7231dg3321bhch0223dgbi23ai33cpan8di811bibg1302bldgbh1216aai12cah8cdjbccicgdha6237cgahbg0dhdd577ccah717dhag7307dbhahahdbajd7chb11dic7113chdgahcg3322dgai336dibdmdp02ai5cicg3112ahdh2011dg1326dai12cpdjccai722cidg227dgahagbh3327ag86ccah5ddjcg206cbhahd6107bg", + "width": 31, + "height": 45, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-31x45-0002", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/31/45/g33di10c32c22ai338dh30dh786ch22ah2do7317ao3d323032ccic102130ddgcibodich7ai8c33d7di7c7cd6b6b6cb5c6ai7a21c8ai8ahdiaodibga121233dbib213020c2bo5027co2ah20ch677dh31bh531bi33d22c33bi23dj7as8bo6880220212b7dk302acsc0207bhch23ci12bhch2ch57dh01bh85bh3dhcgaobgch3ch56d011032b78ch6223dchcibhd302blb5bi6ddl778a7ci6b867dlc5ci7ddl021cchbichc2107bh87c111102d87dh2chagaobgah2ch78dh13dh67ch1bhbh23ci31ahbh8303bcsd021dk8c1112213788co7ds7cj32ci23d12c10di238bh12ah767dh33dh0do8037do1d122021aaid221112dagbicodich5bi6b20c7bi6b7aa8c8d7dc7a8bi6c31d8ai7dhbicocibgc021113bbib310212a3ao6326bo2ch02ah676ah32ch522ci01a22c13ci32b", + "width": 31, + "height": 45, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-31x45-0003", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/31/45/10ci2201ci2111ai23d877cg887cg778chbidgcibgaibh767cg785bg688d22ai1011di0212bi22cg1120ci3111bi2222dh8bg878cg668cg8dhcgbibgcicgch5dg667cg566ag7dh1120bi0211ci2222dg02bi1010ci1202di33d678bg686bg757chaibgbibgbibh878bg588ag878d22bi1122bi1211ci12cg3010di1210bi1121bh8dg866cg777bg7chdgbiagcicgdh8dg687bg577ag6ch3111bi1011ci2223cg13ci0211bi0121di23a787cg588dg757chbibgcibgcich877cg877cg588b21ci2112ci1121bi12cg1121bi1112ai1133dh7dg678ag758bg6dhcgdicgaibgdh5cg757ag758bg7ch3220bi2112ai1021bg33di2123bi1121bi30d686cg557cg787chbibgbicgcich678dg787cg587a11bi1322bi2212di21cg3311ci0102bi2222ch8bg788cg766dg7dhcgcicgbidgch6bg657bg776bg8bh1101bi2213bi2022dg13bi2112ai0211bi23b655bg677bg668ahbiagbibgbidh785bg777cg888c33di0122bi2220ci22c", + "width": 31, + "height": 45, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-31x45-0004", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/31/45/g20136dg61113cg21215cndg8cgamcg223dg32227cg61233bg201cgcmcg8bgbn62331bg22135dg70113dh5bgcmbg5bg62238cg72130bg21027agcjcg6agcmcg51bg01226cg62222ag0222ccmbg5cgcn81023bg10127cg52021bh7bgbmag6dgcg18bg51022ag11216ag62bgcg6bgcmcg7ah01226bg62332bg01125cnag6bgdmb2333cg13117bg82301bg26cgdmag7cgcjbg72021cg11216bg71138bg6bgcmbg7ch12127bg72231ag21217cnbg5bgcmbg211bg11216dg72221bg102cgambg7bgdn71120bg20116cg82223bh7bgbmcg6dg81017bg60121bg11126bgdjdg6cgbmdg72dg11116cg71113bg3233dbmag6bgdn82211bg11206dg62221bh5bgbmbg8cgag36dg61213cg21107cg61cgdg6bgbmcg6ch12316bg61010cg21106dnbg8dgbma3313bg33218dg72233cg37bgbmbg8bgbjdg51322cg01136bg72206bg7dgcmcg7ch31207cg70321bg10216bncg5dgcmcg102ag23226cg70111dg223cgcmcg7cgdn71322cg22027cg82333b", + "width": 31, + "height": 45, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-31x45-0005", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/31/45/g0ca1722ah827072a2dag5ci381aiag0121b1bcg215bh1aod21akc0dh2bcbh8686bgch8ag23daajabg17cg5c5d2c37c851agagah2bbac77bcb20diaj6dbg0cn3576dha52cg518abg5708ch65ddgc61d203caahbh6c07agcck0bg32agagbbj80bibia23bj3dag835d20ck011bi75ch51b132cgd8dic7bidhdag508207ag06ag0bgadjajah12dhb880207cg0cg1aid37bk1cjb8chaai8ch2ak153660b13bh0d8c2dh21a185352dk0ch5cibch5cdj3ak73ddi0dg1ag732358adh30bhdjbjadg3cg81cg731537agdchbi5cai5dcg303c18ah56ai210dk21a625cgd0dj30baici38cjaagdg22ag0dkcag72b5dhbhdd111b15adgd56ch8356cgd508dg38dbh8652dn1dgd7cjbi02cdb86dacc0chagag375c71a2a7c7ag62agadjdc30cg7chag5867bhac3dh0cck10cco1ch820cgd0b1311agbi063bi8bgb0c072536dh0172ca1d", + "width": 31, + "height": 45, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-31x45-0006", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/31/45/a753b7c25853b7b67cgbdh86cgbg5c5bg5a2ag5bg5516868767dg778778525c1cg7b3aidicddh1cbgcgbg2abhb32d61c7dgag63dg756agagag575762agci8125672dcga3ca176dg1ba175b6bg875bg5bb5ch866bg7dg7562d5c770cagbg570bag52bgdg657576cg76d5665cgagbg5675736cgcg6cbbgbg6cc3bg2dbgb2ag0da8ag0c51c8agai3dg76768agcg68138737bi5b36dgcgag280dag68bgagag6dh6a26dhd8b2765dj7dgbkdg8aj8751b6ddh80c6dh5cgagag68bgb271cgagcg80d5di51851378agbg57788cg3cicg6d36c1cg7ca2bg0dbga2ag0ba8agagab8agag7386657bgbgcg5788c66cg575866cgdg38agc366bgaga266a7a2665cg7ag565dh6ab6bg576bg7b851bb1bg760dc3cagc3585236aiag268566dgdgdg856ag26bgbg6b16b12dchd1agdgdgc2dhacbidi1c8dg2a607878886dg8787668178bg5bg1d5bg6b6dgdg68dhddg75d6d16761c7d167bd", + "width": 31, + "height": 45, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-45x31-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/45/31/h33cg8dgbdgba6cddgadk30bk6djc21dgdddg328dk31di21ag7bgbcgcb8ddg6dg10ci32ck5bjd22dg23ddj8ck23di02bg8cgd7cddgdcg6cg22di13cjb02cgddbg22ccj8dk3388bgbdgcc6cadgcdg8cgck8dja32bgbddg22dcj01ai12dg6bgdcgcc6cb17bg13di11bk7cjc11bgahaj6dk12ai31cg8cgdchbagcdg6bg21ci31ck6aibcag22bdj7dk02bi10ddgcb7ccdgbag8bg13di8bjb22dgcdcg13dbj8ai20dg7dgcdgbd7bdcgd31ai21dk7djd22dgcddi6dk21ci21cg7cgccgdbhcdg8dg33di30ck7bjbhdg21bdj5ck21ci02ag81ca6bdcgcdg5cg23bi23djc12cgcdag22cbj8ckdg5dgccgdd7cdbgdcg8620bk7cjd21bgbddg22cbj20di23dg8cgcagdd7cag6cg21ci02ak8cjd11dg31ddj7dk12ai02ag8agd6ddagcdg6dg20ci31dk722dgcdag21cdj7dk30bkbagcd8bdagbcg8bg20b", + "width": 45, + "height": 31, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0017", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/g236713cibm82138dk6bg1377co31cg13cjdi128b32bhc", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-5x7-0001", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/5/7/aga2c23c22c23c2caga", + "width": 5, + "height": 7, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0018", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/g11b11b8266a8c6a7c7c8726a8726a7c7c8c6a8c6a22c22c", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0019", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/g6b6d8b8178b8bp22c21c20c20cp6d6826d6d6b6b", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0020", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/gbjc2312212dbjb63338d8c7b7b6b62117ccjd2132122bbjd", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0021", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/nb2222013c7dhc8b7a6067b5c7d72315ccjc362231ao", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0022", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/h18dcgbc711cb20d7dcj26832132cbicgdm722223132cgak", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0023", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/gcm701201c6cha5637b7ahc67da5b7bcjc10210210bn", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0024", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/31cg8722222227cl73c1c71111118cj16cgbb71116b2cb17218b182d", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0025", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/gbgbi560122agdj177217bgdj25658ao620786bm5656a", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0026", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/dc7b283c812727ci16bh37273c272212727bbh372727127272chc8b", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0027", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/jbj80130cmdh01bo21a02do32aham11315djbh", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0028", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/bch5a607co3810820cx2083172ao635d8bhdb", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0029", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/gdk8dh2ah738cgd60djagbdgcj25bdg817ah0dh8dk5", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0030", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/q202108060clccgb62202118a5chd2ccib2075262agbd7d", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0031", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/gb7d23c31bd2bh2c721b32776787027a37cgbj1126cbj22333c332", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0032", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/g88b227637bg2067a7bj8c6a223c1adh1cb1bi32di1dc33783", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0033", + "puzzleType": "slitherlink", + "sourceUrl": "https://pzv.jp/p.html?slither/10/10/0c36am323bajbdkbg2b3b2agbkcbjb330cm81a3c", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + }, + { + "id": "slitherlink-10x10-0034", + "puzzleType": "slitherlink", + "sourceUrl": "https://puzz.link/p?slither/10/10/gdgdg6cd07ahaj03bibi35163aibi12cjah83bd7agbgb", + "width": 10, + "height": 10, + "tags": ["auto-imported"], + "source": "example.txt" + } + ] +} diff --git a/docs/PROJECT_GUIDE_EN.md b/docs/PROJECT_GUIDE_EN.md index c8939cb..637cece 100644 --- a/docs/PROJECT_GUIDE_EN.md +++ b/docs/PROJECT_GUIDE_EN.md @@ -37,6 +37,7 @@ In short: this project is a **logic reasoning engine with a UI**, not a UI-first src/ app/ # page composition and top-level routing/layout domain/ # puzzle logic source of truth + benchmark/ # dataset manifest validation and solver benchmark runner ir/ # puzzle IR schemas, key utilities, normalize/clone parsers/ # puzz.link/penpa adapters rules/ # rule contracts, step engine, puzzle-specific rule sets @@ -45,6 +46,11 @@ src/ difficulty/ # difficulty snapshot and rule usage aggregation features/ # solver controls, board rendering, editor tools, explanation, stats test/ # test setup/runtime helpers +dataset/ + public/ # committed benchmark/dataset manifests + private/ # local-only manifests, ignored by git +scripts/ + benchmark-solve.ts # project-owned benchmark entrypoint ``` Design rule: @@ -69,18 +75,52 @@ This guarantees the same inference chain can be replayed and inspected later. --- -## 5. Slitherlink Rule Architecture (Current) +## 5. Benchmark and Dataset Flow + +Benchmarks evaluate solver behavior across JSON dataset manifests. They are for +solver quality and rule-usage analysis, not for unit-test correctness. + +Data locations: + +- `dataset/public/**/*.json` is committed and should stay small/curated. +- `dataset/private/**/*.json` is local-only and ignored by git. +- `benchmark-results/` is generated output and ignored by git. + +Run: + +- `pnpm benchmark:solve` + +This command scans public/private manifests, runs each puzzle with the default +plugin rule order, and writes one report per manifest to +`benchmark-results/.report.json`. + +Current defaults: + +- `maxSteps = 2000` +- `timeoutMs = 60000` +- `ruleProfile = "default"` + +Report intent: + +- Per puzzle: status, step count, duration, terminal completion report, + `ruleUsage`, and compact `ruleSteps`. +- `steps` is intentionally an empty array for now to keep large reports small. +- `ruleSteps[ruleId] = [stepNumbers...]` records where each rule fired. + +--- + +## 6. Slitherlink Rule Architecture (Current) The Slitherlink rules are now modularized under `src/domain/rules/slither/rules/`. -### 5.1 Aggregation entrypoint +### 6.1 Aggregation entrypoint - `src/domain/rules/slither/rules.ts` - Exports `deterministicSlitherRules` in a fixed order - Exports `slitherRules = deterministic + strong-inference` - Serves as the single place for execution-order control -### 5.2 Rule modules +### 6.2 Rule modules - `patterns.ts` - pattern-style clue rules (e.g. contiguous 3-run, diagonal adjacent 3) @@ -92,23 +132,28 @@ The Slitherlink rules are now modularized under `src/domain/rules/slither/rules/ - corner-sector inference from local edge/vertex/cell evidence - `sectorPropagation.ts` - sector-to-sector and sector-to-edge propagation family +- `colorAssumptionInference.ts` + - conservative color-branch contradiction inference +- `sectorParityInference.ts` + - conservative sector-parity contradiction inference - `strongInference.ts` - conservative branch-based contradiction inference - `shared.ts` - reusable helpers (geometry adjacency, clue/color utilities, mask helpers) -### 5.3 Strong inference decoupling +### 6.3 Branch inference decoupling -`strongInference` no longer self-references the exported `slitherRules` array. -Instead, it receives deterministic rules via dependency injection: +Branch-based inference rules should not self-reference the exported +`slitherRules` array. They receive deterministic rules via dependency +injection, for example: - `createStrongInferenceRule(() => deterministicSlitherRules)` -This prevents circular coupling and keeps strong-inference reusable/testable. +This prevents circular coupling and keeps branch inference reusable/testable. --- -## 6. Sector Constraint Model (Critical) +## 7. Sector Constraint Model (Critical) Sector state is represented as a bitmask of allowed corner line counts `{0,1,2}`. @@ -121,7 +166,7 @@ Do not revert to old single-label sector semantics. --- -## 7. Replay and Determinism Contract +## 8. Replay and Determinism Contract Two files must stay behaviorally aligned: @@ -136,7 +181,7 @@ If these two paths diverge, timeline replay and solver state will drift. --- -## 8. Current Capability Snapshot +## 9. Current Capability Snapshot Implemented: @@ -150,11 +195,14 @@ Implemented: - Explanation-oriented deduction trace - Sector mask inference/propagation pipeline - Strong-inference fallback for harder states +- Public/private benchmark manifest workflow +- Compact benchmark reports with solve status, timing, rule usage, and rule step indices Partially implemented / planned: - More puzzle families (e.g. Masyu/Nonogram) - Puzzle-specific editor support for each puzzle family +- Dataset browsing as a product surface - Canvas interaction and rendering optimization for larger boards and richer editor states - Penpa adapter/export completeness - Better calibrated difficulty modeling @@ -163,7 +211,7 @@ Important expectation: difficult puzzles may stop at a stable but incomplete sta --- -## 9. AI Agent Quick Start +## 10. AI Agent Quick Start If you are an AI agent onboarding this repository, do this first: @@ -171,7 +219,8 @@ If you are an AI agent onboarding this repository, do this first: 2. Read `src/domain/rules/slither/rules.ts` to understand execution order. 3. Read `src/domain/rules/slither/rules/*.ts` by module category. 4. Verify replay contract in `src/features/solver/solverStore.ts`. -5. Use `src/domain/rules/slither/rules.test.ts` as behavior reference. +5. For benchmark work, read `src/domain/benchmark/runner.ts` and `scripts/benchmark-solve.ts`. +6. Use `src/domain/rules/slither/rules.test.ts` and `src/domain/benchmark/*.test.ts` as behavior references. When editing: @@ -179,13 +228,27 @@ When editing: - Preserve diff/message explainability. - Preserve ordered deterministic behavior unless intentionally changed. - Add/adjust tests alongside rule changes. +- Do not commit private datasets or generated benchmark reports. --- -## 10. Development Commands - -- `npm run dev` - local development -- `npm run lint` - linting -- `npm run test:run` - unit/component tests -- `npm run build` - production build -- `npm run test:e2e` - Playwright end-to-end tests +## 11. Development Commands + +- `pnpm install` - install dependencies using the locked pnpm dependency graph +- `pnpm dev` - local development +- `pnpm benchmark:solve` - run all public/private benchmark manifests +- `pnpm lint` - linting +- `pnpm test:run` - unit/component tests +- `pnpm build` - production build +- `pnpm test:e2e` - Playwright end-to-end tests + +## 12. Deployment and Release Flow + +- Package management is standardized on pnpm 10.33.0 via the `packageManager` + field in `package.json`. GitHub Actions installs that pnpm version before + enabling `actions/setup-node` pnpm caching. +- CI runs on pushes and pull requests targeting `main`; it installs with + `pnpm install --frozen-lockfile`, then runs linting, unit tests, and build. +- GitHub Pages deployment is triggered by pushing a `v*` tag. The deployment + workflow runs the same checks and build, copies `dist/index.html` to + `dist/404.html` for SPA fallback, then publishes `dist/`. diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index a520368..0000000 --- a/package-lock.json +++ /dev/null @@ -1,4801 +0,0 @@ -{ - "name": "puzzlekit-web", - "version": "0.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "puzzlekit-web", - "version": "0.0.0", - "dependencies": { - "fflate": "^0.8.2", - "react": "^19.2.4", - "react-dom": "^19.2.4", - "react-router-dom": "^7.14.0", - "zod": "^4.3.6", - "zustand": "^5.0.12" - }, - "devDependencies": { - "@eslint/js": "^9.39.4", - "@playwright/test": "^1.59.1", - "@testing-library/jest-dom": "^6.9.1", - "@testing-library/react": "^16.3.2", - "@testing-library/user-event": "^14.6.1", - "@types/node": "^24.12.2", - "@types/react": "^19.2.14", - "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^6.0.1", - "autoprefixer": "^10.4.27", - "eslint": "^9.39.4", - "eslint-plugin-react-hooks": "^7.0.1", - "eslint-plugin-react-refresh": "^0.5.2", - "globals": "^17.4.0", - "husky": "^9.1.7", - "jsdom": "^29.0.2", - "lint-staged": "^16.4.0", - "postcss": "^8.5.9", - "prettier": "^3.8.2", - "tailwindcss": "^4.2.2", - "typescript": "~6.0.2", - "typescript-eslint": "^8.58.0", - "vite": "^8.0.4", - "vitest": "^4.1.4" - } - }, - "node_modules/@adobe/css-tools": { - "version": "4.4.4", - "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz", - "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@asamuzakjp/css-color": { - "version": "5.1.10", - "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.10.tgz", - "integrity": "sha512-02OhhkKtgNRuicQ/nF3TRnGsxL9wp0r3Y7VlKWyOHHGmGyvXv03y+PnymU8FKFJMTjIr1Bk8U2g1HWSLrpAHww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@csstools/css-calc": "^3.1.1", - "@csstools/css-color-parser": "^4.0.2", - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/@asamuzakjp/dom-selector": { - "version": "7.0.9", - "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.9.tgz", - "integrity": "sha512-r3ElRr7y8ucyN2KdICwGsmj19RoN13CLCa/pvGydghWK6ZzeKQ+TcDjVdtEZz2ElpndM5jXw//B9CEee0mWnVg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/nwsapi": "^2.3.9", - "bidi-js": "^1.0.3", - "css-tree": "^3.2.1", - "is-potential-custom-element-name": "^1.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/@asamuzakjp/nwsapi": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", - "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@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.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/runtime": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", - "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@bramus/specificity": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", - "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", - "dev": true, - "license": "MIT", - "dependencies": { - "css-tree": "^3.0.0" - }, - "bin": { - "specificity": "bin/cli.js" - } - }, - "node_modules/@csstools/color-helpers": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz", - "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/@csstools/css-calc": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz", - "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/@csstools/css-color-parser": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz", - "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "dependencies": { - "@csstools/color-helpers": "^6.0.2", - "@csstools/css-calc": "^3.1.1" - }, - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-parser-algorithms": "^4.0.0", - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/@csstools/css-parser-algorithms": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", - "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - }, - "peerDependencies": { - "@csstools/css-tokenizer": "^4.0.0" - } - }, - "node_modules/@csstools/css-syntax-patches-for-csstree": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.2.tgz", - "integrity": "sha512-5GkLzz4prTIpoyeUiIu3iV6CSG3Plo7xRVOFPKI7FVEJ3mZ0A8SwK0XU3Gl7xAkiQ+mDyam+NNp875/C5y+jSA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT-0", - "peerDependencies": { - "css-tree": "^3.2.1" - }, - "peerDependenciesMeta": { - "css-tree": { - "optional": true - } - } - }, - "node_modules/@csstools/css-tokenizer": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", - "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/csstools" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/csstools" - } - ], - "license": "MIT", - "engines": { - "node": ">=20.19.0" - } - }, - "node_modules/@emnapi/core": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", - "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/wasi-threads": "1.2.1", - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/runtime": { - "version": "1.9.2", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", - "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", - "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" - } - }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", - "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" - } - }, - "node_modules/@eslint/config-array": { - "version": "0.21.2", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", - "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.7", - "debug": "^4.3.1", - "minimatch": "^3.1.5" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/config-helpers": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", - "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/core": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", - "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "3.3.5", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", - "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ajv": "^6.14.0", - "debug": "^4.3.2", - "espree": "^10.0.1", - "globals": "^14.0.0", - "ignore": "^5.2.0", - "import-fresh": "^3.2.1", - "js-yaml": "^4.1.1", - "minimatch": "^3.1.5", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", - "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@eslint/js": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", - "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - } - }, - "node_modules/@eslint/object-schema": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", - "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", - "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^0.17.0", - "levn": "^0.4.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, - "node_modules/@exodus/bytes": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz", - "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - }, - "peerDependencies": { - "@noble/hashes": "^1.8.0 || ^2.0.0" - }, - "peerDependenciesMeta": { - "@noble/hashes": { - "optional": true - } - } - }, - "node_modules/@humanfs/core": { - "version": "0.19.1", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", - "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.7", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", - "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.1", - "@humanwhocodes/retry": "^0.4.0" - }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", - "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.22" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", - "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } - }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", - "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", - "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" - } - }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", - "dev": true, - "license": "MIT" - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" - } - }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.3.tgz", - "integrity": "sha512-xK9sGVbJWYb08+mTJt3/YV24WxvxpXcXtP6B172paPZ+Ts69Re9dAr7lKwJoeIx8OoeuimEiRZ7umkiUVClmmQ==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@tybys/wasm-util": "^0.10.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.124.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.124.0.tgz", - "integrity": "sha512-VBFWMTBvHxS11Z5Lvlr3IWgrwhMTXV+Md+EQF0Xf60+wAdsGFTBx7X7K/hP4pi8N7dcm1RvcHwDxZ16Qx8keUg==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/Boshen" - } - }, - "node_modules/@playwright/test": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.59.1.tgz", - "integrity": "sha512-PG6q63nQg5c9rIi4/Z5lR5IVF7yU5MqmKaPOe0HSc0O2cX1fPi96sUQu5j7eo4gKCkB2AnNGoWt7y4/Xx3Kcqg==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright": "1.59.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-YYe6aWruPZDtHNpwu7+qAHEMbQ/yRl6atqb/AhznLTnD3UY99Q1jE7ihLSahNWkF4EqRPVC4SiR4O0UkLK02tA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-oArR/ig8wNTPYsXL+Mzhs0oxhxfuHRfG7Ikw7jXsw8mYOtk71W0OkF2VEVh699pdmzjPQsTjlD1JIOoHkLP1Fg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.15.tgz", - "integrity": "sha512-YzeVqOqjPYvUbJSWJ4EDL8ahbmsIXQpgL3JVipmN+MX0XnXMeWomLN3Fb+nwCmP/jfyqte5I3XRSm7OfQrbyxw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.15.tgz", - "integrity": "sha512-9Erhx956jeQ0nNTyif1+QWAXDRD38ZNjr//bSHrt6wDwB+QkAfl2q6Mn1k6OBPerznjRmbM10lgRb1Pli4xZPw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.15.tgz", - "integrity": "sha512-cVwk0w8QbZJGTnP/AHQBs5yNwmpgGYStL88t4UIaqcvYJWBfS0s3oqVLZPwsPU6M0zlW4GqjP0Zq5MnAGwFeGA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-eBZ/u8iAK9SoHGanqe/jrPnY0JvBN6iXbVOsbO38mbz+ZJsaobExAm1Iu+rxa4S1l2FjG0qEZn4Rc6X8n+9M+w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.15.tgz", - "integrity": "sha512-ZvRYMGrAklV9PEkgt4LQM6MjQX2P58HPAuecwYObY2DhS2t35R0I810bKi0wmaYORt6m/2Sm+Z+nFgb0WhXNcQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-VDpgGBzgfg5hLg+uBpCLoFG5kVvEyafmfxGUV0UHLcL5irxAK7PKNeC2MwClgk6ZAiNhmo9FLhRYgvMmedLtnQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-y1uXY3qQWCzcPgRJATPSOUP4tCemh4uBdY7e3EZbVwCJTY3gLJWnQABgeUetvED+bt1FQ01OeZwvhLS2bpNrAQ==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.15.tgz", - "integrity": "sha512-023bTPBod7J3Y/4fzAN6QtpkSABR0rigtrwaP+qSEabUh5zf6ELr9Nc7GujaROuPY3uwdSIXWrvhn1KxOvurWA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.15.tgz", - "integrity": "sha512-witB2O0/hU4CgfOOKUoeFgQ4GktPi1eEbAhaLAIpgD6+ZnhcPkUtPsoKKHRzmOoWPZue46IThdSgdo4XneOLYw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.15.tgz", - "integrity": "sha512-UCL68NJ0Ud5zRipXZE9dF5PmirzJE4E4BCIOOssEnM7wLDsxjc6Qb0sGDxTNRTP53I6MZpygyCpY8Aa8sPfKPg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.15.tgz", - "integrity": "sha512-ApLruZq/ig+nhaE7OJm4lDjayUnOHVUa77zGeqnqZ9pn0ovdVbbNPerVibLXDmWeUZXjIYIT8V3xkT58Rm9u5Q==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.9.2", - "@emnapi/runtime": "1.9.2", - "@napi-rs/wasm-runtime": "^1.1.3" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.15.tgz", - "integrity": "sha512-KmoUoU7HnN+Si5YWJigfTws1jz1bKBYDQKdbLspz0UaqjjFkddHsqorgiW1mxcAj88lYUE6NC/zJNwT+SloqtA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.15.tgz", - "integrity": "sha512-3P2A8L+x75qavWLe/Dll3EYBJLQmtkJN8rfh+U/eR3MqMgL/h98PhYI+JFfXuDPgPeCB7iZAKiqii5vqOvnA0g==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.7", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", - "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@testing-library/dom": { - "version": "10.4.1", - "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", - "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/runtime": "^7.12.5", - "@types/aria-query": "^5.0.1", - "aria-query": "5.3.0", - "dom-accessibility-api": "^0.5.9", - "lz-string": "^1.5.0", - "picocolors": "1.1.1", - "pretty-format": "^27.0.2" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@testing-library/jest-dom": { - "version": "6.9.1", - "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", - "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@adobe/css-tools": "^4.4.0", - "aria-query": "^5.0.0", - "css.escape": "^1.5.1", - "dom-accessibility-api": "^0.6.3", - "picocolors": "^1.1.1", - "redent": "^3.0.0" - }, - "engines": { - "node": ">=14", - "npm": ">=6", - "yarn": ">=1" - } - }, - "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", - "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@testing-library/react": { - "version": "16.3.2", - "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", - "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.12.5" - }, - "engines": { - "node": ">=18" - }, - "peerDependencies": { - "@testing-library/dom": "^10.0.0", - "@types/react": "^18.0.0 || ^19.0.0", - "@types/react-dom": "^18.0.0 || ^19.0.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "@types/react-dom": { - "optional": true - } - } - }, - "node_modules/@testing-library/user-event": { - "version": "14.6.1", - "resolved": "https://registry.npmjs.org/@testing-library/user-event/-/user-event-14.6.1.tgz", - "integrity": "sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12", - "npm": ">=6" - }, - "peerDependencies": { - "@testing-library/dom": ">=7.21.4" - } - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.1", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", - "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", - "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.12.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.2.tgz", - "integrity": "sha512-A1sre26ke7HDIuY/M23nd9gfB+nrmhtYyMINbjI1zHJxYteKR6qSMX56FsmjMcDb3SMcjJg5BiRRgOCC/yBD0g==", - "dev": true, - "license": "MIT", - "dependencies": { - "undici-types": "~7.16.0" - } - }, - "node_modules/@types/react": { - "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "devOptional": true, - "license": "MIT", - "dependencies": { - "csstype": "^3.2.2" - } - }, - "node_modules/@types/react-dom": { - "version": "19.2.3", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", - "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "@types/react": "^19.2.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.1.tgz", - "integrity": "sha512-eSkwoemjo76bdXl2MYqtxg51HNwUSkWfODUOQ3PaTLZGh9uIWWFZIjyjaJnex7wXDu+TRx+ATsnSxdN9YWfRTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/type-utils": "8.58.1", - "@typescript-eslint/utils": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.58.1", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.1.tgz", - "integrity": "sha512-gGkiNMPqerb2cJSVcruigx9eHBlLG14fSdPdqMoOcBfh+vvn4iCq2C8MzUB89PrxOXk0y3GZ1yIWb9aOzL93bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.1.tgz", - "integrity": "sha512-gfQ8fk6cxhtptek+/8ZIqw8YrRW5048Gug8Ts5IYcMLCw18iUgrZAEY/D7s4hkI0FxEfGakKuPK/XUMPzPxi5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.1", - "@typescript-eslint/types": "^8.58.1", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.1.tgz", - "integrity": "sha512-TPYUEqJK6avLcEjumWsIuTpuYODTTDAtoMdt8ZZa93uWMTX13Nb8L5leSje1NluammvU+oI3QRr5lLXPgihX3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.1.tgz", - "integrity": "sha512-JAr2hOIct2Q+qk3G+8YFfqkqi7sC86uNryT+2i5HzMa2MPjw4qNFvtjnw1IiA1rP7QhNKVe21mSSLaSjwA1Olw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.1.tgz", - "integrity": "sha512-HUFxvTJVroT+0rXVJC7eD5zol6ID+Sn5npVPWoFuHGg9Ncq5Q4EYstqR+UOqaNRFXi5TYkpXXkLhoCHe3G0+7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/utils": "8.58.1", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.1.tgz", - "integrity": "sha512-io/dV5Aw5ezwzfPBBWLoT+5QfVtP8O7q4Kftjn5azJ88bYyp/ZMCsyW1lpKK46EXJcaYMZ1JtYj+s/7TdzmQMw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.1.tgz", - "integrity": "sha512-w4w7WR7GHOjqqPnvAYbazq+Y5oS68b9CzasGtnd6jIeOIeKUzYzupGTB2T4LTPSv4d+WPeccbxuneTFHYgAAWg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.58.1", - "@typescript-eslint/tsconfig-utils": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/visitor-keys": "8.58.1", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.1.tgz", - "integrity": "sha512-Ln8R0tmWC7pTtLOzgJzYTXSCjJ9rDNHAqTaVONF4FEi2qwce8mD9iSOxOpLFFvWp/wBFlew0mjM1L1ihYWfBdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.1", - "@typescript-eslint/types": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.1.tgz", - "integrity": "sha512-y+vH7QE8ycjoa0bWciFg7OpFcipUuem1ujhrdLtq1gByKwfbC7bPeKsiny9e0urg93DqwGcHey+bGRKCnF1nZQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.58.1", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@vitejs/plugin-react": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", - "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@rolldown/pluginutils": "1.0.0-rc.7" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "peerDependencies": { - "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", - "babel-plugin-react-compiler": "^1.0.0", - "vite": "^8.0.0" - }, - "peerDependenciesMeta": { - "@rolldown/plugin-babel": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.4.tgz", - "integrity": "sha512-iPBpra+VDuXmBFI3FMKHSFXp3Gx5HfmSCE8X67Dn+bwephCnQCaB7qWK2ldHa+8ncN8hJU8VTMcxjPpyMkUjww==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.4", - "@vitest/utils": "4.1.4", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.4.tgz", - "integrity": "sha512-R9HTZBhW6yCSGbGQnDnH3QHfJxokKN4KB+Yvk9Q1le7eQNYwiCyKxmLmurSpFy6BzJanSLuEUDrD+j97Q+ZLPg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.4", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.4.tgz", - "integrity": "sha512-ddmDHU0gjEUyEVLxtZa7xamrpIefdEETu3nZjWtHeZX4QxqJ7tRxSteHVXJOcr8jhiLoGAhkK4WJ3WqBpjx42A==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.4.tgz", - "integrity": "sha512-xTp7VZ5aXP5ZJrn15UtJUWlx6qXLnGtF6jNxHepdPHpMfz/aVPx+htHtgcAL2mDXJgKhpoo2e9/hVJsIeFbytQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.4", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.4.tgz", - "integrity": "sha512-MCjCFgaS8aZz+m5nTcEcgk/xhWv0rEH4Yl53PPlMXOZ1/Ka2VcZU6CJ+MgYCZbcJvzGhQRjVrGQNZqkGPttIKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.4", - "@vitest/utils": "4.1.4", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.4.tgz", - "integrity": "sha512-XxNdAsKW7C+FLydqFJLb5KhJtl3PGCMmYwFRfhvIgxJvLSXhhVI1zM8f1qD3Zg7RCjTSzDVyct6sghs9UEgBEQ==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.4.tgz", - "integrity": "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.4", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ansi-escapes": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz", - "integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==", - "dev": true, - "license": "MIT", - "dependencies": { - "environment": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true, - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/autoprefixer": { - "version": "10.4.27", - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.27.tgz", - "integrity": "sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/autoprefixer" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "browserslist": "^4.28.1", - "caniuse-lite": "^1.0.30001774", - "fraction.js": "^5.3.4", - "picocolors": "^1.1.1", - "postcss-value-parser": "^4.2.0" - }, - "bin": { - "autoprefixer": "bin/autoprefixer" - }, - "engines": { - "node": "^10 || ^12 || >=14" - }, - "peerDependencies": { - "postcss": "^8.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.18", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.18.tgz", - "integrity": "sha512-VSnGQAOLtP5mib/DPyg2/t+Tlv65NTBz83BJBJvmLVHHuKJVaDOBvJJykiT5TR++em5nfAySPccDZDa4oSrn8A==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/bidi-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", - "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", - "dev": true, - "license": "MIT", - "dependencies": { - "require-from-string": "^2.0.2" - } - }, - "node_modules/brace-expansion": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", - "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001787", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001787.tgz", - "integrity": "sha512-mNcrMN9KeI68u7muanUpEejSLghOKlVhRqS/Za2IeyGllJ9I9otGpR9g3nsw7n4W378TE/LyIteA0+/FOZm4Kg==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cli-cursor": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", - "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "restore-cursor": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-truncate": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.2.0.tgz", - "integrity": "sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==", - "dev": true, - "license": "MIT", - "dependencies": { - "slice-ansi": "^8.0.0", - "string-width": "^8.2.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true, - "license": "MIT" - }, - "node_modules/colorette": { - "version": "2.0.20", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", - "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", - "dev": true, - "license": "MIT" - }, - "node_modules/commander": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.3.tgz", - "integrity": "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, - "license": "MIT" - }, - "node_modules/cookie": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", - "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/css-tree": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", - "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", - "dev": true, - "license": "MIT", - "dependencies": { - "mdn-data": "2.27.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" - } - }, - "node_modules/css.escape": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", - "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", - "dev": true, - "license": "MIT" - }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/data-urls": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", - "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", - "dev": true, - "license": "MIT", - "dependencies": { - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decimal.js": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", - "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", - "dev": true, - "license": "MIT" - }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/dom-accessibility-api": { - "version": "0.5.16", - "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", - "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/electron-to-chromium": { - "version": "1.5.335", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.335.tgz", - "integrity": "sha512-q9n5T4BR4Xwa2cwbrwcsDJtHD/enpQ5S1xF1IAtdqf5AAgqDFmR/aakqH3ChFdqd/QXJhS3rnnXFtexU7rax6Q==", - "dev": true, - "license": "ISC" - }, - "node_modules/emoji-regex": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", - "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", - "dev": true, - "license": "MIT" - }, - "node_modules/entities": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz", - "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/environment": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", - "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/es-module-lexer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", - "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", - "dev": true, - "license": "MIT" - }, - "node_modules/escalade": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint": { - "version": "9.39.4", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", - "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.1", - "@eslint/config-array": "^0.21.2", - "@eslint/config-helpers": "^0.4.2", - "@eslint/core": "^0.17.0", - "@eslint/eslintrc": "^3.3.5", - "@eslint/js": "9.39.4", - "@eslint/plugin-kit": "^0.4.1", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.4.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "esquery": "^1.5.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.1.5", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } - } - }, - "node_modules/eslint-plugin-react-hooks": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", - "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/core": "^7.24.4", - "@babel/parser": "^7.24.4", - "hermes-parser": "^0.25.1", - "zod": "^3.25.0 || ^4.0.0", - "zod-validation-error": "^3.5.0 || ^4.0.0" - }, - "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" - } - }, - "node_modules/eslint-plugin-react-refresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", - "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "eslint": "^9 || ^10" - } - }, - "node_modules/eslint-scope": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", - "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/esquery": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", - "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/expect-type": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz", - "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==" - }, - "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "flat-cache": "^4.0.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "dev": true, - "license": "MIT", - "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/fraction.js": { - "version": "5.3.4", - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", - "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/rawify" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-east-asian-width": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz", - "integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/globals": { - "version": "17.5.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.5.0.tgz", - "integrity": "sha512-qoV+HK2yFl/366t2/Cb3+xxPUo5BuMynomoDmiaZBIdbs+0pYbjfZU+twLhGKp4uCZ/+NbtpVepH5bGCxRyy2g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/hermes-estree": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", - "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", - "dev": true, - "license": "MIT" - }, - "node_modules/hermes-parser": { - "version": "0.25.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", - "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "hermes-estree": "0.25.1" - } - }, - "node_modules/html-encoding-sniffer": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", - "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.6.0" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/husky": { - "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", - "dev": true, - "license": "MIT", - "bin": { - "husky": "bin.js" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/typicode" - } - }, - "node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", - "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.3.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", - "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true, - "license": "ISC" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", - "dev": true, - "license": "MIT", - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsdom": { - "version": "29.0.2", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.2.tgz", - "integrity": "sha512-9VnGEBosc/ZpwyOsJBCQ/3I5p7Q5ngOY14a9bf5btenAORmZfDse1ZEheMiWcJ3h81+Fv7HmJFdS0szo/waF2w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@asamuzakjp/css-color": "^5.1.5", - "@asamuzakjp/dom-selector": "^7.0.6", - "@bramus/specificity": "^2.4.2", - "@csstools/css-syntax-patches-for-csstree": "^1.1.1", - "@exodus/bytes": "^1.15.0", - "css-tree": "^3.2.1", - "data-urls": "^7.0.0", - "decimal.js": "^10.6.0", - "html-encoding-sniffer": "^6.0.0", - "is-potential-custom-element-name": "^1.0.1", - "lru-cache": "^11.2.7", - "parse5": "^8.0.0", - "saxes": "^6.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^6.0.1", - "undici": "^7.24.5", - "w3c-xmlserializer": "^5.0.0", - "webidl-conversions": "^8.0.1", - "whatwg-mimetype": "^5.0.0", - "whatwg-url": "^16.0.1", - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24.0.0" - }, - "peerDependencies": { - "canvas": "^3.0.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/lru-cache": { - "version": "11.3.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.3.3.tgz", - "integrity": "sha512-JvNw9Y81y33E+BEYPr0U7omo+U9AySnsMsEiXgwT6yqd31VQWTLNQqmT4ou5eqPFUrTfIDFta2wKhB1hyohtAQ==", - "dev": true, - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", - "dev": true, - "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/json5": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", - "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/keyv": { - "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "json-buffer": "3.0.1" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", - "dev": true, - "license": "MPL-2.0", - "dependencies": { - "detect-libc": "^2.0.3" - }, - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" - } - }, - "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">= 12.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - } - }, - "node_modules/lint-staged": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.4.0.tgz", - "integrity": "sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==", - "dev": true, - "license": "MIT", - "dependencies": { - "commander": "^14.0.3", - "listr2": "^9.0.5", - "picomatch": "^4.0.3", - "string-argv": "^0.3.2", - "tinyexec": "^1.0.4", - "yaml": "^2.8.2" - }, - "bin": { - "lint-staged": "bin/lint-staged.js" - }, - "engines": { - "node": ">=20.17" - }, - "funding": { - "url": "https://opencollective.com/lint-staged" - } - }, - "node_modules/listr2": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", - "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "cli-truncate": "^5.0.0", - "colorette": "^2.0.20", - "eventemitter3": "^5.0.1", - "log-update": "^6.1.0", - "rfdc": "^1.4.1", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^5.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/log-update": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", - "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^7.0.0", - "cli-cursor": "^5.0.0", - "slice-ansi": "^7.1.0", - "strip-ansi": "^7.1.0", - "wrap-ansi": "^9.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", - "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/lz-string": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", - "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", - "dev": true, - "license": "MIT", - "peer": true, - "bin": { - "lz-string": "bin/bin.js" - } - }, - "node_modules/magic-string": { - "version": "0.30.21", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", - "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" - } - }, - "node_modules/mdn-data": { - "version": "2.27.1", - "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", - "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", - "dev": true, - "license": "CC0-1.0" - }, - "node_modules/mimic-function": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", - "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/min-indent": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", - "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true, - "license": "MIT" - }, - "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, - "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", - "dev": true, - "license": "MIT" - }, - "node_modules/node-releases": { - "version": "2.0.37", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", - "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", - "dev": true, - "license": "MIT" - }, - "node_modules/obug": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz", - "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==", - "dev": true, - "funding": [ - "https://github.com/sponsors/sxzz", - "https://opencollective.com/debug" - ], - "license": "MIT" - }, - "node_modules/onetime": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", - "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "mimic-function": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/optionator": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", - "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", - "dev": true, - "license": "MIT", - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.5" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "yocto-queue": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse5": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz", - "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==", - "dev": true, - "license": "MIT", - "dependencies": { - "entities": "^6.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/pathe": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", - "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, - "license": "ISC" - }, - "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/playwright": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz", - "integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "playwright-core": "1.59.1" - }, - "bin": { - "playwright": "cli.js" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "fsevents": "2.3.2" - } - }, - "node_modules/playwright-core": { - "version": "1.59.1", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz", - "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "playwright-core": "cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/playwright/node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/postcss": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz", - "integrity": "sha512-7a70Nsot+EMX9fFU3064K/kdHWZqGVY+BADLyXc8Dfv+mTLLVl6JzJpPaCZ2kQL9gIJvKXSLMHhqdRRjwQeFtw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "nanoid": "^3.3.11", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" - }, - "engines": { - "node": "^10 || ^12 || >=14" - } - }, - "node_modules/postcss-value-parser": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.2.tgz", - "integrity": "sha512-8c3mgTe0ASwWAJK+78dpviD+A8EqhndQPUBpNUIPt6+xWlIigCwfN01lWr9MAede4uqXGTEKeQWTvzb3vjia0Q==", - "dev": true, - "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" - } - }, - "node_modules/pretty-format": { - "version": "27.5.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", - "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", - "dev": true, - "license": "MIT", - "peer": true, - "dependencies": { - "ansi-regex": "^5.0.1", - "ansi-styles": "^5.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" - } - }, - "node_modules/pretty-format/node_modules/ansi-styles": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", - "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", - "dev": true, - "license": "MIT", - "peer": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/react": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", - "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/react-dom": { - "version": "19.2.5", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", - "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", - "license": "MIT", - "dependencies": { - "scheduler": "^0.27.0" - }, - "peerDependencies": { - "react": "^19.2.5" - } - }, - "node_modules/react-is": { - "version": "17.0.2", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", - "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", - "dev": true, - "license": "MIT", - "peer": true - }, - "node_modules/react-router": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.0.tgz", - "integrity": "sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==", - "license": "MIT", - "dependencies": { - "cookie": "^1.0.1", - "set-cookie-parser": "^2.6.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - }, - "peerDependenciesMeta": { - "react-dom": { - "optional": true - } - } - }, - "node_modules/react-router-dom": { - "version": "7.14.0", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.0.tgz", - "integrity": "sha512-2G3ajSVSZMEtmTjIklRWlNvo8wICEpLihfD/0YMDxbWK2UyP5EGfnoIn9AIQGnF3G/FX0MRbHXdFcD+rL1ZreQ==", - "license": "MIT", - "dependencies": { - "react-router": "7.14.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "peerDependencies": { - "react": ">=18", - "react-dom": ">=18" - } - }, - "node_modules/redent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", - "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", - "dev": true, - "license": "MIT", - "dependencies": { - "indent-string": "^4.0.0", - "strip-indent": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/restore-cursor": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", - "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", - "dev": true, - "license": "MIT", - "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, - "node_modules/rolldown": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.15.tgz", - "integrity": "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@oxc-project/types": "=0.124.0", - "@rolldown/pluginutils": "1.0.0-rc.15" - }, - "bin": { - "rolldown": "bin/cli.mjs" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", - "@rolldown/binding-darwin-x64": "1.0.0-rc.15", - "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", - "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", - "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", - "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", - "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", - "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", - "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" - } - }, - "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-rc.15", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.15.tgz", - "integrity": "sha512-UromN0peaE53IaBRe9W7CjrZgXl90fqGpK+mIZbA3qSTeYqg3pqpROBdIPvOG3F5ereDHNwoHBI2e50n1BDr1g==", - "dev": true, - "license": "MIT" - }, - "node_modules/saxes": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", - "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", - "dev": true, - "license": "ISC", - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=v12.22.7" - } - }, - "node_modules/scheduler": { - "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", - "license": "MIT" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "license": "MIT", - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, - "node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/slice-ansi": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-8.0.0.tgz", - "integrity": "sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.3", - "is-fullwidth-code-point": "^5.1.0" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/std-env": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz", - "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/string-argv": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", - "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.6.19" - } - }, - "node_modules/string-width": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.0.tgz", - "integrity": "sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.5.0", - "strip-ansi": "^7.1.2" - }, - "engines": { - "node": ">=20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/strip-ansi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", - "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^6.2.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/strip-ansi?sponsor=1" - } - }, - "node_modules/strip-ansi/node_modules/ansi-regex": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", - "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-regex?sponsor=1" - } - }, - "node_modules/strip-indent": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", - "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "min-indent": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true, - "license": "MIT" - }, - "node_modules/tailwindcss": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz", - "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinybench": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", - "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/tinyexec": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.1.tgz", - "integrity": "sha512-VKS/ZaQhhkKFMANmAOhhXVoIfBXblQxGX1myCQ2faQrfmobMftXeJPcZGp0gS07ocvGJWDLZGyOZDadDBqYIJg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", - "dev": true, - "license": "MIT", - "dependencies": { - "fdir": "^6.5.0", - "picomatch": "^4.0.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/SuperchupuDev" - } - }, - "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/tldts": { - "version": "7.0.28", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.28.tgz", - "integrity": "sha512-+Zg3vWhRUv8B1maGSTFdev9mjoo8Etn2Ayfs4cnjlD3CsGkxXX4QyW3j2WJ0wdjYcYmy7Lx2RDsZMhgCWafKIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "tldts-core": "^7.0.28" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "7.0.28", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.28.tgz", - "integrity": "sha512-7W5Efjhsc3chVdFhqtaU0KtK32J37Zcr9RKtID54nG+tIpcY79CQK/veYPODxtD/LJ4Lue66jvrQzIX2Z2/pUQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/tough-cookie": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", - "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "tldts": "^7.0.5" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tr46": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", - "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=20" - } - }, - "node_modules/ts-api-utils": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", - "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.12" - }, - "peerDependencies": { - "typescript": ">=4.8.4" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "dev": true, - "license": "0BSD", - "optional": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "license": "MIT", - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/typescript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", - "integrity": "sha512-bGdAIrZ0wiGDo5l8c++HWtbaNCWTS4UTv7RaTH/ThVIgjkveJt83m74bBHMJkuCbslY8ixgLBVZJIOiQlQTjfQ==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/typescript-eslint": { - "version": "8.58.1", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.1.tgz", - "integrity": "sha512-gf6/oHChByg9HJvhMO1iBexJh12AqqTfnuxscMDOVqfJW3htsdRJI/GfPpHTTcyeB8cSTUY2JcZmVgoyPqcrDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/eslint-plugin": "8.58.1", - "@typescript-eslint/parser": "8.58.1", - "@typescript-eslint/typescript-estree": "8.58.1", - "@typescript-eslint/utils": "8.58.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/undici": { - "version": "7.24.7", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.7.tgz", - "integrity": "sha512-H/nlJ/h0ggGC+uRL3ovD+G0i4bqhvsDOpbDv7At5eFLlj2b41L8QliGbnl2H7SnDiYhENphh1tQFJZf+MyfLsQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.18.1" - } - }, - "node_modules/undici-types": { - "version": "7.16.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", - "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", - "dev": true, - "license": "MIT" - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", - "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/vite": { - "version": "8.0.8", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.8.tgz", - "integrity": "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw==", - "dev": true, - "license": "MIT", - "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.8", - "rolldown": "1.0.0-rc.15", - "tinyglobby": "^0.2.15" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.0", - "esbuild": "^0.27.0 || ^0.28.0", - "jiti": ">=1.21.0", - "less": "^4.0.0", - "sass": "^1.70.0", - "sass-embedded": "^1.70.0", - "stylus": ">=0.54.8", - "sugarss": "^5.0.0", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "@vitejs/devtools": { - "optional": true - }, - "esbuild": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitest": { - "version": "4.1.4", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.4.tgz", - "integrity": "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/expect": "4.1.4", - "@vitest/mocker": "4.1.4", - "@vitest/pretty-format": "4.1.4", - "@vitest/runner": "4.1.4", - "@vitest/snapshot": "4.1.4", - "@vitest/spy": "4.1.4", - "@vitest/utils": "4.1.4", - "es-module-lexer": "^2.0.0", - "expect-type": "^1.3.0", - "magic-string": "^0.30.21", - "obug": "^2.1.1", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "std-env": "^4.0.0-rc.1", - "tinybench": "^2.9.0", - "tinyexec": "^1.0.2", - "tinyglobby": "^0.2.15", - "tinyrainbow": "^3.1.0", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", - "why-is-node-running": "^2.3.0" - }, - "bin": { - "vitest": "vitest.mjs" - }, - "engines": { - "node": "^20.0.0 || ^22.0.0 || >=24.0.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@edge-runtime/vm": "*", - "@opentelemetry/api": "^1.9.0", - "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.4", - "@vitest/browser-preview": "4.1.4", - "@vitest/browser-webdriverio": "4.1.4", - "@vitest/coverage-istanbul": "4.1.4", - "@vitest/coverage-v8": "4.1.4", - "@vitest/ui": "4.1.4", - "happy-dom": "*", - "jsdom": "*", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "@edge-runtime/vm": { - "optional": true - }, - "@opentelemetry/api": { - "optional": true - }, - "@types/node": { - "optional": true - }, - "@vitest/browser-playwright": { - "optional": true - }, - "@vitest/browser-preview": { - "optional": true - }, - "@vitest/browser-webdriverio": { - "optional": true - }, - "@vitest/coverage-istanbul": { - "optional": true - }, - "@vitest/coverage-v8": { - "optional": true - }, - "@vitest/ui": { - "optional": true - }, - "happy-dom": { - "optional": true - }, - "jsdom": { - "optional": true - }, - "vite": { - "optional": false - } - } - }, - "node_modules/w3c-xmlserializer": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", - "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", - "dev": true, - "license": "MIT", - "dependencies": { - "xml-name-validator": "^5.0.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/webidl-conversions": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", - "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=20" - } - }, - "node_modules/whatwg-mimetype": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", - "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/whatwg-url": { - "version": "16.0.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", - "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@exodus/bytes": "^1.11.0", - "tr46": "^6.0.0", - "webidl-conversions": "^8.0.1" - }, - "engines": { - "node": "^20.19.0 || ^22.12.0 || >=24.0.0" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", - "dev": true, - "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, - "bin": { - "why-is-node-running": "cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/word-wrap": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", - "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wrap-ansi": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", - "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "string-width": "^7.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", - "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/xml-name-validator": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", - "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18" - } - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true, - "license": "MIT" - }, - "node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true, - "license": "ISC" - }, - "node_modules/yaml": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.3.tgz", - "integrity": "sha512-AvbaCLOO2Otw/lW5bmh9d/WEdcDFdQp2Z2ZUH3pX9U2ihyUY0nvLv7J6TrWowklRGPYbB/IuIMfYgxaCPg5Bpg==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14.6" - }, - "funding": { - "url": "https://github.com/sponsors/eemeli" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/zod": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", - "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/zod-validation-error": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", - "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "peerDependencies": { - "zod": "^3.25.0 || ^4.0.0" - } - }, - "node_modules/zustand": { - "version": "5.0.12", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz", - "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - } - } -} diff --git a/package.json b/package.json index feda84c..c2c845e 100644 --- a/package.json +++ b/package.json @@ -2,11 +2,13 @@ "name": "puzzlekit-web", "private": true, "version": "0.0.0", + "packageManager": "pnpm@10.33.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc -b && vite build", "lint": "eslint .", + "benchmark:solve": "tsx scripts/benchmark-solve.ts", "preview": "vite preview", "test": "vitest", "test:run": "vitest run", @@ -33,7 +35,7 @@ "@vitejs/plugin-react": "^6.0.1", "autoprefixer": "^10.4.27", "eslint": "^9.39.4", - "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-hooks": "7.0.1", "eslint-plugin-react-refresh": "^0.5.2", "globals": "^17.4.0", "husky": "^9.1.7", @@ -42,6 +44,7 @@ "postcss": "^8.5.9", "prettier": "^3.8.2", "tailwindcss": "^4.2.2", + "tsx": "^4.21.0", "typescript": "~6.0.2", "typescript-eslint": "^8.58.0", "vite": "^8.0.4", diff --git a/playwright.config.ts b/playwright.config.ts index e38d3e6..02aa00d 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -8,7 +8,7 @@ export default defineConfig({ trace: 'on-first-retry', }, webServer: { - command: `"${process.execPath}" ./node_modules/vite/bin/vite.js --host 127.0.0.1 --port 4173`, + command: 'pnpm vite --host 127.0.0.1 --port 4173', port: 4173, reuseExistingServer: true, }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..19d6246 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,3317 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + fflate: + specifier: ^0.8.2 + version: 0.8.2 + react: + specifier: ^19.2.4 + version: 19.2.6 + react-dom: + specifier: ^19.2.4 + version: 19.2.6(react@19.2.6) + react-router-dom: + specifier: ^7.14.0 + version: 7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + zod: + specifier: ^4.3.6 + version: 4.4.3 + zustand: + specifier: ^5.0.12 + version: 5.0.13(@types/react@19.2.14)(react@19.2.6) + devDependencies: + '@eslint/js': + specifier: ^9.39.4 + version: 9.39.4 + '@playwright/test': + specifier: ^1.59.1 + version: 1.60.0 + '@testing-library/jest-dom': + specifier: ^6.9.1 + version: 6.9.1 + '@testing-library/react': + specifier: ^16.3.2 + version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + '@testing-library/user-event': + specifier: ^14.6.1 + version: 14.6.1(@testing-library/dom@10.4.1) + '@types/node': + specifier: ^24.12.2 + version: 24.12.4 + '@types/react': + specifier: ^19.2.14 + version: 19.2.14 + '@types/react-dom': + specifier: ^19.2.3 + version: 19.2.3(@types/react@19.2.14) + '@vitejs/plugin-react': + specifier: ^6.0.1 + version: 6.0.1(vite@8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.9.0)) + autoprefixer: + specifier: ^10.4.27 + version: 10.5.0(postcss@8.5.14) + eslint: + specifier: ^9.39.4 + version: 9.39.4 + eslint-plugin-react-hooks: + specifier: 7.0.1 + version: 7.0.1(eslint@9.39.4) + eslint-plugin-react-refresh: + specifier: ^0.5.2 + version: 0.5.2(eslint@9.39.4) + globals: + specifier: ^17.4.0 + version: 17.6.0 + husky: + specifier: ^9.1.7 + version: 9.1.7 + jsdom: + specifier: ^29.0.2 + version: 29.1.1 + lint-staged: + specifier: ^16.4.0 + version: 16.4.0 + postcss: + specifier: ^8.5.9 + version: 8.5.14 + prettier: + specifier: ^3.8.2 + version: 3.8.3 + tailwindcss: + specifier: ^4.2.2 + version: 4.3.0 + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ~6.0.2 + version: 6.0.3 + typescript-eslint: + specifier: ^8.58.0 + version: 8.59.3(eslint@9.39.4)(typescript@6.0.3) + vite: + specifier: ^8.0.4 + version: 8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.9.0) + vitest: + specifier: ^4.1.4 + version: 4.1.6(@types/node@24.12.4)(jsdom@29.1.1)(vite@8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.9.0)) + +packages: + + '@adobe/css-tools@4.4.4': + resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==} + + '@asamuzakjp/css-color@5.1.11': + resolution: {integrity: sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/dom-selector@7.1.1': + resolution: {integrity: sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/generational-cache@1.0.1': + resolution: {integrity: sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + '@asamuzakjp/nwsapi@2.3.9': + resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.3': + resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} + 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.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/runtime@7.29.2': + resolution: {integrity: sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==} + engines: {node: '>=6.9.0'} + + '@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'} + + '@bramus/specificity@2.4.2': + resolution: {integrity: sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==} + hasBin: true + + '@csstools/color-helpers@6.0.2': + resolution: {integrity: sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==} + engines: {node: '>=20.19.0'} + + '@csstools/css-calc@3.2.1': + resolution: {integrity: sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-color-parser@4.1.1': + resolution: {integrity: sha512-eZ5XOtyhK+mggRafYUWzA0tvaYOFgdY8AkgQiCJF9qNAePnUo/zmsqqYubBBb3sQ8uNUaSKTY9s9klfRaAXL0g==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-parser-algorithms': ^4.0.0 + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-parser-algorithms@4.0.0': + resolution: {integrity: sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==} + engines: {node: '>=20.19.0'} + peerDependencies: + '@csstools/css-tokenizer': ^4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.4': + resolution: {integrity: sha512-wgsqt92b7C7tQhIdPNxj0n9zuUbQlvAuI1exyzeNrOKOi62SD7ren8zqszmpVREjAOqg8cD2FqYhQfAuKjk4sw==} + peerDependencies: + css-tree: ^3.2.1 + peerDependenciesMeta: + css-tree: + optional: true + + '@csstools/css-tokenizer@4.0.0': + resolution: {integrity: sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==} + engines: {node: '>=20.19.0'} + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@esbuild/aix-ppc64@0.27.7': + resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.27.7': + resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.27.7': + resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.27.7': + resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.27.7': + resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.7': + resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.27.7': + resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.7': + resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.27.7': + resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.27.7': + resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.27.7': + resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.27.7': + resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.27.7': + resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.27.7': + resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.7': + resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.27.7': + resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.27.7': + resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.27.7': + resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.7': + resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.27.7': + resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.7': + resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.27.7': + resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.27.7': + resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.27.7': + resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.27.7': + resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.27.7': + resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/config-array@0.21.2': + resolution: {integrity: sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/config-helpers@0.4.2': + resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.17.0': + resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/eslintrc@3.3.5': + resolution: {integrity: sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/js@9.39.4': + resolution: {integrity: sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/object-schema@2.1.7': + resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/plugin-kit@0.4.1': + resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@exodus/bytes@1.15.0': + resolution: {integrity: sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + peerDependencies: + '@noble/hashes': ^1.8.0 || ^2.0.0 + peerDependenciesMeta: + '@noble/hashes': + optional: true + + '@humanfs/core@0.19.2': + resolution: {integrity: sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.8': + resolution: {integrity: sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==} + engines: {node: '>=18.18.0'} + + '@humanfs/types@0.15.0': + resolution: {integrity: sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==} + engines: {node: '>=18.18.0'} + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/retry@0.4.3': + resolution: {integrity: sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==} + engines: {node: '>=18.18'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.4': + resolution: {integrity: sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.129.0': + resolution: {integrity: sha512-3oz8m3FGdr2nDXVqmFUw7jolKliC4MoyXYIG2c7gpjBnzUWQpUGIYcXYKxTdTi+N2jusvt610ckTMkxdwHkYEg==} + + '@playwright/test@1.60.0': + resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} + engines: {node: '>=18'} + hasBin: true + + '@rolldown/binding-android-arm64@1.0.0': + resolution: {integrity: sha512-TWMZnRLMe63C2Lhyicviu7ZHaU4kxa6PS3rofvc9GmcvptzNN11BcfQ4Sl7MwTOsisQoa2keB/EBdNCAnUo8vA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.0': + resolution: {integrity: sha512-6XcD+8k0gPVItNagEw78/qqcBDwKcwDYS8V2hRmVsfUSIrd8cWe/CBvRDI5toqFyPfj+FJr6t8U6Xj2P2prEew==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.0': + resolution: {integrity: sha512-iN/tWVXRQDWvmZlKdceP1Dwug9GDpEymhb9p4xnEe6zvCg5lFmzVljl+1qR1NVx3yfGpr2Na+CuLmv5IU8uzfQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.0': + resolution: {integrity: sha512-jjQMDvvwSOuhOwMszD/klSOjyWMM3zI64hWTj9KT5x4MxRbZAf+7vLQ6qouRhtsLVFHr3f0ILaJAfgENPiQdAQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0': + resolution: {integrity: sha512-d//Dtg2x6/m3mbV64yUGNnDGNZaDGRpDLLNGerHQUVObuNaIQaaDp25yUiqGXtHEXX+NP2d0wAlmKgpYgIAJ2A==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.0': + resolution: {integrity: sha512-n7Ofp0mx+aB2cC+Sdy5YtMnXtY9lchnHbY+3Yt0uq9JsWQExf4f5Whu0tK0R8Jdc9S6RchTHjIFY7uc92puOVQ==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-arm64-musl@1.0.0': + resolution: {integrity: sha512-EIVjy2cgd7uuMMo94FVkBp7F6DhcZAUwNURkSG3RwUmvAXR6s0ISxM81U+IydcZByPG0pZIHsf1b6kTxoFDgJA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rolldown/binding-linux-ppc64-gnu@1.0.0': + resolution: {integrity: sha512-JEwwOPcwTLAcpDQlqSmjEmfs63xJnSiUNIGvLcDLUHCWK4XowpS/7c7tUsUH6uT/ct6bMUTdXKfI8967FYj6mg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-s390x-gnu@1.0.0': + resolution: {integrity: sha512-0wjCFhLrihtAubnT9iA0N++0pSV0z5Hg7tNGdNJ4RFaINceHadoF+kiFGyY1qSSNVIAZtLotG8Ju1bgDPkjnFA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-gnu@1.0.0': + resolution: {integrity: sha512-Dfn7iak9BcMMePxcoJfpSbWqnEyrp/dRF63/8qW/eHBdOZov6x5aShLLEYGYdIeSJ6vMLK/XCVB+lGIxm41bQA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rolldown/binding-linux-x64-musl@1.0.0': + resolution: {integrity: sha512-5/utzzDmD/pD/bmuaUcbTf/sZYy0aztwIVlfpoW1fTjCZ0BaPOMVWGZL1zvgxyi7ZIVYWlxKONHmSbHuiOh8Jw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rolldown/binding-openharmony-arm64@1.0.0': + resolution: {integrity: sha512-ouJs8VcUomfLfpbUECqFMRqdV4x6aeAK3MA4m6vTrJJjKyWTV5KnxZx7Jd9G+GlDaQQxubcba00x16OyJ1meig==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.0': + resolution: {integrity: sha512-E+oHKGiDA+lsKMmFtffDDw91EryDT7uJocrIuCHqhm6bCTM6xFK+3gaCkYOHfPwQr0cCNarSM2xaELoQDz9jJg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.0': + resolution: {integrity: sha512-yYK02n8Rngo+gbm1y6G0+7jk1sJ/2Wt7K0me0Y7k/ErBpyf+LJ2gFpqWVTcRV1rUepBlQRmpgWkTQCiiwrK0Ow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.0': + resolution: {integrity: sha512-14bpChMahXRRXiTwahSl+zzHPW6qQTXtkMuJBFlbo+pqSAews2d4BdCSHfrJ/MBsCZtpmTafsY+1QhBzitcmdg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.0': + resolution: {integrity: sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==} + + '@rolldown/pluginutils@1.0.0-rc.7': + resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@testing-library/dom@10.4.1': + resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==} + engines: {node: '>=18'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react@16.3.2': + resolution: {integrity: sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==} + engines: {node: '>=18'} + peerDependencies: + '@testing-library/dom': ^10.0.0 + '@types/react': ^18.0.0 || ^19.0.0 + '@types/react-dom': ^18.0.0 || ^19.0.0 + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@testing-library/user-event@14.6.1': + resolution: {integrity: sha512-vq7fv0rnt+QTXgPxr5Hjc210p6YKq2kmdziLgnsZGgLJ9e6VAShx1pACLuRjd/AS/sr7phAR58OIIpf0LlmQNw==} + engines: {node: '>=12', npm: '>=6'} + peerDependencies: + '@testing-library/dom': '>=7.21.4' + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/node@24.12.4': + resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.14': + resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} + + '@typescript-eslint/eslint-plugin@8.59.3': + resolution: {integrity: sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + '@typescript-eslint/parser': ^8.59.3 + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/parser@8.59.3': + resolution: {integrity: sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/project-service@8.59.3': + resolution: {integrity: sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/scope-manager@8.59.3': + resolution: {integrity: sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/tsconfig-utils@8.59.3': + resolution: {integrity: sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/type-utils@8.59.3': + resolution: {integrity: sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/types@8.59.3': + resolution: {integrity: sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@typescript-eslint/typescript-estree@8.59.3': + resolution: {integrity: sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/utils@8.59.3': + resolution: {integrity: sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + '@typescript-eslint/visitor-keys@8.59.3': + resolution: {integrity: sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@vitejs/plugin-react@6.0.1': + resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 + babel-plugin-react-compiler: ^1.0.0 + vite: ^8.0.0 + peerDependenciesMeta: + '@rolldown/plugin-babel': + optional: true + babel-plugin-react-compiler: + optional: true + + '@vitest/expect@4.1.6': + resolution: {integrity: sha512-7EHDquPthALSV0jhhjgEW8FXaviMx7rSqu8W6oqCoAuOhKov814P99QDV1pxMA3QPv21YudvJngIhjrNI4opLg==} + + '@vitest/mocker@4.1.6': + resolution: {integrity: sha512-MCFc63czMjEInOlcY2cpQCvCN+KgbAn+60xu9cMgP4sKaLC5JNAKw7JH8QdAnoAC88hW1IiSNZ+GgVXlN1UcMQ==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.1.6': + resolution: {integrity: sha512-h5SxD/IzNhZYnrSZRsUZQIC+vD0GY8cUvq0iwsmkFKixRCKLLWqCXa/FIQ4S1R+sI+PGoojkHsdNrbZiM9Qpgw==} + + '@vitest/runner@4.1.6': + resolution: {integrity: sha512-nOPCmn2+yD0ZNmKdsXGv/UxMMWbMuKeD6GyYncNwdkYDxpQvrPSKYj2rWuDjC2Y4b6w6hjip5dBKFzEUuZe3vA==} + + '@vitest/snapshot@4.1.6': + resolution: {integrity: sha512-YhsdE6xAVfTDmzjxL2ZDUvjj+ZsgyOKe+TdQzqkD72wIOmHka8NuGQ6NpTNZv9D2Z63fbwWKJPeVpEw4EQgYxw==} + + '@vitest/spy@4.1.6': + resolution: {integrity: sha512-JFKxMx6udhwKh/Ldo270e17QX710vgunMkuPAvXjHSvC6oqLWAHhVhjg/I71q0u0CBSErIODV1Kjv0FQNSWjdg==} + + '@vitest/utils@4.1.6': + resolution: {integrity: sha512-FxIY+U81R3LGKCxaHHFRQ5+g6/iRgGLmeHWdp2Amj4ljQRrEIWHmZyDfDYBRZlpyqA7qKxtS9DD1dhk8RnRIVQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv@6.15.0: + resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} + engines: {node: '>=18'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.0: + resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + autoprefixer@10.5.0: + resolution: {integrity: sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.29: + resolution: {integrity: sha512-Asa2krT+XTPZINCS+2QcyS8WTkObE77RwkydwF7h6DmnKqbvlalz93m/dnphUyCa6SWSP51VgtEUf2FN+gelFQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + bidi-js@1.0.3: + resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + + brace-expansion@1.1.14: + resolution: {integrity: sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} + + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-lite@1.0.30001792: + resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + cli-cursor@5.0.0: + resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} + engines: {node: '>=18'} + + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + commander@14.0.3: + resolution: {integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==} + engines: {node: '>=20'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-tree@3.2.1: + resolution: {integrity: sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + data-urls@7.0.0: + resolution: {integrity: sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + electron-to-chromium@1.5.354: + resolution: {integrity: sha512-JaBHwWcfIdmSAfWM5l3uwjGd431j8YEMikZ+K/2nXVuBqJKyZ0f+2h4n4JY5AyNiZmnY9qQr2RU3v9DxDmHMNg==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + entities@8.0.0: + resolution: {integrity: sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==} + engines: {node: '>=20.19.0'} + + environment@1.1.0: + resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==} + engines: {node: '>=18'} + + es-module-lexer@2.1.0: + resolution: {integrity: sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==} + + esbuild@0.27.7: + resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + 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'} + + 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-refresh@0.5.2: + resolution: {integrity: sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==} + peerDependencies: + eslint: ^9 || ^10 + + eslint-scope@8.4.0: + resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@4.2.1: + resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} + + eslint@9.39.4: + resolution: {integrity: sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true + + espree@10.4.0: + resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fflate@0.8.2: + resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==} + + file-entry-cache@8.0.0: + resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} + engines: {node: '>=16.0.0'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@4.0.1: + resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} + engines: {node: '>=16'} + + flatted@3.4.2: + resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-east-asian-width@1.6.0: + resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==} + engines: {node: '>=18'} + + get-tsconfig@4.14.0: + resolution: {integrity: sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + globals@14.0.0: + resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} + engines: {node: '>=18'} + + globals@17.6.0: + resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} + engines: {node: '>=18'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + hermes-estree@0.25.1: + resolution: {integrity: sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==} + + hermes-parser@0.25.1: + resolution: {integrity: sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==} + + html-encoding-sniffer@6.0.0: + resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + husky@9.1.7: + resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==} + engines: {node: '>=18'} + hasBin: true + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + ignore@7.0.5: + resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + engines: {node: '>= 4'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@5.1.0: + resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} + engines: {node: '>=18'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsdom@29.1.1: + resolution: {integrity: sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24.0.0} + peerDependencies: + canvas: ^3.0.0 + peerDependenciesMeta: + canvas: + optional: 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==} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lint-staged@16.4.0: + resolution: {integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==} + engines: {node: '>=20.17'} + hasBin: true + + listr2@9.0.5: + resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==} + engines: {node: '>=20.0.0'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + log-update@6.1.0: + resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} + engines: {node: '>=18'} + + lru-cache@11.3.6: + resolution: {integrity: sha512-Gf/KoL3C/MlI7Bt0PGI9I+TeTC/I6r/csU58N4BSNc4lppLBeKsOdFYkK+dX0ABDUMJNfCHTyPpzwwO21Awd3A==} + engines: {node: 20 || >=22} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + mdn-data@2.27.1: + resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + + mimic-function@5.0.1: + resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} + engines: {node: '>=18'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + node-releases@2.0.44: + resolution: {integrity: sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + onetime@7.0.0: + resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==} + engines: {node: '>=18'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse5@8.0.1: + resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + playwright-core@1.60.0: + resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.60.0: + resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} + engines: {node: '>=18'} + hasBin: true + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.14: + resolution: {integrity: sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} + engines: {node: '>=14'} + hasBin: true + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + react-dom@19.2.6: + resolution: {integrity: sha512-0prMI+hvBbPjsWnxDLxlCGyM8PN6UuWjEUCYmZhO67xIV9Xasa/r/vDnq+Xyq4Lo27g8QSbO5YzARu0D1Sps3g==} + peerDependencies: + react: ^19.2.6 + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-router-dom@7.15.0: + resolution: {integrity: sha512-VcrVg64Fo8nwBvDscajG8gRTLIuTC6N50nb22l2HOOV4PTOHgoGp8mUjy9wLiHYoYTSYI36tUnXZgasSRFZorQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + + react-router@7.15.0: + resolution: {integrity: sha512-HW9vYwuM8f4yx66Izy8xfrzCM+SBJluoZcCbww9A1TySax11S5Vgw6fi3ZjMONw9J4gQwngL7PzkyIpJJpJ7RQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + react: '>=18' + react-dom: '>=18' + peerDependenciesMeta: + react-dom: + optional: true + + react@19.2.6: + resolution: {integrity: sha512-sfWGGfavi0xr8Pg0sVsyHMAOziVYKgPLNrS7ig+ivMNb3wbCBw3KxtflsGBAwD3gYQlE/AEZsTLgToRrSCjb0Q==} + engines: {node: '>=0.10.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + restore-cursor@5.1.0: + resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==} + engines: {node: '>=18'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rolldown@1.0.0: + resolution: {integrity: sha512-yD986aXDESFGS95spT1LAv0jssywP4npMEjmMHyN2/5+eE8qQJUype2AaKkRiLgBgyD0LFlubwAht7VmY8rGoA==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.8.0: + resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} + engines: {node: '>=10'} + hasBin: true + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slice-ansi@7.1.2: + resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} + engines: {node: '>=18'} + + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@4.1.0: + resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + string-width@8.2.1: + resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} + engines: {node: '>=20'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tailwindcss@4.3.0: + resolution: {integrity: sha512-y6nxMGB1nMW9R6k96e5gdIFzcfL/gTJRNaqGes1YvkLnPVXzWgbqFF2yLC0T8G774n24cx3Pe8XrKoniCOAH+Q==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.1.2: + resolution: {integrity: sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==} + engines: {node: '>=18'} + + tinyglobby@0.2.16: + resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.1.0: + resolution: {integrity: sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==} + engines: {node: '>=14.0.0'} + + tldts-core@7.0.30: + resolution: {integrity: sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==} + + tldts@7.0.30: + resolution: {integrity: sha512-ELrFxuqsDdHUwoh0XxDbxuLD3Wnz49Z57IFvTtvWy1hJdcMZjXLIuonjilCiWHlT2GbE4Wlv1wKVTzDFnXH1aw==} + hasBin: true + + tough-cookie@6.0.1: + resolution: {integrity: sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==} + engines: {node: '>=16'} + + tr46@6.0.0: + resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} + engines: {node: '>=20'} + + ts-api-utils@2.5.0: + resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==} + engines: {node: '>=18.12'} + peerDependencies: + typescript: '>=4.8.4' + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + tsx@4.21.0: + resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + engines: {node: '>=18.0.0'} + hasBin: true + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + typescript-eslint@8.59.3: + resolution: {integrity: sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + peerDependencies: + eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 + typescript: '>=4.8.4 <6.1.0' + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + undici@7.25.0: + resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} + engines: {node: '>=20.18.1'} + + 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==} + + vite@8.0.12: + resolution: {integrity: sha512-w2dDofOWv2QB09ZITZBsvKTVAlYvPR4IAmrY/v0ir9KvLs0xybR7i48wxhM1/oyBWO34wPns+bPGw5ZrZqDpZg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitest@4.1.6: + resolution: {integrity: sha512-6lvjbS3p9b4CrdCmguzbh2/4uoXhGE2q71R4OX5sqF9R1bo9Xd6fGrMAfvp5wnCzlBnFVdCOp6onuTQVbo8iUQ==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.1.6 + '@vitest/browser-preview': 4.1.6 + '@vitest/browser-webdriverio': 4.1.6 + '@vitest/coverage-istanbul': 4.1.6 + '@vitest/coverage-v8': 4.1.6 + '@vitest/ui': 4.1.6 + happy-dom: '*' + jsdom: '*' + vite: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/coverage-istanbul': + optional: true + '@vitest/coverage-v8': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + w3c-xmlserializer@5.0.0: + resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} + engines: {node: '>=18'} + + webidl-conversions@8.0.1: + resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} + engines: {node: '>=20'} + + whatwg-mimetype@5.0.0: + resolution: {integrity: sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==} + engines: {node: '>=20'} + + whatwg-url@16.0.1: + resolution: {integrity: sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==} + engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + xml-name-validator@5.0.0: + resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==} + engines: {node: '>=18'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + + yocto-queue@0.1.0: + 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.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + + zustand@5.0.13: + resolution: {integrity: sha512-efI2tVaVQPqtOh114loML/Z80Y4NP3yc+Ff0fYiZJPauNeWZeIp/bRFD7I9bfmCOYBh/PHxlglQ9+wvlwnPikQ==} + engines: {node: '>=12.20.0'} + peerDependencies: + '@types/react': '>=18.0.0' + immer: '>=9.0.6' + react: '>=18.0.0' + use-sync-external-store: '>=1.2.0' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + use-sync-external-store: + optional: true + +snapshots: + + '@adobe/css-tools@4.4.4': {} + + '@asamuzakjp/css-color@5.1.11': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-color-parser': 4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@asamuzakjp/dom-selector@7.1.1': + dependencies: + '@asamuzakjp/generational-cache': 1.0.1 + '@asamuzakjp/nwsapi': 2.3.9 + bidi-js: 1.0.3 + css-tree: 3.2.1 + is-potential-custom-element-name: 1.0.1 + + '@asamuzakjp/generational-cache@1.0.1': {} + + '@asamuzakjp/nwsapi@2.3.9': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.3': {} + + '@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.29.2 + '@babel/parser': 7.29.3 + '@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.3 + '@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.3 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.2 + 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.29.2': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.3': + dependencies: + '@babel/types': 7.29.0 + + '@babel/runtime@7.29.2': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.3 + '@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.3 + '@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 + + '@bramus/specificity@2.4.2': + dependencies: + css-tree: 3.2.1 + + '@csstools/color-helpers@6.0.2': {} + + '@csstools/css-calc@3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-color-parser@4.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/color-helpers': 6.0.2 + '@csstools/css-calc': 3.2.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0) + '@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0) + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0)': + dependencies: + '@csstools/css-tokenizer': 4.0.0 + + '@csstools/css-syntax-patches-for-csstree@1.1.4(css-tree@3.2.1)': + optionalDependencies: + css-tree: 3.2.1 + + '@csstools/css-tokenizer@4.0.0': {} + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.27.7': + optional: true + + '@esbuild/android-arm64@0.27.7': + optional: true + + '@esbuild/android-arm@0.27.7': + optional: true + + '@esbuild/android-x64@0.27.7': + optional: true + + '@esbuild/darwin-arm64@0.27.7': + optional: true + + '@esbuild/darwin-x64@0.27.7': + optional: true + + '@esbuild/freebsd-arm64@0.27.7': + optional: true + + '@esbuild/freebsd-x64@0.27.7': + optional: true + + '@esbuild/linux-arm64@0.27.7': + optional: true + + '@esbuild/linux-arm@0.27.7': + optional: true + + '@esbuild/linux-ia32@0.27.7': + optional: true + + '@esbuild/linux-loong64@0.27.7': + optional: true + + '@esbuild/linux-mips64el@0.27.7': + optional: true + + '@esbuild/linux-ppc64@0.27.7': + optional: true + + '@esbuild/linux-riscv64@0.27.7': + optional: true + + '@esbuild/linux-s390x@0.27.7': + optional: true + + '@esbuild/linux-x64@0.27.7': + optional: true + + '@esbuild/netbsd-arm64@0.27.7': + optional: true + + '@esbuild/netbsd-x64@0.27.7': + optional: true + + '@esbuild/openbsd-arm64@0.27.7': + optional: true + + '@esbuild/openbsd-x64@0.27.7': + optional: true + + '@esbuild/openharmony-arm64@0.27.7': + optional: true + + '@esbuild/sunos-x64@0.27.7': + optional: true + + '@esbuild/win32-arm64@0.27.7': + optional: true + + '@esbuild/win32-ia32@0.27.7': + optional: true + + '@esbuild/win32-x64@0.27.7': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@9.39.4)': + dependencies: + eslint: 9.39.4 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/config-array@0.21.2': + dependencies: + '@eslint/object-schema': 2.1.7 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@eslint/config-helpers@0.4.2': + dependencies: + '@eslint/core': 0.17.0 + + '@eslint/core@0.17.0': + dependencies: + '@types/json-schema': 7.0.15 + + '@eslint/eslintrc@3.3.5': + dependencies: + ajv: 6.15.0 + debug: 4.4.3 + espree: 10.4.0 + globals: 14.0.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@9.39.4': {} + + '@eslint/object-schema@2.1.7': {} + + '@eslint/plugin-kit@0.4.1': + dependencies: + '@eslint/core': 0.17.0 + levn: 0.4.1 + + '@exodus/bytes@1.15.0': {} + + '@humanfs/core@0.19.2': + dependencies: + '@humanfs/types': 0.15.0 + + '@humanfs/node@0.16.8': + dependencies: + '@humanfs/core': 0.19.2 + '@humanfs/types': 0.15.0 + '@humanwhocodes/retry': 0.4.3 + + '@humanfs/types@0.15.0': {} + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/retry@0.4.3': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@oxc-project/types@0.129.0': {} + + '@playwright/test@1.60.0': + dependencies: + playwright: 1.60.0 + + '@rolldown/binding-android-arm64@1.0.0': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.0': + optional: true + + '@rolldown/binding-darwin-x64@1.0.0': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.0': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.0': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.0': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.0': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.0': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.0': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.0': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.0': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.0': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.0': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.4(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.0': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.0': + optional: true + + '@rolldown/pluginutils@1.0.0': {} + + '@rolldown/pluginutils@1.0.0-rc.7': {} + + '@standard-schema/spec@1.1.0': {} + + '@testing-library/dom@10.4.1': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.29.2 + '@types/aria-query': 5.0.4 + aria-query: 5.3.0 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + picocolors: 1.1.1 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.4.4 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@babel/runtime': 7.29.2 + '@testing-library/dom': 10.4.1 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + '@types/react-dom': 19.2.3(@types/react@19.2.14) + + '@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)': + dependencies: + '@testing-library/dom': 10.4.1 + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/aria-query@5.0.4': {} + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/deep-eql@4.0.2': {} + + '@types/estree@1.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/node@24.12.4': + dependencies: + undici-types: 7.16.0 + + '@types/react-dom@19.2.3(@types/react@19.2.14)': + dependencies: + '@types/react': 19.2.14 + + '@types/react@19.2.14': + dependencies: + csstype: 3.2.3 + + '@typescript-eslint/eslint-plugin@8.59.3(@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@6.0.3))(eslint@9.39.4)(typescript@6.0.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 8.59.3(eslint@9.39.4)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.59.3 + '@typescript-eslint/type-utils': 8.59.3(eslint@9.39.4)(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.3(eslint@9.39.4)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.3 + eslint: 9.39.4 + ignore: 7.0.5 + natural-compare: 1.4.0 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/scope-manager': 8.59.3 + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.59.3 + debug: 4.4.3 + eslint: 9.39.4 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/project-service@8.59.3(typescript@6.0.3)': + dependencies: + '@typescript-eslint/tsconfig-utils': 8.59.3(typescript@6.0.3) + '@typescript-eslint/types': 8.59.3 + debug: 4.4.3 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@8.59.3': + dependencies: + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/visitor-keys': 8.59.3 + + '@typescript-eslint/tsconfig-utils@8.59.3(typescript@6.0.3)': + dependencies: + typescript: 6.0.3 + + '@typescript-eslint/type-utils@8.59.3(eslint@9.39.4)(typescript@6.0.3)': + dependencies: + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.3(eslint@9.39.4)(typescript@6.0.3) + debug: 4.4.3 + eslint: 9.39.4 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@8.59.3': {} + + '@typescript-eslint/typescript-estree@8.59.3(typescript@6.0.3)': + dependencies: + '@typescript-eslint/project-service': 8.59.3(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.59.3(typescript@6.0.3) + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/visitor-keys': 8.59.3 + debug: 4.4.3 + minimatch: 10.2.5 + semver: 7.8.0 + tinyglobby: 0.2.16 + ts-api-utils: 2.5.0(typescript@6.0.3) + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@8.59.3(eslint@9.39.4)(typescript@6.0.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@typescript-eslint/scope-manager': 8.59.3 + '@typescript-eslint/types': 8.59.3 + '@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3) + eslint: 9.39.4 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/visitor-keys@8.59.3': + dependencies: + '@typescript-eslint/types': 8.59.3 + eslint-visitor-keys: 5.0.1 + + '@vitejs/plugin-react@6.0.1(vite@8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.9.0))': + dependencies: + '@rolldown/pluginutils': 1.0.0-rc.7 + vite: 8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.9.0) + + '@vitest/expect@4.1.6': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.1.6 + '@vitest/utils': 4.1.6 + chai: 6.2.2 + tinyrainbow: 3.1.0 + + '@vitest/mocker@4.1.6(vite@8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.9.0))': + dependencies: + '@vitest/spy': 4.1.6 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.9.0) + + '@vitest/pretty-format@4.1.6': + dependencies: + tinyrainbow: 3.1.0 + + '@vitest/runner@4.1.6': + dependencies: + '@vitest/utils': 4.1.6 + pathe: 2.0.3 + + '@vitest/snapshot@4.1.6': + dependencies: + '@vitest/pretty-format': 4.1.6 + '@vitest/utils': 4.1.6 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.1.6': {} + + '@vitest/utils@4.1.6': + dependencies: + '@vitest/pretty-format': 4.1.6 + convert-source-map: 2.0.0 + tinyrainbow: 3.1.0 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + ajv@6.15.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-escapes@7.3.0: + dependencies: + environment: 1.1.0 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + ansi-styles@6.2.3: {} + + argparse@2.0.1: {} + + aria-query@5.3.0: + dependencies: + dequal: 2.0.3 + + aria-query@5.3.2: {} + + assertion-error@2.0.1: {} + + autoprefixer@10.5.0(postcss@8.5.14): + dependencies: + browserslist: 4.28.2 + caniuse-lite: 1.0.30001792 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.14 + postcss-value-parser: 4.2.0 + + balanced-match@1.0.2: {} + + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.29: {} + + bidi-js@1.0.3: + dependencies: + require-from-string: 2.0.2 + + brace-expansion@1.1.14: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + + browserslist@4.28.2: + dependencies: + baseline-browser-mapping: 2.10.29 + caniuse-lite: 1.0.30001792 + electron-to-chromium: 1.5.354 + node-releases: 2.0.44 + update-browserslist-db: 1.2.3(browserslist@4.28.2) + + callsites@3.1.0: {} + + caniuse-lite@1.0.30001792: {} + + chai@6.2.2: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + cli-cursor@5.0.0: + dependencies: + restore-cursor: 5.1.0 + + cli-truncate@5.2.0: + dependencies: + slice-ansi: 8.0.0 + string-width: 8.2.1 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + commander@14.0.3: {} + + concat-map@0.0.1: {} + + convert-source-map@2.0.0: {} + + cookie@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-tree@3.2.1: + dependencies: + mdn-data: 2.27.1 + source-map-js: 1.2.1 + + css.escape@1.5.1: {} + + csstype@3.2.3: {} + + data-urls@7.0.0: + dependencies: + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js@10.6.0: {} + + deep-is@0.1.4: {} + + dequal@2.0.3: {} + + detect-libc@2.1.2: {} + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + electron-to-chromium@1.5.354: {} + + emoji-regex@10.6.0: {} + + entities@8.0.0: {} + + environment@1.1.0: {} + + es-module-lexer@2.1.0: {} + + esbuild@0.27.7: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.7 + '@esbuild/android-arm': 0.27.7 + '@esbuild/android-arm64': 0.27.7 + '@esbuild/android-x64': 0.27.7 + '@esbuild/darwin-arm64': 0.27.7 + '@esbuild/darwin-x64': 0.27.7 + '@esbuild/freebsd-arm64': 0.27.7 + '@esbuild/freebsd-x64': 0.27.7 + '@esbuild/linux-arm': 0.27.7 + '@esbuild/linux-arm64': 0.27.7 + '@esbuild/linux-ia32': 0.27.7 + '@esbuild/linux-loong64': 0.27.7 + '@esbuild/linux-mips64el': 0.27.7 + '@esbuild/linux-ppc64': 0.27.7 + '@esbuild/linux-riscv64': 0.27.7 + '@esbuild/linux-s390x': 0.27.7 + '@esbuild/linux-x64': 0.27.7 + '@esbuild/netbsd-arm64': 0.27.7 + '@esbuild/netbsd-x64': 0.27.7 + '@esbuild/openbsd-arm64': 0.27.7 + '@esbuild/openbsd-x64': 0.27.7 + '@esbuild/openharmony-arm64': 0.27.7 + '@esbuild/sunos-x64': 0.27.7 + '@esbuild/win32-arm64': 0.27.7 + '@esbuild/win32-ia32': 0.27.7 + '@esbuild/win32-x64': 0.27.7 + + escalade@3.2.0: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@7.0.1(eslint@9.39.4): + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.3 + eslint: 9.39.4 + hermes-parser: 0.25.1 + zod: 4.4.3 + zod-validation-error: 4.0.2(zod@4.4.3) + transitivePeerDependencies: + - supports-color + + eslint-plugin-react-refresh@0.5.2(eslint@9.39.4): + dependencies: + eslint: 9.39.4 + + eslint-scope@8.4.0: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint-visitor-keys@4.2.1: {} + + eslint-visitor-keys@5.0.1: {} + + eslint@9.39.4: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@9.39.4) + '@eslint-community/regexpp': 4.12.2 + '@eslint/config-array': 0.21.2 + '@eslint/config-helpers': 0.4.2 + '@eslint/core': 0.17.0 + '@eslint/eslintrc': 3.3.5 + '@eslint/js': 9.39.4 + '@eslint/plugin-kit': 0.4.1 + '@humanfs/node': 0.16.8 + '@humanwhocodes/module-importer': 1.0.1 + '@humanwhocodes/retry': 0.4.3 + '@types/estree': 1.0.9 + ajv: 6.15.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + escape-string-regexp: 4.0.0 + eslint-scope: 8.4.0 + eslint-visitor-keys: 4.2.1 + espree: 10.4.0 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 8.0.0 + find-up: 5.0.0 + glob-parent: 6.0.2 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + json-stable-stringify-without-jsonify: 1.0.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + transitivePeerDependencies: + - supports-color + + espree@10.4.0: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 4.2.1 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.9 + + esutils@2.0.3: {} + + eventemitter3@5.0.4: {} + + expect-type@1.3.0: {} + + fast-deep-equal@3.1.3: {} + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + fflate@0.8.2: {} + + file-entry-cache@8.0.0: + dependencies: + flat-cache: 4.0.1 + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@4.0.1: + dependencies: + flatted: 3.4.2 + keyv: 4.5.4 + + flatted@3.4.2: {} + + fraction.js@5.3.4: {} + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-east-asian-width@1.6.0: {} + + get-tsconfig@4.14.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + globals@14.0.0: {} + + globals@17.6.0: {} + + has-flag@4.0.0: {} + + hermes-estree@0.25.1: {} + + hermes-parser@0.25.1: + dependencies: + hermes-estree: 0.25.1 + + html-encoding-sniffer@6.0.0: + dependencies: + '@exodus/bytes': 1.15.0 + transitivePeerDependencies: + - '@noble/hashes' + + husky@9.1.7: {} + + ignore@5.3.2: {} + + ignore@7.0.5: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@5.1.0: + dependencies: + get-east-asian-width: 1.6.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-potential-custom-element-name@1.0.1: {} + + isexe@2.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsdom@29.1.1: + dependencies: + '@asamuzakjp/css-color': 5.1.11 + '@asamuzakjp/dom-selector': 7.1.1 + '@bramus/specificity': 2.4.2 + '@csstools/css-syntax-patches-for-csstree': 1.1.4(css-tree@3.2.1) + '@exodus/bytes': 1.15.0 + css-tree: 3.2.1 + data-urls: 7.0.0 + decimal.js: 10.6.0 + html-encoding-sniffer: 6.0.0 + is-potential-custom-element-name: 1.0.1 + lru-cache: 11.3.6 + parse5: 8.0.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 6.0.1 + undici: 7.25.0 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 8.0.1 + whatwg-mimetype: 5.0.0 + whatwg-url: 16.0.1 + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - '@noble/hashes' + + jsesc@3.1.0: {} + + json-buffer@3.0.1: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lint-staged@16.4.0: + dependencies: + commander: 14.0.3 + listr2: 9.0.5 + picomatch: 4.0.4 + string-argv: 0.3.2 + tinyexec: 1.1.2 + yaml: 2.9.0 + + listr2@9.0.5: + dependencies: + cli-truncate: 5.2.0 + colorette: 2.0.20 + eventemitter3: 5.0.4 + log-update: 6.1.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.2 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.merge@4.6.2: {} + + log-update@6.1.0: + dependencies: + ansi-escapes: 7.3.0 + cli-cursor: 5.0.0 + slice-ansi: 7.1.2 + strip-ansi: 7.2.0 + wrap-ansi: 9.0.2 + + lru-cache@11.3.6: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lz-string@1.5.0: {} + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + mdn-data@2.27.1: {} + + mimic-function@5.0.1: {} + + min-indent@1.0.1: {} + + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.14 + + ms@2.1.3: {} + + nanoid@3.3.12: {} + + natural-compare@1.4.0: {} + + node-releases@2.0.44: {} + + obug@2.1.1: {} + + onetime@7.0.0: + dependencies: + mimic-function: 5.0.1 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse5@8.0.1: + dependencies: + entities: 8.0.0 + + path-exists@4.0.0: {} + + path-key@3.1.1: {} + + pathe@2.0.3: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + playwright-core@1.60.0: {} + + playwright@1.60.0: + dependencies: + playwright-core: 1.60.0 + optionalDependencies: + fsevents: 2.3.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.14: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@3.8.3: {} + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + punycode@2.3.1: {} + + react-dom@19.2.6(react@19.2.6): + dependencies: + react: 19.2.6 + scheduler: 0.27.0 + + react-is@17.0.2: {} + + react-router-dom@7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + react-router: 7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6) + + react-router@7.15.0(react-dom@19.2.6(react@19.2.6))(react@19.2.6): + dependencies: + cookie: 1.1.1 + react: 19.2.6 + set-cookie-parser: 2.7.2 + optionalDependencies: + react-dom: 19.2.6(react@19.2.6) + + react@19.2.6: {} + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + require-from-string@2.0.2: {} + + resolve-from@4.0.0: {} + + resolve-pkg-maps@1.0.0: {} + + restore-cursor@5.1.0: + dependencies: + onetime: 7.0.0 + signal-exit: 4.1.0 + + rfdc@1.4.1: {} + + rolldown@1.0.0: + dependencies: + '@oxc-project/types': 0.129.0 + '@rolldown/pluginutils': 1.0.0 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.0 + '@rolldown/binding-darwin-arm64': 1.0.0 + '@rolldown/binding-darwin-x64': 1.0.0 + '@rolldown/binding-freebsd-x64': 1.0.0 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.0 + '@rolldown/binding-linux-arm64-gnu': 1.0.0 + '@rolldown/binding-linux-arm64-musl': 1.0.0 + '@rolldown/binding-linux-ppc64-gnu': 1.0.0 + '@rolldown/binding-linux-s390x-gnu': 1.0.0 + '@rolldown/binding-linux-x64-gnu': 1.0.0 + '@rolldown/binding-linux-x64-musl': 1.0.0 + '@rolldown/binding-openharmony-arm64': 1.0.0 + '@rolldown/binding-wasm32-wasi': 1.0.0 + '@rolldown/binding-win32-arm64-msvc': 1.0.0 + '@rolldown/binding-win32-x64-msvc': 1.0.0 + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.8.0: {} + + set-cookie-parser@2.7.2: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + slice-ansi@7.1.2: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + slice-ansi@8.0.0: + dependencies: + ansi-styles: 6.2.3 + is-fullwidth-code-point: 5.1.0 + + source-map-js@1.2.1: {} + + stackback@0.0.2: {} + + std-env@4.1.0: {} + + string-argv@0.3.2: {} + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + string-width@8.2.1: + dependencies: + get-east-asian-width: 1.6.0 + strip-ansi: 7.2.0 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + symbol-tree@3.2.4: {} + + tailwindcss@4.3.0: {} + + tinybench@2.9.0: {} + + tinyexec@1.1.2: {} + + tinyglobby@0.2.16: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tinyrainbow@3.1.0: {} + + tldts-core@7.0.30: {} + + tldts@7.0.30: + dependencies: + tldts-core: 7.0.30 + + tough-cookie@6.0.1: + dependencies: + tldts: 7.0.30 + + tr46@6.0.0: + dependencies: + punycode: 2.3.1 + + ts-api-utils@2.5.0(typescript@6.0.3): + dependencies: + typescript: 6.0.3 + + tslib@2.8.1: + optional: true + + tsx@4.21.0: + dependencies: + esbuild: 0.27.7 + get-tsconfig: 4.14.0 + optionalDependencies: + fsevents: 2.3.3 + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + typescript-eslint@8.59.3(eslint@9.39.4)(typescript@6.0.3): + dependencies: + '@typescript-eslint/eslint-plugin': 8.59.3(@typescript-eslint/parser@8.59.3(eslint@9.39.4)(typescript@6.0.3))(eslint@9.39.4)(typescript@6.0.3) + '@typescript-eslint/parser': 8.59.3(eslint@9.39.4)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.59.3(typescript@6.0.3) + '@typescript-eslint/utils': 8.59.3(eslint@9.39.4)(typescript@6.0.3) + eslint: 9.39.4 + typescript: 6.0.3 + transitivePeerDependencies: + - supports-color + + typescript@6.0.3: {} + + undici-types@7.16.0: {} + + undici@7.25.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.2): + dependencies: + browserslist: 4.28.2 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + vite@8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.9.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.14 + rolldown: 1.0.0 + tinyglobby: 0.2.16 + optionalDependencies: + '@types/node': 24.12.4 + esbuild: 0.27.7 + fsevents: 2.3.3 + tsx: 4.21.0 + yaml: 2.9.0 + + vitest@4.1.6(@types/node@24.12.4)(jsdom@29.1.1)(vite@8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.9.0)): + dependencies: + '@vitest/expect': 4.1.6 + '@vitest/mocker': 4.1.6(vite@8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.9.0)) + '@vitest/pretty-format': 4.1.6 + '@vitest/runner': 4.1.6 + '@vitest/snapshot': 4.1.6 + '@vitest/spy': 4.1.6 + '@vitest/utils': 4.1.6 + es-module-lexer: 2.1.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.4 + std-env: 4.1.0 + tinybench: 2.9.0 + tinyexec: 1.1.2 + tinyglobby: 0.2.16 + tinyrainbow: 3.1.0 + vite: 8.0.12(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)(yaml@2.9.0) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 24.12.4 + jsdom: 29.1.1 + transitivePeerDependencies: + - msw + + w3c-xmlserializer@5.0.0: + dependencies: + xml-name-validator: 5.0.0 + + webidl-conversions@8.0.1: {} + + whatwg-mimetype@5.0.0: {} + + whatwg-url@16.0.1: + dependencies: + '@exodus/bytes': 1.15.0 + tr46: 6.0.0 + webidl-conversions: 8.0.1 + transitivePeerDependencies: + - '@noble/hashes' + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + word-wrap@1.2.5: {} + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.2.0 + + xml-name-validator@5.0.0: {} + + xmlchars@2.2.0: {} + + yallist@3.1.1: {} + + yaml@2.9.0: {} + + yocto-queue@0.1.0: {} + + zod-validation-error@4.0.2(zod@4.4.3): + dependencies: + zod: 4.4.3 + + zod@4.4.3: {} + + zustand@5.0.13(@types/react@19.2.14)(react@19.2.6): + optionalDependencies: + '@types/react': 19.2.14 + react: 19.2.6 diff --git a/scripts/benchmark-solve.ts b/scripts/benchmark-solve.ts new file mode 100644 index 0000000..13a8a18 --- /dev/null +++ b/scripts/benchmark-solve.ts @@ -0,0 +1,82 @@ +import type { Dirent } from 'node:fs' +import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises' +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { + runBenchmarkManifest, + validateBenchmarkManifest, +} from '../src/domain/benchmark' + +const DATASET_DIRS = ['dataset/public', 'dataset/private'] +const OUTPUT_DIR = 'benchmark-results' + +const projectRoot = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '..', +) + +const findJsonFiles = async (dir: string): Promise => { + const absoluteDir = path.join(projectRoot, dir) + let entries: Dirent[] + try { + entries = await readdir(absoluteDir, { withFileTypes: true }) + } catch (error) { + if (error instanceof Error && 'code' in error && error.code === 'ENOENT') { + return [] + } + throw error + } + + const files: string[] = [] + for (const entry of entries) { + const relativePath = path.join(dir, entry.name) + if (entry.isDirectory()) { + files.push(...(await findJsonFiles(relativePath))) + } else if (entry.isFile() && entry.name.endsWith('.json')) { + files.push(relativePath) + } + } + return files.sort() +} + +const readManifest = async (manifestPath: string) => { + const raw = await readFile(path.join(projectRoot, manifestPath), 'utf8') + return validateBenchmarkManifest(JSON.parse(raw)) +} + +const writeReport = async ( + datasetId: string, + report: unknown, +): Promise => { + const outPath = path.join(OUTPUT_DIR, `${datasetId}.report.json`) + const absoluteOutPath = path.join(projectRoot, outPath) + await mkdir(path.dirname(absoluteOutPath), { recursive: true }) + await writeFile(absoluteOutPath, `${JSON.stringify(report, null, 2)}\n`) + return outPath +} + +const main = async (): Promise => { + const manifestPaths = ( + await Promise.all(DATASET_DIRS.map((dir) => findJsonFiles(dir))) + ).flat() + + if (manifestPaths.length === 0) { + throw new Error( + `No benchmark manifests found under ${DATASET_DIRS.join(' or ')}.`, + ) + } + + for (const manifestPath of manifestPaths) { + const manifest = await readManifest(manifestPath) + const report = runBenchmarkManifest(manifest) + const outPath = await writeReport(manifest.id, report) + console.log( + `Wrote ${manifest.id}: ${report.summary.total} puzzle(s), ${outPath}`, + ) + } +} + +main().catch((error: unknown) => { + console.error(error instanceof Error ? error.message : String(error)) + process.exitCode = 1 +}) diff --git a/src/app/EditorPage.test.tsx b/src/app/EditorPage.test.tsx index 6771072..66e4a96 100644 --- a/src/app/EditorPage.test.tsx +++ b/src/app/EditorPage.test.tsx @@ -285,6 +285,28 @@ describe('EditorPage', () => { expect(within(dialog).queryByText(/default slitherlink 1/i)).not.toBeInTheDocument() }) + it('opens slitherlink rules from the editor puzzle type row', () => { + render( + + + , + ) + + fireEvent.click(screen.getByRole('button', { name: /show slitherlink rules/i })) + + const dialog = screen.getByRole('dialog', { name: /slitherlink rules/i }) + expect(dialog).toHaveAttribute('aria-modal', 'false') + expect(within(dialog).getByText(/draw lines along the edges/i)).toBeInTheDocument() + expect(within(dialog).getByText(/the loop cannot branch off or cross itself/i)).toBeInTheDocument() + expect(within(dialog).getByText(/a number indicates the amount of edges/i)).toBeInTheDocument() + expect(within(dialog).queryByText(/in puzzlekit/i)).not.toBeInTheDocument() + expect(within(dialog).getByLabelText(/before example canvas/i)).toBeInTheDocument() + expect(within(dialog).getByLabelText(/after example canvas/i)).toBeInTheDocument() + + fireEvent.click(within(dialog).getByRole('button', { name: /close slitherlink rules/i })) + expect(screen.queryByRole('dialog', { name: /slitherlink rules/i })).not.toBeInTheDocument() + }) + it('uses the shared workspace grid columns on the editor page', () => { render( diff --git a/src/app/EditorPage.tsx b/src/app/EditorPage.tsx index e22f7ac..da5a3fd 100644 --- a/src/app/EditorPage.tsx +++ b/src/app/EditorPage.tsx @@ -7,9 +7,11 @@ import { } from '../domain/ir/slither' import type { PuzzleIR } from '../domain/ir/types' import { puzzleRegistry } from '../domain/plugins/registry' +import { BoardLegendButton } from '../features/board/BoardLegendButton' import { SlitherlinkEditorBoard } from '../features/editor/SlitherlinkEditorBoard' import { useEditorStore } from '../features/editor/editorStore' import { puzzlePresets, type PuzzlePreset } from '../features/editor/presets' +import { PuzzleInfoButton } from '../features/puzzleInfo/PuzzleInfoButton' import { useSolverStore } from '../features/solver/solverStore' import './workspace.css' @@ -416,20 +418,24 @@ export const EditorPage = () => {

Puzzle Builder

- +
+ Puzzle Type +
+ + + +
+
Grid
diff --git a/src/app/WorkspacePage.test.tsx b/src/app/WorkspacePage.test.tsx index b4de8f7..31a59f8 100644 --- a/src/app/WorkspacePage.test.tsx +++ b/src/app/WorkspacePage.test.tsx @@ -55,6 +55,43 @@ describe('WorkspacePage', () => { 'aria-pressed', 'false', ) + const boardTools = document.querySelector('.board-header-tools') + expect(boardTools?.children[0]?.tagName).toBe('SMALL') + expect(boardTools?.children[1]).toHaveClass('board-zoom-control') + const typeControls = document.querySelector('.type-row-controls') + expect(typeControls?.children[1]?.querySelector('[aria-label="Show Slitherlink rules"]')).not.toBeNull() + expect(typeControls?.children[2]?.querySelector('[aria-label="Show Slitherlink legend"]')).not.toBeNull() + }) + + it('opens slitherlink board legend from the puzzle type row', () => { + renderWorkspace() + + fireEvent.click(screen.getByRole('button', { name: /show slitherlink legend/i })) + + const legendDialog = screen.getByRole('dialog', { name: /slitherlink legend/i }) + expect(legendDialog).toBeInTheDocument() + expect(legendDialog).toHaveClass('board-legend-panel') + expect(legendDialog).toHaveAttribute('aria-modal', 'false') + expect(screen.getByText('Only One')).toBeInTheDocument() + expect(screen.getByText('NOT ONE')).toBeInTheDocument() + expect(screen.getByText('NOT ZERO')).toBeInTheDocument() + expect(screen.getByText('NOT TWO')).toBeInTheDocument() + expect(screen.getByText('YELLOW')).toBeInTheDocument() + expect(screen.getByText('GREEN')).toBeInTheDocument() + expect(screen.getByText(/ONLY have ONE connected/i)).toBeInTheDocument() + expect(screen.getByText(/cannot have exactly one connected/i)).toBeInTheDocument() + expect(screen.getByText(/cannot have ZERO connected/i)).toBeInTheDocument() + expect(screen.getByText(/cannot both be connected/i)).toBeInTheDocument() + expect(screen.getByText(/outside the final loop/i)).toBeInTheDocument() + expect(screen.getByText(/inside the final loop/i)).toBeInTheDocument() + expect(screen.getAllByLabelText(/legend canvas/i)).toHaveLength(6) + + fireEvent.keyDown(document, { key: 'Escape' }) + expect(screen.queryByRole('dialog', { name: /slitherlink legend/i })).not.toBeInTheDocument() + + fireEvent.click(screen.getByRole('button', { name: /show slitherlink legend/i })) + fireEvent.click(screen.getByRole('button', { name: /close slitherlink legend/i })) + expect(screen.queryByRole('dialog', { name: /slitherlink legend/i })).not.toBeInTheDocument() }) it('shows import errors in a closeable dialog with expandable details', () => { @@ -90,19 +127,43 @@ describe('WorkspacePage', () => { expect(exportDialog).toBeInTheDocument() expect(exportDialog).toHaveClass('export-panel') expect(exportDialog).toHaveAttribute('aria-modal', 'false') - expect(screen.getByRole('button', { name: /close export/i })).toHaveAttribute( + expect(screen.getByRole('button', { name: /^close export$/i })).toHaveAttribute( 'data-active', 'true', ) - fireEvent.click(screen.getByRole('button', { name: /close export/i })) + fireEvent.click(screen.getByRole('button', { name: /^close export$/i })) expect(screen.queryByRole('dialog', { name: /export puzzle/i })).not.toBeInTheDocument() fireEvent.click(screen.getByRole('button', { name: /export/i })) - fireEvent.click(screen.getByRole('button', { name: /cancel/i })) + fireEvent.click(screen.getByRole('button', { name: /close export panel/i })) expect(screen.queryByRole('dialog', { name: /export puzzle/i })).not.toBeInTheDocument() }) + it('opens slitherlink rules as a closeable puzzle info popout', () => { + renderWorkspace() + + fireEvent.click(screen.getByRole('button', { name: /show slitherlink rules/i })) + + const infoDialog = screen.getByRole('dialog', { name: /slitherlink rules/i }) + expect(infoDialog).toBeInTheDocument() + expect(infoDialog).toHaveClass('puzzle-info-panel') + expect(infoDialog).toHaveAttribute('aria-modal', 'false') + expect(screen.getByText(/draw lines along the edges/i)).toBeInTheDocument() + expect(screen.getByText(/the loop cannot branch off or cross itself/i)).toBeInTheDocument() + expect(screen.getByText(/a number indicates the amount of edges/i)).toBeInTheDocument() + expect(screen.queryByText(/in puzzlekit/i)).not.toBeInTheDocument() + expect(screen.getByLabelText(/before example canvas/i)).toBeInTheDocument() + expect(screen.getByLabelText(/after example canvas/i)).toBeInTheDocument() + + fireEvent.keyDown(document, { key: 'Escape' }) + expect(screen.queryByRole('dialog', { name: /slitherlink rules/i })).not.toBeInTheDocument() + + fireEvent.click(screen.getByRole('button', { name: /show slitherlink rules/i })) + fireEvent.click(screen.getByRole('button', { name: /close slitherlink rules/i })) + expect(screen.queryByRole('dialog', { name: /slitherlink rules/i })).not.toBeInTheDocument() + }) + it('shows solve progress, then terminal report, and keeps solve buttons disabled after close', async () => { const puzzle = createSolvedLoopPuzzle() useSolverStore.setState((state) => ({ diff --git a/src/app/workspace.css b/src/app/workspace.css index 43868b3..d41450f 100644 --- a/src/app/workspace.css +++ b/src/app/workspace.css @@ -268,6 +268,189 @@ button[data-active='true'] { min-width: 140px; } +.puzzle-info-anchor { + position: relative; + flex: 0 0 auto; +} + +.puzzle-info-button { + display: inline-grid; + width: 34px; + height: 34px; + place-items: center; + border-radius: 999px; + padding: 0; + font-weight: 800; + line-height: 1; +} + +.puzzle-info-panel { + position: absolute; + top: calc(100% + 8px); + right: 0; + z-index: 35; + width: min(432px, calc(100vw - 48px)); + max-height: min(620px, calc(100vh - 120px)); + overflow: auto; + border: 1px solid #cbd5e1; + border-radius: 8px; + background: #f9fafb; + box-shadow: 0 18px 48px rgb(15 23 42 / 0.16); + padding: 12px; + color: #4b5563; +} + +.puzzle-info-panel-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + margin-bottom: 8px; +} + +.puzzle-info-panel-header h2 { + margin: 0; + color: #0f172a; + font-size: 1rem; +} + +.puzzle-info-summary { + margin: 0 0 10px; + color: #1f2937; + line-height: 1.4; +} + +.puzzle-info-section { + margin-top: 10px; +} + +.puzzle-info-section h3 { + margin: 0 0 6px; + color: #0f172a; + font-size: 0.9rem; +} + +.puzzle-info-section ul { + margin: 0; + padding-left: 18px; + line-height: 1.45; +} + +.puzzle-info-section li + li { + margin-top: 4px; +} + +.puzzle-info-example-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.puzzle-info-example-card { + min-width: 0; + margin: 0; + overflow: hidden; + border: 1px solid #e5e7eb; + border-radius: 8px; + background: #ffffff; +} + +.puzzle-info-example-card canvas { + display: block; + width: 100%; + height: auto; + border-bottom: 1px solid #e5e7eb; +} + +.puzzle-info-example-card figcaption { + display: flex; + flex-direction: column; + gap: 2px; + padding: 7px; + color: #6b7280; + font-size: 0.76rem; + line-height: 1.3; +} + +.puzzle-info-example-card strong { + color: #0f172a; + font-size: 0.82rem; +} + +.board-legend-anchor { + position: relative; + flex: 0 0 auto; +} + +.board-legend-panel { + position: absolute; + top: calc(100% + 8px); + right: 0; + z-index: 35; + width: min(560px, calc(100vw - 48px)); + max-height: min(680px, calc(100vh - 120px)); + overflow: auto; + border: 1px solid #cbd5e1; + border-radius: 8px; + background: #f9fafb; + box-shadow: 0 18px 48px rgb(15 23 42 / 0.16); + padding: 12px; + color: #4b5563; +} + +.board-legend-list { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 10px; +} + +.board-legend-item { + display: grid; + grid-template-columns: 116px minmax(0, 1fr); + gap: 8px; + align-items: center; + min-width: 0; + border: 1px solid #e5e7eb; + border-radius: 8px; + background: #ffffff; + padding: 8px; +} + +.board-legend-item canvas { + display: block; + width: 116px; + height: auto; + border: 1px solid #e5e7eb; + border-radius: 6px; +} + +.board-legend-item h3 { + margin: 0 0 4px; + color: #0f172a; + font-size: 0.86rem; +} + +.board-legend-item p { + margin: 0; + color: #4b5563; + font-size: 0.78rem; + line-height: 1.35; +} + +.panel-icon-close { + display: inline-grid; + flex: 0 0 auto; + width: 30px; + height: 30px; + place-items: center; + border-radius: 999px; + padding: 0; + color: #475569; + font-size: 1.15rem; + font-weight: 700; + line-height: 1; +} + .custom-grid-anchor { position: relative; } @@ -1035,6 +1218,34 @@ details pre { flex-direction: column; } + .puzzle-info-panel { + right: auto; + left: 50%; + transform: translateX(-50%); + } + + .puzzle-info-example-grid { + grid-template-columns: 1fr; + } + + .board-legend-panel { + right: auto; + left: 50%; + transform: translateX(-50%); + } + + .board-legend-list { + grid-template-columns: 1fr; + } + + .board-legend-item { + grid-template-columns: 104px minmax(0, 1fr); + } + + .board-legend-item canvas { + width: 104px; + } + .io-action-row { grid-template-columns: 1fr; } diff --git a/src/domain/benchmark/index.ts b/src/domain/benchmark/index.ts new file mode 100644 index 0000000..95db03e --- /dev/null +++ b/src/domain/benchmark/index.ts @@ -0,0 +1,10 @@ +export { validateBenchmarkManifest } from './manifest' +export { runBenchmarkItem, runBenchmarkManifest } from './runner' +export type { + BenchmarkDatasetItem, + BenchmarkDatasetManifest, + BenchmarkPuzzleResult, + BenchmarkPuzzleStatus, + BenchmarkReport, + BenchmarkRunnerOptions, +} from './types' diff --git a/src/domain/benchmark/manifest.test.ts b/src/domain/benchmark/manifest.test.ts new file mode 100644 index 0000000..8bc6f76 --- /dev/null +++ b/src/domain/benchmark/manifest.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from 'vitest' +import { validateBenchmarkManifest } from './manifest' + +describe('benchmark manifest', () => { + it('validates a tiny two-item manifest', () => { + const manifest = validateBenchmarkManifest({ + schemaVersion: 1, + id: 'tiny-slitherlink', + title: 'Tiny Slitherlink', + puzzleType: 'slitherlink', + items: [ + { + id: 'slitherlink-3x3-0001', + puzzleType: 'slitherlink', + sourceUrl: 'https://puzz.link/p?slither/3/3/g0h', + width: 3, + height: 3, + tags: ['auto-imported'], + }, + { + id: 'slitherlink-3x3-0002', + puzzleType: 'slitherlink', + sourceUrl: 'https://puzz.link/p?slither/3/3/i0', + width: 3, + height: 3, + tags: ['auto-imported'], + }, + ], + }) + + expect(manifest.items).toHaveLength(2) + expect(manifest.items[0].puzzleType).toBe('slitherlink') + }) +}) diff --git a/src/domain/benchmark/manifest.ts b/src/domain/benchmark/manifest.ts new file mode 100644 index 0000000..72c0424 --- /dev/null +++ b/src/domain/benchmark/manifest.ts @@ -0,0 +1,24 @@ +import { z } from 'zod' +import type { BenchmarkDatasetManifest } from './types' + +const DatasetItemSchema = z.object({ + id: z.string().min(1), + puzzleType: z.string().min(1), + sourceUrl: z.string().min(1), + width: z.number().int().positive(), + height: z.number().int().positive(), + tags: z.array(z.string()), + source: z.string().optional(), +}) + +const DatasetManifestSchema = z.object({ + schemaVersion: z.literal(1), + id: z.string().min(1), + title: z.string().min(1), + puzzleType: z.string().min(1), + items: z.array(DatasetItemSchema), +}) + +export const validateBenchmarkManifest = ( + input: unknown, +): BenchmarkDatasetManifest => DatasetManifestSchema.parse(input) diff --git a/src/domain/benchmark/runner.test.ts b/src/domain/benchmark/runner.test.ts new file mode 100644 index 0000000..561b0c0 --- /dev/null +++ b/src/domain/benchmark/runner.test.ts @@ -0,0 +1,73 @@ +import { afterEach, describe, expect, it, vi } from 'vitest' +import { runBenchmarkItem, runBenchmarkManifest } from './runner' +import type { BenchmarkDatasetItem, BenchmarkDatasetManifest } from './types' + +const validItem: BenchmarkDatasetItem = { + id: 'slitherlink-3x3-0001', + puzzleType: 'slitherlink', + sourceUrl: 'https://puzz.link/p?slither/3/3/g0h', + width: 3, + height: 3, + tags: ['auto-imported'], +} + +describe('benchmark runner', () => { + afterEach(() => { + vi.restoreAllMocks() + }) + + it('reports step-capped runs when maxSteps is reached before completion', () => { + const result = runBenchmarkItem(validItem, { + maxSteps: 1, + timeoutMs: 60_000, + }) + + expect(result.status).toBe('step-capped') + expect(result.stepCount).toBe(1) + expect(result.steps).toEqual([]) + expect(result.ruleSteps).toEqual({ + [Object.keys(result.ruleUsage)[0]]: [1], + }) + }) + + it('reports time-capped runs without crashing', () => { + vi.spyOn(performance, 'now') + .mockReturnValueOnce(0) + .mockReturnValueOnce(2) + .mockReturnValueOnce(2) + + const result = runBenchmarkItem(validItem, { maxSteps: 2000, timeoutMs: 1 }) + + expect(result.status).toBe('time-capped') + expect(result.stepCount).toBe(0) + }) + + it('keeps running after per-item parse errors', () => { + const manifest: BenchmarkDatasetManifest = { + schemaVersion: 1, + id: 'mixed', + title: 'Mixed', + puzzleType: 'slitherlink', + items: [ + validItem, + { + ...validItem, + id: 'bad-url', + sourceUrl: 'https://example.com/not-a-puzzle', + }, + ], + } + + const report = runBenchmarkManifest(manifest, { + maxSteps: 1, + timeoutMs: 60_000, + }) + + expect(report.summary.total).toBe(2) + expect(report.summary.parseError).toBe(1) + expect(report.items.map((item) => item.id)).toEqual([ + 'slitherlink-3x3-0001', + 'bad-url', + ]) + }) +}) diff --git a/src/domain/benchmark/runner.ts b/src/domain/benchmark/runner.ts new file mode 100644 index 0000000..8364d86 --- /dev/null +++ b/src/domain/benchmark/runner.ts @@ -0,0 +1,175 @@ +import type { PuzzleIR } from '../ir/types' +import { puzzleRegistry } from '../plugins/registry' +import { runNextRule } from '../rules/engine' +import { analyzeSlitherCompletion } from '../rules/slither/completion' +import type { RuleStep } from '../rules/types' +import type { + BenchmarkDatasetItem, + BenchmarkDatasetManifest, + BenchmarkPuzzleResult, + BenchmarkPuzzleStatus, + BenchmarkReport, + BenchmarkRunnerOptions, + BenchmarkSummary, +} from './types' + +const DEFAULT_MAX_STEPS = 2000 +const DEFAULT_TIMEOUT_MS = 60_000 + +const normalizeLimit = ( + value: number | undefined, + fallback: number, +): number => { + if (value === undefined || !Number.isFinite(value)) { + return fallback + } + return Math.max(1, Math.floor(value)) +} + +const addRuleUsage = ( + ruleUsage: Record, + ruleSteps: Record, + step: RuleStep, + stepNumber: number, +): void => { + ruleUsage[step.ruleId] = (ruleUsage[step.ruleId] ?? 0) + 1 + ruleSteps[step.ruleId] = [...(ruleSteps[step.ruleId] ?? []), stepNumber] +} + +const getSlitherTerminal = (puzzleType: string, puzzle: PuzzleIR) => + puzzleType === 'slitherlink' ? analyzeSlitherCompletion(puzzle) : null + +const getStatusCountKey = ( + status: BenchmarkPuzzleStatus, +): keyof Omit => { + if (status === 'parse-error') return 'parseError' + if (status === 'runtime-error') return 'runtimeError' + if (status === 'step-capped') return 'stepCapped' + if (status === 'time-capped') return 'timeCapped' + return status +} + +export const runBenchmarkItem = ( + item: BenchmarkDatasetItem, + options: Required, +): BenchmarkPuzzleResult => { + const startedAt = performance.now() + const ruleUsage: Record = {} + const ruleSteps: Record = {} + let stepCount = 0 + const finish = ( + status: BenchmarkPuzzleStatus, + terminal: BenchmarkPuzzleResult['terminal'], + error?: string, + ): BenchmarkPuzzleResult => ({ + id: item.id, + puzzleType: item.puzzleType, + sourceUrl: item.sourceUrl, + width: item.width, + height: item.height, + status, + stepCount, + durationMs: Math.max(0, performance.now() - startedAt), + ruleUsage, + ruleSteps, + terminal, + steps: [], + ...(error ? { error } : {}), + }) + + const plugin = puzzleRegistry.get(item.puzzleType) + if (!plugin) { + return finish('parse-error', null, `Plugin "${item.puzzleType}" not found.`) + } + + let puzzle: PuzzleIR + try { + puzzle = plugin.parse(item.sourceUrl) + } catch (error) { + return finish( + 'parse-error', + null, + error instanceof Error ? error.message : String(error), + ) + } + + const rules = plugin.getRules() + while (true) { + if (performance.now() - startedAt >= options.timeoutMs) { + return finish('time-capped', getSlitherTerminal(item.puzzleType, puzzle)) + } + + if (stepCount >= options.maxSteps) { + const terminal = getSlitherTerminal(item.puzzleType, puzzle) + return finish( + terminal?.status === 'solved' ? 'solved' : 'step-capped', + terminal, + ) + } + + let result: ReturnType + try { + result = runNextRule(puzzle, rules, stepCount + 1) + } catch (error) { + return finish( + 'runtime-error', + getSlitherTerminal(item.puzzleType, puzzle), + error instanceof Error ? error.message : String(error), + ) + } + + if (!result.step) { + const terminal = getSlitherTerminal(item.puzzleType, puzzle) + return finish(terminal?.status ?? 'stalled', terminal) + } + + puzzle = result.nextPuzzle + stepCount += 1 + addRuleUsage(ruleUsage, ruleSteps, result.step, stepCount) + } +} + +export const runBenchmarkManifest = ( + manifest: BenchmarkDatasetManifest, + options: BenchmarkRunnerOptions = {}, +): BenchmarkReport => { + const maxSteps = normalizeLimit(options.maxSteps, DEFAULT_MAX_STEPS) + const timeoutMs = normalizeLimit(options.timeoutMs, DEFAULT_TIMEOUT_MS) + const startedAt = new Date().toISOString() + const items = manifest.items.map((item) => + runBenchmarkItem(item, { maxSteps, timeoutMs }), + ) + const summary: BenchmarkSummary = { + total: items.length, + solved: 0, + stalled: 0, + parseError: 0, + runtimeError: 0, + stepCapped: 0, + timeCapped: 0, + totalDurationMs: 0, + ruleUsage: {}, + } + + for (const item of items) { + summary[getStatusCountKey(item.status)] += 1 + summary.totalDurationMs += item.durationMs + for (const [ruleId, count] of Object.entries(item.ruleUsage)) { + summary.ruleUsage[ruleId] = (summary.ruleUsage[ruleId] ?? 0) + count + } + } + + return { + schemaVersion: 1, + run: { + datasetId: manifest.id, + startedAt, + completedAt: new Date().toISOString(), + maxSteps, + timeoutMs, + ruleProfile: 'default', + }, + summary, + items, + } +} diff --git a/src/domain/benchmark/types.ts b/src/domain/benchmark/types.ts new file mode 100644 index 0000000..a87ce29 --- /dev/null +++ b/src/domain/benchmark/types.ts @@ -0,0 +1,76 @@ +import type { SlitherCompletionReport } from '../rules/slither/completion' + +export type BenchmarkDatasetItem = { + id: string + puzzleType: string + sourceUrl: string + width: number + height: number + tags: string[] + source?: string +} + +export type BenchmarkDatasetManifest = { + schemaVersion: 1 + id: string + title: string + puzzleType: string + items: BenchmarkDatasetItem[] +} + +export type BenchmarkPuzzleStatus = + | 'solved' + | 'stalled' + | 'parse-error' + | 'runtime-error' + | 'step-capped' + | 'time-capped' + +export type BenchmarkPuzzleResult = { + id: string + puzzleType: string + sourceUrl: string + width: number + height: number + status: BenchmarkPuzzleStatus + stepCount: number + durationMs: number + ruleUsage: Record + ruleSteps: Record + terminal: SlitherCompletionReport | null + steps: [] + error?: string +} + +export type BenchmarkRunConfig = { + datasetId: string + startedAt: string + completedAt: string + maxSteps: number + timeoutMs: number + ruleProfile: 'default' +} + +export type BenchmarkSummary = { + total: number + solved: number + stalled: number + parseError: number + runtimeError: number + stepCapped: number + timeCapped: number + totalDurationMs: number + ruleUsage: Record +} + +export type BenchmarkReport = { + schemaVersion: 1 + run: BenchmarkRunConfig + summary: BenchmarkSummary + items: BenchmarkPuzzleResult[] +} + +export type BenchmarkRunnerOptions = { + maxSteps?: number + timeoutMs?: number +} diff --git a/src/domain/plugins/slitherPlugin.ts b/src/domain/plugins/slitherPlugin.ts index 760e023..cf46267 100644 --- a/src/domain/plugins/slitherPlugin.ts +++ b/src/domain/plugins/slitherPlugin.ts @@ -1,7 +1,7 @@ import { decodeSlitherFromPuzzlink, encodeSlitherToPuzzlink } from '../parsers/puzzlink' import { decodeSlitherFromPenpa } from '../parsers/penpa' import { slitherRules } from '../rules/slither/rules' -import type { PuzzlePlugin } from './types' +import type { PuzzleHelpContent, PuzzleLegendContent, PuzzlePlugin } from './types' const parseSlitherInput = (input: string) => { try { @@ -20,9 +20,186 @@ const parseSlitherInput = (input: string) => { } } +const slitherHelp: PuzzleHelpContent = { + title: 'Slitherlink Rules', + summary: 'Draw lines along the edges of some cells to form a loop.', + rules: [ + 'The loop cannot branch off or cross itself.', + 'A number indicates the amount of edges surrounding the cell that are visited by the loop.', + ], + example: { + title: 'Before and after', + before: { + label: 'Before', + description: 'Only the given clues are known.', + rows: 3, + cols: 3, + clues: [ + { row: 0, col: 1, value: 3 }, + { row: 1, col: 0, value: 3 }, + { row: 1, col: 1, value: 0 }, + { row: 2, col: 1, value: 3 }, + ], + edges: [], + }, + after: { + label: 'After', + description: 'One valid loop satisfies every clue.', + rows: 3, + cols: 3, + clues: [ + { row: 0, col: 1, value: 3 }, + { row: 1, col: 0, value: 3 }, + { row: 1, col: 1, value: 0 }, + { row: 2, col: 1, value: 3 }, + ], + edges: [ + { edge: [[0, 0], [0, 1]], mark: 'blank' }, + { edge: [[0, 1], [0, 2]], mark: 'line' }, + { edge: [[0, 2], [0, 3]], mark: 'blank' }, + { edge: [[1, 0], [1, 1]], mark: 'line' }, + { edge: [[1, 1], [1, 2]], mark: 'blank' }, + { edge: [[1, 2], [1, 3]], mark: 'line' }, + { edge: [[2, 0], [2, 1]], mark: 'line' }, + { edge: [[2, 1], [2, 2]], mark: 'blank' }, + { edge: [[2, 2], [2, 3]], mark: 'line' }, + { edge: [[3, 0], [3, 1]], mark: 'blank' }, + { edge: [[3, 1], [3, 2]], mark: 'line' }, + { edge: [[3, 2], [3, 3]], mark: 'blank' }, + { edge: [[0, 0], [1, 0]], mark: 'blank' }, + { edge: [[0, 1], [1, 1]], mark: 'line' }, + { edge: [[0, 2], [1, 2]], mark: 'line' }, + { edge: [[0, 3], [1, 3]], mark: 'blank' }, + { edge: [[1, 0], [2, 0]], mark: 'line' }, + { edge: [[1, 1], [2, 1]], mark: 'blank' }, + { edge: [[1, 2], [2, 2]], mark: 'blank' }, + { edge: [[1, 3], [2, 3]], mark: 'line' }, + { edge: [[2, 0], [3, 0]], mark: 'blank' }, + { edge: [[2, 1], [3, 1]], mark: 'line' }, + { edge: [[2, 2], [3, 2]], mark: 'line' }, + { edge: [[2, 3], [3, 3]], mark: 'blank' }, + ], + }, + }, +} + +const slitherLegend: PuzzleLegendContent = { + title: 'Slitherlink Legend', + items: [ + { + label: 'Only One', + description: 'Two edges in this sector can and will ONLY have ONE connected.', + example: { + rows: 3, + cols: 3, + clues: [{ row: 1, col: 1, value: 3 }], + edges: [ + { edge: [[2, 1], [2, 2]], mark: 'line' }, + { edge: [[1, 2], [2, 2]], mark: 'line' }, + ], + sectors: [{ row: 1, col: 1, corner: 'nw', kind: 'onlyOne' }], + }, + }, + { + label: 'NOT ONE', + description: + 'Two edges in this sector cannot have exactly one connected; they must have ZERO or TWO connected.', + example: { + rows: 3, + cols: 3, + edges: [ + { edge: [[1, 1], [2, 1]], mark: 'blank' }, + { edge: [[1, 1], [1, 2]], mark: 'blank' }, + ], + sectors: [{ row: 0, col: 0, corner: 'se', kind: 'notOne' }], + }, + }, + { + label: 'NOT ZERO', + description: + 'Two edges in this sector cannot have ZERO connected; they must have ONE or TWO connected.', + example: { + rows: 3, + cols: 3, + clues: [{ row: 1, col: 1, value: 3 }], + sectors: [ + { row: 1, col: 1, corner: 'nw', kind: 'notZero' }, + { row: 1, col: 1, corner: 'ne', kind: 'notZero' }, + { row: 1, col: 1, corner: 'sw', kind: 'notZero' }, + { row: 1, col: 1, corner: 'se', kind: 'notZero' }, + ], + }, + }, + { + label: 'NOT TWO', + description: + 'Two edges in this sector cannot both be connected; they must have ZERO or ONE connected.', + example: { + rows: 3, + cols: 3, + edges: [{ edge: [[1, 2], [2, 2]], mark: 'blank' }], + sectors: [{ row: 1, col: 1, corner: 'se', kind: 'notTwo' }], + }, + }, + { + label: 'YELLOW', + description: 'This YELLOW cell is outside the final loop.', + example: { + rows: 3, + cols: 3, + filledCells: [{ row: 0, col: 1, fill: 'yellow' }], + edges: [ + { edge: [[0, 0], [0, 1]], mark: 'line' }, + { edge: [[0, 1], [1, 1]], mark: 'line' }, + { edge: [[1, 1], [1, 2]], mark: 'line' }, + { edge: [[0, 2], [1, 2]], mark: 'line' }, + { edge: [[0, 2], [0, 3]], mark: 'line' }, + ], + }, + }, + { + label: 'GREEN', + description: 'These GREEN cells are inside the final loop.', + example: { + rows: 3, + cols: 3, + clues: [ + { row: 0, col: 1, value: 3 }, + { row: 1, col: 0, value: 3 }, + { row: 1, col: 1, value: 0 }, + { row: 2, col: 1, value: 3 }, + ], + filledCells: [ + { row: 0, col: 1, fill: 'green' }, + { row: 1, col: 0, fill: 'green' }, + { row: 1, col: 1, fill: 'green' }, + { row: 1, col: 2, fill: 'green' }, + { row: 2, col: 1, fill: 'green' }, + ], + edges: [ + { edge: [[0, 1], [0, 2]], mark: 'line' }, + { edge: [[0, 1], [1, 1]], mark: 'line' }, + { edge: [[0, 2], [1, 2]], mark: 'line' }, + { edge: [[1, 0], [1, 1]], mark: 'line' }, + { edge: [[1, 0], [2, 0]], mark: 'line' }, + { edge: [[1, 2], [1, 3]], mark: 'line' }, + { edge: [[1, 3], [2, 3]], mark: 'line' }, + { edge: [[2, 0], [2, 1]], mark: 'line' }, + { edge: [[2, 1], [3, 1]], mark: 'line' }, + { edge: [[2, 2], [2, 3]], mark: 'line' }, + { edge: [[2, 2], [3, 2]], mark: 'line' }, + { edge: [[3, 1], [3, 2]], mark: 'line' }, + ], + }, + }, + ], +} + export const slitherPlugin: PuzzlePlugin = { id: 'slitherlink', displayName: 'Slitherlink', + help: slitherHelp, + legend: slitherLegend, parse: parseSlitherInput, encode: encodeSlitherToPuzzlink, getRules: () => slitherRules, diff --git a/src/domain/plugins/types.ts b/src/domain/plugins/types.ts index ac3da29..028a8e5 100644 --- a/src/domain/plugins/types.ts +++ b/src/domain/plugins/types.ts @@ -1,9 +1,64 @@ import type { PuzzleIR } from '../ir/types' import type { Rule } from '../rules/types' +export type PuzzleHelpExampleEdge = { + edge: [[row: number, col: number], [row: number, col: number]] + mark: 'line' | 'blank' +} + +export type PuzzleHelpExample = { + label: string + description: string + rows: number + cols: number + clues: Array<{ row: number; col: number; value: number | '?' }> + edges: PuzzleHelpExampleEdge[] +} + +export type PuzzleLegendSectorMarker = { + row: number + col: number + corner: 'nw' | 'ne' | 'sw' | 'se' + kind: 'onlyOne' | 'notOne' | 'notZero' | 'notTwo' +} + +export type PuzzleLegendExample = { + rows: number + cols: number + clues?: Array<{ row: number; col: number; value: number | '?' }> + edges?: PuzzleHelpExampleEdge[] + filledCells?: Array<{ row: number; col: number; fill: 'green' | 'yellow' }> + sectors?: PuzzleLegendSectorMarker[] +} + +export type PuzzleLegendItem = { + label: string + description: string + example: PuzzleLegendExample +} + +export type PuzzleLegendContent = { + title: string + items: PuzzleLegendItem[] +} + +export type PuzzleHelpContent = { + title: string + summary: string + rules: string[] + notes?: string[] + example?: { + title: string + before: PuzzleHelpExample + after: PuzzleHelpExample + } +} + export interface PuzzlePlugin { id: string displayName: string + help?: PuzzleHelpContent + legend?: PuzzleLegendContent parse: (input: string) => PuzzleIR encode: (puzzle: PuzzleIR) => string getRules: () => Rule[] diff --git a/src/domain/rules/slither/rules.test.ts b/src/domain/rules/slither/rules.test.ts index 8eb539d..e0a1525 100644 --- a/src/domain/rules/slither/rules.test.ts +++ b/src/domain/rules/slither/rules.test.ts @@ -24,11 +24,11 @@ import { } from '../../ir/types' import { runNextRule } from '../engine' import type { Rule } from '../types' -import { slitherRules } from './rules' +import { deterministicSlitherRules, slitherRules } from './rules' import { createColorAssumptionInferenceRule } from './rules/colorAssumptionInference' import { createSectorParityInferenceRule } from './rules/sectorParityInference' import { createStrongInferenceRule } from './rules/strongInference' -import { runTrialUntilFixpoint } from './rules/trial' +import { findHardContradictionReason, runTrialUntilFixpoint } from './rules/trial' const setClue = (puzzle: PuzzleIR, row: number, col: number, value: number): void => { puzzle.cells[cellKey(row, col)] = { @@ -39,6 +39,25 @@ const setClue = (puzzle: PuzzleIR, row: number, col: number, value: number): voi const getEdgeDiffKeys = (result: ReturnType<(typeof slitherRules)[number]['apply']>): string[] => result?.diffs.flatMap((d) => (d.kind === 'edge' ? [d.edgeKey] : [])) ?? [] +describe('slither deterministic rule order', () => { + it('prioritizes exact sector edge propagation before color and sector inference rules', () => { + const edgePropagationIdx = deterministicSlitherRules.findIndex( + (rule) => rule.id === 'sector-constraint-edge-propagation', + ) + const vertexDegreeIdx = deterministicSlitherRules.findIndex((rule) => rule.id === 'vertex-degree') + const colorOutsideIdx = deterministicSlitherRules.findIndex( + (rule) => rule.id === 'color-outside-seeding', + ) + const sectorInferenceIdx = deterministicSlitherRules.findIndex( + (rule) => rule.id === 'sector-inference', + ) + + expect(edgePropagationIdx).toBeGreaterThan(vertexDegreeIdx) + expect(edgePropagationIdx).toBeLessThan(colorOutsideIdx) + expect(edgePropagationIdx).toBeLessThan(sectorInferenceIdx) + }) +}) + describe('slither contiguous 3-run boundaries rule', () => { const threeRunRule = slitherRules.find((rule) => rule.id === 'contiguous-three-run-boundaries') if (!threeRunRule) { @@ -54,7 +73,7 @@ describe('slither contiguous 3-run boundaries rule', () => { const result = threeRunRule.apply(puzzle) expect(result).not.toBeNull() - expect(result?.message).toContain('Contiguous 3-run pattern forced') + expect(result?.message).toContain('Contiguous 3-run') expect(result?.affectedCells).toEqual(['1,1', '1,2', '1,3']) expect(result?.diffs).toEqual([ { kind: 'edge', edgeKey: edgeKey([1, 1], [2, 1]), from: 'unknown', to: 'line' }, @@ -77,7 +96,7 @@ describe('slither contiguous 3-run boundaries rule', () => { const result = threeRunRule.apply(puzzle) expect(result).not.toBeNull() - expect(result?.message).toContain('Contiguous 3-run pattern forced') + expect(result?.message).toContain('Contiguous 3-run') expect(result?.affectedCells).toEqual(['1,2', '2,2', '3,2']) expect(result?.diffs).toEqual([ { kind: 'edge', edgeKey: edgeKey([1, 2], [1, 3]), from: 'unknown', to: 'line' }, @@ -201,7 +220,7 @@ describe('slither diagonal adjacent 3 outer corners rule', () => { const result = diagonalRule.apply(puzzle) expect(result).not.toBeNull() - expect(result?.message).toContain('Diagonal adjacent 3s force outer-corner boundary edges to be lines.') + expect(result?.message).toContain('Diagonal adjacent 3s force their outside corner edges') expect(result?.affectedCells).toEqual(['0,0', '1,1']) expect(getEdgeDiffKeys(result)).toEqual([ edgeKey([0, 0], [1, 0]), @@ -219,7 +238,7 @@ describe('slither diagonal adjacent 3 outer corners rule', () => { const result = diagonalRule.apply(puzzle) expect(result).not.toBeNull() - expect(result?.message).toContain('Diagonal adjacent 3s force outer-corner boundary edges to be lines.') + expect(result?.message).toContain('Diagonal adjacent 3s force their outside corner edges') expect(result?.affectedCells).toEqual(['0,1', '1,0']) expect(getEdgeDiffKeys(result)).toEqual([ edgeKey([0, 1], [0, 2]), @@ -289,6 +308,122 @@ describe('slither diagonal adjacent 3 outer corners rule', () => { }) }) +describe('slither adjacent 2-3 opposite-cross rule', () => { + const adjacentRule = slitherRules.find((rule) => rule.id === 'adjacent-two-three-opposite-cross') + if (!adjacentRule) { + throw new Error('Expected adjacent-two-three-opposite-cross rule') + } + + it('forces the 3 opposite edge and shared-edge extensions for a horizontal 2-3 pair', () => { + const puzzle = createSlitherPuzzle(4, 4) + setClue(puzzle, 1, 1, 2) + setClue(puzzle, 1, 2, 3) + puzzle.edges[edgeKey([1, 1], [2, 1])].mark = 'blank' + + const result = adjacentRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.affectedCells).toEqual([cellKey(1, 1), cellKey(1, 2)]) + expect(result?.diffs).toEqual([ + { kind: 'edge', edgeKey: edgeKey([1, 3], [2, 3]), from: 'unknown', to: 'line' }, + { kind: 'edge', edgeKey: edgeKey([0, 2], [1, 2]), from: 'unknown', to: 'blank' }, + { kind: 'edge', edgeKey: edgeKey([2, 2], [3, 2]), from: 'unknown', to: 'blank' }, + ]) + }) + + it('supports the mirrored horizontal 3-2 order', () => { + const puzzle = createSlitherPuzzle(4, 4) + setClue(puzzle, 1, 1, 3) + setClue(puzzle, 1, 2, 2) + puzzle.edges[edgeKey([1, 3], [2, 3])].mark = 'blank' + + const result = adjacentRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.affectedCells).toEqual([cellKey(1, 2), cellKey(1, 1)]) + expect(result?.diffs).toEqual([ + { kind: 'edge', edgeKey: edgeKey([1, 1], [2, 1]), from: 'unknown', to: 'line' }, + { kind: 'edge', edgeKey: edgeKey([0, 2], [1, 2]), from: 'unknown', to: 'blank' }, + { kind: 'edge', edgeKey: edgeKey([2, 2], [3, 2]), from: 'unknown', to: 'blank' }, + ]) + }) + + it('forces the 3 opposite edge and shared-edge extensions for a vertical 2-3 pair', () => { + const puzzle = createSlitherPuzzle(4, 4) + setClue(puzzle, 1, 1, 2) + setClue(puzzle, 2, 1, 3) + puzzle.edges[edgeKey([1, 1], [1, 2])].mark = 'blank' + + const result = adjacentRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.affectedCells).toEqual([cellKey(1, 1), cellKey(2, 1)]) + expect(result?.diffs).toEqual([ + { kind: 'edge', edgeKey: edgeKey([3, 1], [3, 2]), from: 'unknown', to: 'line' }, + { kind: 'edge', edgeKey: edgeKey([2, 0], [2, 1]), from: 'unknown', to: 'blank' }, + { kind: 'edge', edgeKey: edgeKey([2, 2], [2, 3]), from: 'unknown', to: 'blank' }, + ]) + }) + + it('supports the mirrored vertical 3-2 order', () => { + const puzzle = createSlitherPuzzle(4, 4) + setClue(puzzle, 1, 1, 3) + setClue(puzzle, 2, 1, 2) + puzzle.edges[edgeKey([3, 1], [3, 2])].mark = 'blank' + + const result = adjacentRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.affectedCells).toEqual([cellKey(2, 1), cellKey(1, 1)]) + expect(result?.diffs).toEqual([ + { kind: 'edge', edgeKey: edgeKey([1, 1], [1, 2]), from: 'unknown', to: 'line' }, + { kind: 'edge', edgeKey: edgeKey([2, 0], [2, 1]), from: 'unknown', to: 'blank' }, + { kind: 'edge', edgeKey: edgeKey([2, 2], [2, 3]), from: 'unknown', to: 'blank' }, + ]) + }) + + it('only emits in-bounds extension blanks at the boundary', () => { + const puzzle = createSlitherPuzzle(3, 4) + setClue(puzzle, 0, 1, 2) + setClue(puzzle, 0, 2, 3) + puzzle.edges[edgeKey([0, 1], [1, 1])].mark = 'blank' + + const result = adjacentRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([ + { kind: 'edge', edgeKey: edgeKey([0, 3], [1, 3]), from: 'unknown', to: 'line' }, + { kind: 'edge', edgeKey: edgeKey([1, 2], [2, 2]), from: 'unknown', to: 'blank' }, + ]) + }) + + it('does not apply when the 2 opposite edge is not explicitly blank', () => { + const puzzle = createSlitherPuzzle(4, 4) + setClue(puzzle, 1, 1, 2) + setClue(puzzle, 1, 2, 3) + + const result = adjacentRule.apply(puzzle) + + expect(result).toBeNull() + }) + + it('emits diffs only for unknown target edges', () => { + const puzzle = createSlitherPuzzle(4, 4) + setClue(puzzle, 1, 1, 2) + setClue(puzzle, 1, 2, 3) + puzzle.edges[edgeKey([1, 1], [2, 1])].mark = 'blank' + puzzle.edges[edgeKey([1, 3], [2, 3])].mark = 'line' + puzzle.edges[edgeKey([0, 2], [1, 2])].mark = 'blank' + + const result = adjacentRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([ + { kind: 'edge', edgeKey: edgeKey([2, 2], [3, 2]), from: 'unknown', to: 'blank' }, + ]) + }) +}) + describe('slither cell clue completion rule', () => { const cellCountRule = slitherRules.find((rule) => rule.id === 'cell-count-completion') if (!cellCountRule) { @@ -622,6 +757,90 @@ describe('slither color clue propagation rule', () => { toFill: 'yellow', }) }) + + it('propagates green neighbors from two yellow neighbors around a clue 2 without needing the clue cell color', () => { + const puzzle = createSlitherPuzzle(3, 3) + setClue(puzzle, 1, 1, 2) + puzzle.cells[cellKey(0, 1)] = { fill: 'yellow' } + puzzle.cells[cellKey(1, 0)] = { fill: 'yellow' } + + const result = clueColorRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toContainEqual({ + kind: 'cell', + cellKey: cellKey(2, 1), + fromFill: null, + toFill: 'green', + }) + expect(result?.diffs).toContainEqual({ + kind: 'cell', + cellKey: cellKey(1, 2), + fromFill: null, + toFill: 'green', + }) + }) + + it('propagates yellow neighbors from two green neighbors around a clue 2 without needing the clue cell color', () => { + const puzzle = createSlitherPuzzle(3, 3) + setClue(puzzle, 1, 1, 2) + puzzle.cells[cellKey(0, 1)] = { fill: 'green' } + puzzle.cells[cellKey(1, 0)] = { fill: 'green' } + + const result = clueColorRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toContainEqual({ + kind: 'cell', + cellKey: cellKey(2, 1), + fromFill: null, + toFill: 'yellow', + }) + expect(result?.diffs).toContainEqual({ + kind: 'cell', + cellKey: cellKey(1, 2), + fromFill: null, + toFill: 'yellow', + }) + }) + + it('counts out-of-bounds directions as yellow when propagating from a corner clue 2', () => { + const puzzle = createSlitherPuzzle(2, 2) + setClue(puzzle, 0, 0, 2) + + const result = clueColorRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toContainEqual({ + kind: 'cell', + cellKey: cellKey(1, 0), + fromFill: null, + toFill: 'green', + }) + expect(result?.diffs).toContainEqual({ + kind: 'cell', + cellKey: cellKey(0, 1), + fromFill: null, + toFill: 'green', + }) + }) + + it('counts out-of-bounds directions as yellow when coloring a corner numbered cell', () => { + const puzzle = createSlitherPuzzle(2, 2) + setClue(puzzle, 0, 0, 1) + puzzle.cells[cellKey(1, 0)] = { fill: 'yellow' } + puzzle.cells[cellKey(0, 1)] = { fill: 'yellow' } + + const result = clueColorRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toContainEqual({ + kind: 'cell', + cellKey: cellKey(0, 0), + fromFill: null, + toFill: 'green', + }) + }) }) describe('slither color orthogonal consensus propagation rule', () => { @@ -994,7 +1213,7 @@ describe('slither color connectivity cut coloring rule', () => { expect(result).toBeNull() }) - it('does not traverse through an unknown clue-3 cell', () => { + it('treats an unknown clue-3 cell as a colorable connectivity candidate', () => { const puzzle = createSlitherPuzzle(1, 3) puzzle.cells[cellKey(0, 0)] = { fill: 'green' } puzzle.cells[cellKey(0, 2)] = { fill: 'green' } @@ -1002,7 +1221,10 @@ describe('slither color connectivity cut coloring rule', () => { const result = cutColorRule.apply(puzzle) - expect(result).toBeNull() + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([ + { kind: 'cell', cellKey: cellKey(0, 1), fromFill: null, toFill: 'green' }, + ]) }) it('colors every unknown cell inside a blank-compressed green bottleneck', () => { @@ -1034,6 +1256,44 @@ describe('slither color connectivity cut coloring rule', () => { { kind: 'cell', cellKey: cellKey(0, 1), fromFill: null, toFill: 'yellow' }, ]) }) + + it('colors cells unreachable from green sources yellow', () => { + const puzzle = createSlitherPuzzle(1, 3) + puzzle.cells[cellKey(0, 0)] = { fill: 'green' } + puzzle.edges[edgeKey([0, 1], [1, 1])].mark = 'line' + + const result = cutColorRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([ + { kind: 'cell', cellKey: cellKey(0, 1), fromFill: null, toFill: 'yellow' }, + { kind: 'cell', cellKey: cellKey(0, 2), fromFill: null, toFill: 'yellow' }, + ]) + }) + + it('colors cells unreachable from the exterior green without an existing yellow source', () => { + const puzzle = createSlitherPuzzle(1, 1) + puzzle.edges[edgeKey([0, 0], [0, 1])].mark = 'line' + puzzle.edges[edgeKey([1, 0], [1, 1])].mark = 'line' + puzzle.edges[edgeKey([0, 0], [1, 0])].mark = 'line' + puzzle.edges[edgeKey([0, 1], [1, 1])].mark = 'line' + + const result = cutColorRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([ + { kind: 'cell', cellKey: cellKey(0, 0), fromFill: null, toFill: 'green' }, + ]) + }) + + it('keeps unknown edges passable for connectivity reachability', () => { + const puzzle = createSlitherPuzzle(1, 2) + puzzle.cells[cellKey(0, 0)] = { fill: 'green' } + + const result = cutColorRule.apply(puzzle) + + expect(result).toBeNull() + }) }) describe('slither color sector-mask propagation rule', () => { @@ -1119,35 +1379,145 @@ describe('slither color sector-mask propagation rule', () => { }) }) - it('does not apply when both adjacent cells are unknown and in bounds', () => { - const puzzle = createSlitherPuzzle(3, 3) - puzzle.sectors[sectorKey(1, 1, 'se')].constraintsMask = SECTOR_MASK_NOT_1 + it('infers onlyOne sectors from different diagonal-adjacent colors', () => { + const puzzle = createSlitherPuzzle(2, 2) + puzzle.cells[cellKey(0, 1)] = { fill: 'green' } + puzzle.cells[cellKey(1, 0)] = { fill: 'yellow' } const result = sectorColorRule.apply(puzzle) - expect(result).toBeNull() + expect(result).not.toBeNull() + expect(result?.diffs).toContainEqual({ + kind: 'sector', + sectorKey: sectorKey(0, 0, 'se'), + fromMask: SECTOR_MASK_ALL, + toMask: SECTOR_MASK_ONLY_1, + }) + expect(result?.diffs).toContainEqual({ + kind: 'sector', + sectorKey: sectorKey(1, 1, 'nw'), + fromMask: SECTOR_MASK_ALL, + toMask: SECTOR_MASK_ONLY_1, + }) + }) + + it('infers notOne sectors from same diagonal-adjacent colors', () => { + const puzzle = createSlitherPuzzle(2, 2) + puzzle.cells[cellKey(0, 1)] = { fill: 'green' } + puzzle.cells[cellKey(1, 0)] = { fill: 'green' } + + const result = sectorColorRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toContainEqual({ + kind: 'sector', + sectorKey: sectorKey(0, 0, 'se'), + fromMask: SECTOR_MASK_ALL, + toMask: SECTOR_MASK_NOT_1, + }) + expect(result?.diffs).toContainEqual({ + kind: 'sector', + sectorKey: sectorKey(1, 1, 'nw'), + fromMask: SECTOR_MASK_ALL, + toMask: SECTOR_MASK_NOT_1, + }) + }) + + it('uses out-of-bounds yellow for color-to-sector propagation at the boundary', () => { + const puzzle = createSlitherPuzzle(2, 2) + puzzle.cells[cellKey(0, 1)] = { fill: 'green' } + + const result = sectorColorRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toContainEqual({ + kind: 'sector', + sectorKey: sectorKey(0, 0, 'ne'), + fromMask: SECTOR_MASK_ALL, + toMask: SECTOR_MASK_ONLY_1, + }) + expect(result?.diffs).not.toContainEqual({ + kind: 'sector', + sectorKey: sectorKey(-1, 1, 'sw'), + fromMask: SECTOR_MASK_ALL, + toMask: SECTOR_MASK_ONLY_1, + }) }) - it('does not apply when both adjacent cells are already colored', () => { + it('does not emit redundant or invalid color-to-sector updates for the target sectors', () => { + const compatiblePuzzle = createSlitherPuzzle(3, 3) + compatiblePuzzle.sectors[sectorKey(1, 1, 'se')].constraintsMask = SECTOR_MASK_ONLY_1 + compatiblePuzzle.sectors[sectorKey(2, 2, 'nw')].constraintsMask = SECTOR_MASK_ONLY_1 + compatiblePuzzle.cells[cellKey(1, 2)] = { fill: 'green' } + compatiblePuzzle.cells[cellKey(2, 1)] = { fill: 'yellow' } + + const compatibleResult = sectorColorRule.apply(compatiblePuzzle) + + expect(compatibleResult?.diffs).not.toContainEqual({ + kind: 'sector', + sectorKey: sectorKey(1, 1, 'se'), + fromMask: SECTOR_MASK_ONLY_1, + toMask: SECTOR_MASK_ONLY_1, + }) + expect(compatibleResult?.diffs).not.toContainEqual({ + kind: 'sector', + sectorKey: sectorKey(2, 2, 'nw'), + fromMask: SECTOR_MASK_ONLY_1, + toMask: SECTOR_MASK_ONLY_1, + }) + + const invalidPuzzle = createSlitherPuzzle(3, 3) + invalidPuzzle.sectors[sectorKey(1, 1, 'se')].constraintsMask = SECTOR_MASK_NOT_1 + invalidPuzzle.sectors[sectorKey(2, 2, 'nw')].constraintsMask = SECTOR_MASK_NOT_1 + invalidPuzzle.cells[cellKey(1, 2)] = { fill: 'green' } + invalidPuzzle.cells[cellKey(2, 1)] = { fill: 'yellow' } + + const invalidResult = sectorColorRule.apply(invalidPuzzle) + + expect(invalidResult?.diffs).not.toContainEqual({ + kind: 'sector', + sectorKey: sectorKey(1, 1, 'se'), + fromMask: SECTOR_MASK_NOT_1, + toMask: SECTOR_MASK_ONLY_1, + }) + expect(invalidResult?.diffs).not.toContainEqual({ + kind: 'sector', + sectorKey: sectorKey(2, 2, 'nw'), + fromMask: SECTOR_MASK_NOT_1, + toMask: SECTOR_MASK_ONLY_1, + }) + }) + + it('does not apply when both adjacent cells are unknown and in bounds', () => { const puzzle = createSlitherPuzzle(3, 3) puzzle.sectors[sectorKey(1, 1, 'se')].constraintsMask = SECTOR_MASK_NOT_1 - puzzle.cells[cellKey(1, 2)] = { fill: 'green' } - puzzle.cells[cellKey(2, 1)] = { fill: 'yellow' } const result = sectorColorRule.apply(puzzle) expect(result).toBeNull() }) - it('skips conflicting inference and returns null when no other updates exist', () => { - const puzzle = createSlitherPuzzle(3, 3) - puzzle.sectors[sectorKey(1, 1, 'se')].constraintsMask = SECTOR_MASK_ONLY_1 - puzzle.cells[cellKey(1, 2)] = { fill: 'green' } - puzzle.cells[cellKey(2, 1)] = { fill: 'green' } + it('skips conflicting color-to-sector inference without reporting an invalid mask', () => { + const puzzle = createSlitherPuzzle(2, 2) + puzzle.sectors[sectorKey(0, 0, 'se')].constraintsMask = SECTOR_MASK_ONLY_1 + puzzle.sectors[sectorKey(1, 1, 'nw')].constraintsMask = SECTOR_MASK_ONLY_1 + puzzle.cells[cellKey(0, 1)] = { fill: 'green' } + puzzle.cells[cellKey(1, 0)] = { fill: 'green' } const result = sectorColorRule.apply(puzzle) - expect(result).toBeNull() + expect(result?.diffs).not.toContainEqual({ + kind: 'sector', + sectorKey: sectorKey(0, 0, 'se'), + fromMask: SECTOR_MASK_ONLY_1, + toMask: SECTOR_MASK_NOT_1, + }) + expect(result?.diffs).not.toContainEqual({ + kind: 'sector', + sectorKey: sectorKey(1, 1, 'nw'), + fromMask: SECTOR_MASK_ONLY_1, + toMask: SECTOR_MASK_NOT_1, + }) }) }) @@ -1157,8 +1527,9 @@ describe('slither prevent premature loop rule', () => { throw new Error('Expected prevent-premature-loop rule') } - it('is ordered after vertex-degree, outside-seeding, edge-propagation, and clue-propagation', () => { + it('is ordered after vertex-degree, exact sector edge propagation, coloring, and clue-propagation', () => { const vertexRuleIdx = slitherRules.findIndex((rule) => rule.id === 'vertex-degree') + const sectorEdgeRuleIdx = slitherRules.findIndex((rule) => rule.id === 'sector-constraint-edge-propagation') const outsideRuleIdx = slitherRules.findIndex((rule) => rule.id === 'color-outside-seeding') const colorRuleIdx = slitherRules.findIndex((rule) => rule.id === 'color-edge-propagation') const clueRuleIdx = slitherRules.findIndex((rule) => rule.id === 'color-clue-propagation') @@ -1171,7 +1542,8 @@ describe('slither prevent premature loop rule', () => { const cutColorRuleIdx = slitherRules.findIndex((rule) => rule.id === 'color-connectivity-cut-coloring') const antiLoopRuleIdx = slitherRules.findIndex((rule) => rule.id === 'prevent-premature-loop') expect(vertexRuleIdx).toBeGreaterThanOrEqual(0) - expect(outsideRuleIdx).toBe(vertexRuleIdx + 1) + expect(sectorEdgeRuleIdx).toBe(vertexRuleIdx + 1) + expect(outsideRuleIdx).toBe(sectorEdgeRuleIdx + 1) expect(colorRuleIdx).toBe(outsideRuleIdx + 1) expect(clueRuleIdx).toBe(colorRuleIdx + 1) expect(sectorColorRuleIdx).toBe(clueRuleIdx + 1) @@ -1434,7 +1806,7 @@ describe('slither sector diagonal shared-vertex propagation rule', () => { expect(triggered).toBe(true) }) - it('appears during stepwise solving for the provided 10x10 puzzle', () => { + it('appears or becomes unnecessary during stepwise solving for the provided 10x10 puzzle', () => { let current = decodeSlitherFromPuzzlink( 'https://puzz.link/p?slither/10/10/ga337ddkdh2adbgdi20dp23dibgbd0dhdkd511da', ) @@ -1452,7 +1824,7 @@ describe('slither sector diagonal shared-vertex propagation rule', () => { current = nextPuzzle } - expect(triggered).toBe(true) + expect(triggered || diagonalSectorRule.apply(current) === null).toBe(true) }) }) @@ -1647,6 +2019,51 @@ describe('slither sector constraint edge propagation rule', () => { ]) }) + it('propagates the provided 10x10 top-edge onlyTwo sector to lines immediately after inference', () => { + let current = decodeSlitherFromPuzzlink( + 'https://puzz.link/p?slither/10/10/g88b227637bg2067a7bj8c6a223c1adh1cb1bi32di1dc33783', + ) + const targetSector = sectorKey(0, 1, 'ne') + const targetEdges = getCornerEdgeKeys(0, 1, 'ne') + let sawOnlyTwoInference = false + + for (let stepNumber = 1; stepNumber <= 80; stepNumber += 1) { + const { nextPuzzle, step } = runNextRule(current, slitherRules, stepNumber) + if (!step) { + break + } + + if (!sawOnlyTwoInference) { + const sectorDiff = step.diffs.find((d) => d.kind === 'sector' && d.sectorKey === targetSector) + if (sectorDiff?.kind === 'sector' && sectorDiff.toMask === SECTOR_MASK_ONLY_2) { + if (targetEdges.every((edge) => nextPuzzle.edges[edge]?.mark === 'line')) { + return + } + sawOnlyTwoInference = true + current = nextPuzzle + continue + } + } else { + expect(step.ruleId).toBe('sector-constraint-edge-propagation') + expect(step.diffs).toEqual( + expect.arrayContaining( + targetEdges.map((edge) => ({ + kind: 'edge' as const, + edgeKey: edge, + from: 'unknown' as const, + to: 'line' as const, + })), + ), + ) + return + } + + current = nextPuzzle + } + + throw new Error('Expected sector inference followed by immediate edge propagation for (R1, C2, NE).') + }) + it('forces the last unknown corner edge to line when onlyOne with one blank', () => { const puzzle = createSlitherPuzzle(2, 2) puzzle.sectors[sectorKey(0, 0, 'nw')].constraintsMask = SECTOR_MASK_ONLY_1 @@ -1882,7 +2299,7 @@ describe('slither apply sectors rule', () => { const result = applySectorsRule.apply(puzzle) expect(result).not.toBeNull() - expect(result?.message).toContain('Apply Sectors from Vertex') + expect(result?.message).toContain('narrow the allowed corner line counts') expect(result?.affectedCells).toEqual(['0,0']) expect(result?.affectedSectors).toEqual([ sectorKey(0, 0, 'nw'), @@ -2088,8 +2505,8 @@ describe('slither sector parity inference rule', () => { { kind: 'edge', edgeKey: right, from: 'unknown', to: 'blank' }, ]) expect(result?.affectedSectors).toEqual([targetSector]) - expect(result?.message).toContain('candidate=sector-not-one') - expect(result?.message).toContain('result=contradiction') + expect(result?.message).toContain('cannot have exactly one line') + expect(result?.message).toContain('contradicts the puzzle') }) it('forces both notOne sector edges line when the both-blank branch violates a clue', () => { @@ -2110,7 +2527,128 @@ describe('slither sector parity inference rule', () => { { kind: 'edge', edgeKey: right, from: 'unknown', to: 'line' }, ]) expect(result?.affectedSectors).toEqual([targetSector]) - expect(result?.message).toContain('result=contradiction') + expect(result?.message).toContain('contradicts the puzzle') + }) + + it('forces a sector parity branch when the opposite branch contradicts and the survivor reaches the probe budget', () => { + const puzzle = createSlitherPuzzle(1, 1) + setClue(puzzle, 0, 0, 2) + const targetSector = sectorKey(0, 0, 'nw') + puzzle.sectors[targetSector].constraintsMask = SECTOR_MASK_NOT_1 + const [top, left] = getCornerEdgeKeys(0, 0, 'nw') + const bottom = edgeKey([1, 0], [1, 1]) + const right = edgeKey([0, 1], [1, 1]) + const stallRule: Rule = { + id: 'sector-parity-stall-test', + name: 'Sector Parity Stall Test', + apply: (trial) => { + const topMark = trial.edges[top]?.mark ?? 'unknown' + const leftMark = trial.edges[left]?.mark ?? 'unknown' + if (topMark === 'blank' && leftMark === 'blank') { + return { + message: 'make blank branch contradict the clue', + diffs: [ + { kind: 'edge', edgeKey: bottom, from: 'unknown', to: 'blank' }, + { kind: 'edge', edgeKey: right, from: 'unknown', to: 'blank' }, + ], + affectedCells: [cellKey(0, 0)], + } + } + if (topMark !== 'line' || leftMark !== 'line') { + return null + } + return { + message: 'keep line branch unresolved', + diffs: [ + { + kind: 'sector', + sectorKey: targetSector, + fromMask: trial.sectors[targetSector].constraintsMask, + toMask: trial.sectors[targetSector].constraintsMask, + }, + ], + affectedCells: [cellKey(0, 0)], + affectedSectors: [targetSector], + } + }, + } + const probingSectorParityRule = createSectorParityInferenceRule(() => [stallRule], { + maxMs: Number.POSITIVE_INFINITY, + maxTrialSteps: 24, + }) + + const result = probingSectorParityRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([ + { kind: 'edge', edgeKey: top, from: 'unknown', to: 'line' }, + { kind: 'edge', edgeKey: left, from: 'unknown', to: 'line' }, + ]) + expect(result?.message).toContain('probe budget 24') + expect(result?.message).toContain('line branch unresolved after 24 steps') + expect(result?.message).toContain('blank branch contradicted after 1 step') + }) + + it('checks later sector parity candidates at the first probe budget', () => { + const puzzle = createSlitherPuzzle(2, 1) + const firstSector = sectorKey(0, 0, 'nw') + const secondSector = sectorKey(1, 0, 'nw') + puzzle.sectors[firstSector].constraintsMask = SECTOR_MASK_NOT_1 + puzzle.sectors[secondSector].constraintsMask = SECTOR_MASK_NOT_1 + setClue(puzzle, 1, 0, 2) + const bottom = edgeKey([2, 0], [2, 1]) + const right = edgeKey([1, 1], [2, 1]) + const [firstA, firstB] = getCornerEdgeKeys(0, 0, 'nw') + const [secondA, secondB] = getCornerEdgeKeys(1, 0, 'nw') + const stallRule: Rule = { + id: 'sector-parity-first-candidate-stall-test', + name: 'Sector Parity First Candidate Stall Test', + apply: (trial) => { + if ((trial.edges[secondA]?.mark ?? 'unknown') === 'blank' && (trial.edges[secondB]?.mark ?? 'unknown') === 'blank') { + return { + message: 'make second blank branch contradict the clue', + diffs: [ + { kind: 'edge', edgeKey: bottom, from: 'unknown', to: 'blank' }, + { kind: 'edge', edgeKey: right, from: 'unknown', to: 'blank' }, + ], + affectedCells: [cellKey(1, 0)], + } + } + if ((trial.edges[firstA]?.mark ?? 'unknown') === 'unknown') { + return null + } + if ((trial.edges[firstB]?.mark ?? 'unknown') === 'unknown') { + return null + } + return { + message: 'keep first candidate unresolved', + diffs: [ + { + kind: 'sector', + sectorKey: firstSector, + fromMask: trial.sectors[firstSector].constraintsMask, + toMask: trial.sectors[firstSector].constraintsMask, + }, + ], + affectedCells: [cellKey(0, 0)], + affectedSectors: [firstSector], + } + }, + } + const probingSectorParityRule = createSectorParityInferenceRule(() => [stallRule], { + maxMs: Number.POSITIVE_INFINITY, + maxTrialSteps: 24, + }) + + const result = probingSectorParityRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([ + { kind: 'edge', edgeKey: secondA, from: 'unknown', to: 'line' }, + { kind: 'edge', edgeKey: secondB, from: 'unknown', to: 'line' }, + ]) + expect(result?.affectedSectors).toEqual([secondSector]) + expect(result?.message).toContain('probe budget 24') }) it('returns null when both notOne parity branches remain feasible', () => { @@ -2124,6 +2662,129 @@ describe('slither sector parity inference rule', () => { }) }) +describe('slither trial diagnostics', () => { + it('reports vertex-degree contradiction locations', () => { + const puzzle = createSlitherPuzzle(2, 2) + puzzle.edges[edgeKey([0, 1], [1, 1])].mark = 'line' + puzzle.edges[edgeKey([1, 0], [1, 1])].mark = 'line' + puzzle.edges[edgeKey([1, 1], [1, 2])].mark = 'line' + + const reason = findHardContradictionReason(puzzle) + + expect(reason?.kind).toBe('vertex-degree') + expect(reason?.message).toContain('V(1, 1)') + }) + + it('reports cell-clue contradiction locations', () => { + const puzzle = createSlitherPuzzle(1, 1) + setClue(puzzle, 0, 0, 0) + puzzle.edges[edgeKey([0, 0], [0, 1])].mark = 'line' + + const reason = findHardContradictionReason(puzzle) + + expect(reason?.kind).toBe('cell-clue') + expect(reason?.message).toContain('(R1, C1)') + }) + + it('reports sector-mask contradiction locations', () => { + const puzzle = createSlitherPuzzle(1, 1) + const targetSector = sectorKey(0, 0, 'nw') + puzzle.sectors[targetSector].constraintsMask = 0 + + const reason = findHardContradictionReason(puzzle) + + expect(reason?.kind).toBe('sector-mask') + expect(reason?.message).toContain('(R1, C1, NW)') + }) + + it('reports vertex-candidates contradiction locations', () => { + const puzzle = createSlitherPuzzle(1, 1) + puzzle.vertices[vertexKey(0, 0)].candidateEdgeSets = [] + + const reason = findHardContradictionReason(puzzle) + + expect(reason?.kind).toBe('vertex-candidates') + expect(reason?.message).toContain('V(0, 0)') + }) + + it('reports color-edge contradiction locations', () => { + const puzzle = createSlitherPuzzle(1, 2) + puzzle.cells[cellKey(0, 0)] = { fill: 'green' } + puzzle.cells[cellKey(0, 1)] = { fill: 'green' } + const shared = edgeKey([0, 1], [1, 1]) + puzzle.edges[shared].mark = 'line' + + const reason = findHardContradictionReason(puzzle) + + expect(reason?.kind).toBe('color-edge') + expect(reason?.message).toContain('edge V(0, 1)-V(1, 1)') + }) + + it('reports line-loop contradiction shape', () => { + const puzzle = createSlitherPuzzle(2, 2) + puzzle.edges[edgeKey([0, 0], [0, 1])].mark = 'line' + puzzle.edges[edgeKey([0, 0], [1, 0])].mark = 'line' + puzzle.edges[edgeKey([0, 1], [1, 1])].mark = 'line' + puzzle.edges[edgeKey([1, 0], [1, 1])].mark = 'line' + puzzle.edges[edgeKey([2, 1], [2, 2])].mark = 'line' + + const reason = findHardContradictionReason(puzzle) + + expect(reason?.kind).toBe('line-loop') + expect(reason?.message).toContain('closed loop') + }) + + it('reports disconnected-green contradiction locations', () => { + const puzzle = createSlitherPuzzle(1, 3) + puzzle.cells[cellKey(0, 0)] = { fill: 'green' } + puzzle.cells[cellKey(0, 1)] = { fill: 'yellow' } + puzzle.cells[cellKey(0, 2)] = { fill: 'green' } + + const reason = findHardContradictionReason(puzzle) + + expect(reason?.kind).toBe('disconnected-green') + expect(reason?.message).toContain('(R1, C3)') + }) + + it('reports zero trial steps for immediate contradictions', () => { + const puzzle = createSlitherPuzzle(1, 2) + puzzle.cells[cellKey(0, 0)] = { fill: 'green' } + puzzle.cells[cellKey(0, 1)] = { fill: 'green' } + puzzle.edges[edgeKey([0, 1], [1, 1])].mark = 'line' + + const result = runTrialUntilFixpoint(puzzle, [], 10, Number.POSITIVE_INFINITY) + + expect(result.contradiction).toBe(true) + expect(result.stepsRun).toBe(0) + expect(result.contradictionReason?.kind).toBe('color-edge') + }) + + it('keeps exhausted trial behavior while reporting steps run', () => { + const puzzle = createSlitherPuzzle(1, 2) + const target = edgeKey([0, 0], [0, 1]) + const oneStepRule: Rule = { + id: 'trial-diagnostic-one-step', + name: 'Trial Diagnostic One Step', + apply: (trial) => { + if ((trial.edges[target]?.mark ?? 'unknown') !== 'unknown') { + return null + } + return { + message: 'test one trial step', + diffs: [{ kind: 'edge', edgeKey: target, from: 'unknown', to: 'blank' }], + affectedCells: [], + } + }, + } + + const result = runTrialUntilFixpoint(puzzle, [oneStepRule], 1, Number.POSITIVE_INFINITY) + + expect(result.contradiction).toBe(false) + expect(result.exhausted).toBe(true) + expect(result.stepsRun).toBe(1) + }) +}) + describe('slither strong inference rule', () => { const colorAssumptionRule = slitherRules.find((rule) => rule.id === 'color-assumption-inference') if (!colorAssumptionRule) { @@ -2177,8 +2838,58 @@ describe('slither strong inference rule', () => { expect(result).not.toBeNull() expect(result?.diffs).toEqual([{ kind: 'cell', cellKey: cellKey(0, 1), fromFill: null, toFill: 'yellow' }]) - expect(result?.message).toContain('result=contradiction') - expect(result?.message).toContain('green fails') + expect(result?.message).toContain('contradiction') + expect(result?.message).toContain('color-edge contradiction') + expect(result?.message).toContain('edge V(0, 1)-V(1, 1)') + expect(result?.message).toContain('0 trial steps') + expect(result?.message).toContain('Searched 1 candidate') + expect(result?.message).toContain('is green') + }) + + it('compresses same-color candidate components before searching', () => { + const puzzle = createSlitherPuzzle(1, 4) + puzzle.cells[cellKey(0, 0)] = { fill: 'green' } + puzzle.cells[cellKey(0, 3)] = { fill: 'yellow' } + puzzle.edges[edgeKey([0, 1], [1, 1])].mark = 'line' + puzzle.edges[edgeKey([0, 2], [1, 2])].mark = 'blank' + + const result = colorAssumptionRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([{ kind: 'cell', cellKey: cellKey(0, 1), fromFill: null, toFill: 'yellow' }]) + expect(result?.message).toContain('from 2 candidate cells') + expect(result?.message).toContain('compressed to 1 component') + }) + + it('compresses opposite-color candidate components connected by a line edge', () => { + const puzzle = createSlitherPuzzle(1, 4) + puzzle.cells[cellKey(0, 0)] = { fill: 'yellow' } + puzzle.cells[cellKey(0, 3)] = { fill: 'yellow' } + puzzle.edges[edgeKey([0, 1], [1, 1])].mark = 'line' + puzzle.edges[edgeKey([0, 2], [1, 2])].mark = 'line' + + const result = colorAssumptionRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([{ kind: 'cell', cellKey: cellKey(0, 1), fromFill: null, toFill: 'green' }]) + expect(result?.message).toContain('from 2 candidate cells') + expect(result?.message).toContain('compressed to 1 component') + }) + + it('keeps the highest-scored representative within a compressed component', () => { + const puzzle = createSlitherPuzzle(1, 4) + puzzle.cells[cellKey(0, 0)] = { fill: 'yellow' } + puzzle.cells[cellKey(0, 3)] = { fill: 'yellow' } + puzzle.edges[edgeKey([0, 2], [1, 2])].mark = 'blank' + puzzle.edges[edgeKey([0, 3], [1, 3])].mark = 'line' + puzzle.sectors[sectorKey(0, 2, 'nw')].constraintsMask = SECTOR_MASK_ONLY_1 + + const result = colorAssumptionRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([{ kind: 'cell', cellKey: cellKey(0, 2), fromFill: null, toFill: 'green' }]) + expect(result?.message).toContain('from 2 candidate cells') + expect(result?.message).toContain('compressed to 1 component') }) it('uses boundary color contradiction to force the opposite color', () => { @@ -2190,7 +2901,7 @@ describe('slither strong inference rule', () => { expect(result).not.toBeNull() expect(result?.diffs).toEqual([{ kind: 'cell', cellKey: cellKey(0, 1), fromFill: null, toFill: 'green' }]) - expect(result?.message).toContain('yellow fails') + expect(result?.message).toContain('is yellow') }) it('uses deterministic downstream propagation to find a contradiction', () => { @@ -2220,7 +2931,87 @@ describe('slither strong inference rule', () => { expect(result).not.toBeNull() expect(result?.diffs).toEqual([{ kind: 'cell', cellKey: cellKey(0, 1), fromFill: null, toFill: 'yellow' }]) - expect(result?.message).toContain('green fails') + expect(result?.message).toContain('is green') + expect(result?.message).toContain('after 1 trial step') + expect(result?.message).toContain('green branch: 1 step') + }) + + it('infers from a quick contradiction even when the opposite branch only reaches the probe budget', () => { + const puzzle = createSlitherPuzzle(3, 3) + puzzle.cells[cellKey(0, 0)] = { fill: 'green' } + puzzle.edges[edgeKey([0, 1], [1, 1])].mark = 'blank' + const stallRule: Rule = { + id: 'stall-test', + name: 'Stall Test', + apply: (trial) => { + if (trial.cells[cellKey(0, 1)]?.fill !== 'green') { + return null + } + return { + message: 'keep this branch unresolved', + diffs: [ + { + kind: 'sector', + sectorKey: sectorKey(2, 2, 'se'), + fromMask: trial.sectors[sectorKey(2, 2, 'se')].constraintsMask, + toMask: trial.sectors[sectorKey(2, 2, 'se')].constraintsMask, + }, + ], + affectedCells: [], + affectedSectors: [sectorKey(2, 2, 'se')], + } + }, + } + const probingRule = createColorAssumptionInferenceRule(() => [stallRule], { + maxMs: Number.POSITIVE_INFINITY, + maxTrialSteps: 24, + }) + + const result = probingRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([{ kind: 'cell', cellKey: cellKey(0, 1), fromFill: null, toFill: 'green' }]) + expect(result?.message).toContain('probe budget 24') + expect(result?.message).toContain('green branch: unresolved after 24 steps') + expect(result?.message).toContain('yellow branch: 0 steps') + }) + + it('moves to later components when an earlier component is unresolved at the current probe budget', () => { + const puzzle = createSlitherPuzzle(3, 3) + puzzle.cells[cellKey(0, 0)] = { fill: 'green' } + puzzle.sectors[sectorKey(0, 1, 'nw')].constraintsMask = SECTOR_MASK_ONLY_1 + puzzle.sectors[sectorKey(0, 1, 'ne')].constraintsMask = SECTOR_MASK_ONLY_1 + puzzle.sectors[sectorKey(0, 1, 'sw')].constraintsMask = SECTOR_MASK_ONLY_1 + puzzle.sectors[sectorKey(0, 1, 'se')].constraintsMask = SECTOR_MASK_ONLY_1 + puzzle.edges[edgeKey([1, 0], [1, 1])].mark = 'line' + const stallRule: Rule = { + id: 'stall-test', + name: 'Stall Test', + apply: () => ({ + message: 'keep this branch unresolved', + diffs: [ + { + kind: 'sector', + sectorKey: sectorKey(2, 2, 'se'), + fromMask: puzzle.sectors[sectorKey(2, 2, 'se')].constraintsMask, + toMask: puzzle.sectors[sectorKey(2, 2, 'se')].constraintsMask, + }, + ], + affectedCells: [], + affectedSectors: [sectorKey(2, 2, 'se')], + }), + } + const probingRule = createColorAssumptionInferenceRule(() => [stallRule], { + maxMs: Number.POSITIVE_INFINITY, + maxTrialSteps: 24, + }) + + const result = probingRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([{ kind: 'cell', cellKey: cellKey(1, 0), fromFill: null, toFill: 'yellow' }]) + expect(result?.message).toContain('Searched 2 candidate components') + expect(result?.message).toContain('probe budget 24') }) it('treats unreachable fixed green regions as a contradiction', () => { @@ -2232,7 +3023,9 @@ describe('slither strong inference rule', () => { expect(result).not.toBeNull() expect(result?.diffs).toEqual([{ kind: 'cell', cellKey: cellKey(0, 1), fromFill: null, toFill: 'green' }]) - expect(result?.message).toContain('yellow fails') + expect(result?.message).toContain('is yellow') + expect(result?.message).toContain('disconnected-green contradiction') + expect(result?.message).toContain('yellow branch: 0 steps') }) it('returns null when both color branches remain feasible', () => { @@ -2260,8 +3053,8 @@ describe('slither strong inference rule', () => { { kind: 'edge', edgeKey: right, from: 'unknown', to: 'blank' }, ]) expect(result?.affectedSectors).toEqual([sectorKey(0, 0, 'se')]) - expect(result?.message).toContain('candidate=sector-only-one') - expect(result?.message).toContain('result=contradiction') + expect(result?.message).toContain('must have exactly one line') + expect(result?.message).toContain('contradicts the puzzle') }) it('returns null when both onlyOne branches remain feasible', () => { @@ -2290,8 +3083,114 @@ describe('slither strong inference rule', () => { { kind: 'edge', edgeKey: up, from: 'unknown', to: 'blank' }, { kind: 'edge', edgeKey: down, from: 'unknown', to: 'line' }, ]) - expect(result?.message).toContain('candidate=vertex-two-choice((1, 0))') - expect(result?.message).toContain('result=contradiction') + expect(result?.message).toContain('has two possible continuations') + expect(result?.message).toContain('contradicts the puzzle') + }) + + it('forces an edge branch when the opposite branch contradicts and the survivor reaches the probe budget', () => { + const puzzle = createSlitherPuzzle(1, 1) + setClue(puzzle, 0, 0, 1) + const top = edgeKey([0, 0], [0, 1]) + const bottom = edgeKey([1, 0], [1, 1]) + const left = edgeKey([0, 0], [1, 0]) + const right = edgeKey([0, 1], [1, 1]) + const stallRule: Rule = { + id: 'strong-edge-stall-test', + name: 'Strong Edge Stall Test', + apply: (trial) => { + const topMark = trial.edges[top]?.mark ?? 'unknown' + if (topMark === 'blank') { + return { + message: 'make blank branch contradict the clue', + diffs: [ + { kind: 'edge', edgeKey: bottom, from: 'unknown', to: 'blank' }, + { kind: 'edge', edgeKey: left, from: 'unknown', to: 'blank' }, + { kind: 'edge', edgeKey: right, from: 'unknown', to: 'blank' }, + ], + affectedCells: [cellKey(0, 0)], + } + } + if (topMark !== 'line') { + return null + } + return { + message: 'keep line branch unresolved', + diffs: [ + { + kind: 'sector', + sectorKey: sectorKey(0, 0, 'nw'), + fromMask: trial.sectors[sectorKey(0, 0, 'nw')].constraintsMask, + toMask: trial.sectors[sectorKey(0, 0, 'nw')].constraintsMask, + }, + ], + affectedCells: [cellKey(0, 0)], + affectedSectors: [sectorKey(0, 0, 'nw')], + } + }, + } + const probingStrongRule = createStrongInferenceRule(() => [stallRule], { + maxMs: Number.POSITIVE_INFINITY, + maxTrialSteps: 24, + }) + + const result = probingStrongRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([{ kind: 'edge', edgeKey: top, from: 'unknown', to: 'line' }]) + expect(result?.message).toContain('probe budget 24') + expect(result?.message).toContain('A branch unresolved after 24 steps') + expect(result?.message).toContain('B branch contradicted after 1 step') + }) + + it('forces a binary strong branch when the opposite branch contradicts and the survivor reaches the probe budget', () => { + const puzzle = createSlitherPuzzle(2, 2) + setClue(puzzle, 0, 0, 1) + + const up = edgeKey([0, 0], [1, 0]) + const down = edgeKey([1, 0], [2, 0]) + const right = edgeKey([1, 0], [1, 1]) + puzzle.edges[right].mark = 'line' + const stallRule: Rule = { + id: 'strong-binary-stall-test', + name: 'Strong Binary Stall Test', + apply: (trial) => { + if ((trial.edges[up]?.mark ?? 'unknown') !== 'blank') { + return null + } + if ((trial.edges[down]?.mark ?? 'unknown') !== 'line') { + return null + } + return { + message: 'keep surviving binary branch unresolved', + diffs: [ + { + kind: 'sector', + sectorKey: sectorKey(1, 0, 'nw'), + fromMask: trial.sectors[sectorKey(1, 0, 'nw')].constraintsMask, + toMask: trial.sectors[sectorKey(1, 0, 'nw')].constraintsMask, + }, + ], + affectedCells: [cellKey(1, 0)], + affectedSectors: [sectorKey(1, 0, 'nw')], + } + }, + } + const probingStrongRule = createStrongInferenceRule(() => [stallRule], { + maxMs: Number.POSITIVE_INFINITY, + maxTrialSteps: 24, + }) + + const result = probingStrongRule.apply(puzzle) + + expect(result).not.toBeNull() + expect(result?.diffs).toEqual([ + { kind: 'edge', edgeKey: up, from: 'unknown', to: 'blank' }, + { kind: 'edge', edgeKey: down, from: 'unknown', to: 'line' }, + ]) + expect(result?.message).toContain('has two possible continuations') + expect(result?.message).toContain('probe budget 24') + expect(result?.message).toContain('A branch contradicted after 0 steps') + expect(result?.message).toContain('B branch unresolved after 24 steps') }) it('extracts shared consequences when both feasible branches agree downstream', () => { @@ -2326,8 +3225,9 @@ describe('slither strong inference rule', () => { expect(result).not.toBeNull() expect(result?.diffs).toEqual([{ kind: 'edge', edgeKey: top, from: 'unknown', to: 'line' }]) - expect(result?.message).toContain('candidate=sector-only-one') - expect(result?.message).toContain('result=shared-consequence') + expect(result?.message).toContain('must have exactly one line') + expect(result?.message).toContain('same consequence') + expect(result?.message).toContain('probe budget') }) it('can run on the provided 10x10 puzzle after deterministic stabilization', () => { diff --git a/src/domain/rules/slither/rules.ts b/src/domain/rules/slither/rules.ts index d77447a..90b001e 100644 --- a/src/domain/rules/slither/rules.ts +++ b/src/domain/rules/slither/rules.ts @@ -12,6 +12,7 @@ import { import { createColorAssumptionInferenceRule } from './rules/colorAssumptionInference' import { createCellCountRule, createPreventPrematureLoopRule, createVertexDegreeRule } from './rules/core' import { + createAdjacentTwoThreeOppositeCrossRule, createContiguousThreeRunBoundariesRule, createDiagonalAdjacentThreeOuterCornersRule, } from './rules/patterns' @@ -31,8 +32,10 @@ import { createStrongInferenceRule } from './rules/strongInference' export const deterministicSlitherRules: Rule[] = [ createContiguousThreeRunBoundariesRule(), createDiagonalAdjacentThreeOuterCornersRule(), + createAdjacentTwoThreeOppositeCrossRule(), createCellCountRule(), createVertexDegreeRule(), + createSectorConstraintEdgePropagationRule(), createColorOutsideSeedingRule(), createColorEdgePropagationRule(), createColorCluePropagationRule(), @@ -47,7 +50,7 @@ export const deterministicSlitherRules: Rule[] = [ createVertexCandidateEdgePruningRule(), createClueVertexCandidateCombinationPruningRule(), createSectorClueOneThreeIntraCellPropagationRule(), - createSectorConstraintEdgePropagationRule(), + // createSectorConstraintEdgePropagationRule(), createVertexOnlyOneNonSectorBalanceRule(), createSectorNotOneClueTwoPropagationRule(), ] diff --git a/src/domain/rules/slither/rules/color.ts b/src/domain/rules/slither/rules/color.ts index b3d306e..23dfe68 100644 --- a/src/domain/rules/slither/rules/color.ts +++ b/src/domain/rules/slither/rules/color.ts @@ -1,15 +1,21 @@ import { cellKey, edgeKey, sectorKey } from '../../../ir/keys' import { + SECTOR_MASK_NOT_1, SECTOR_MASK_ONLY_1, type EdgeMark, type PuzzleIR, + type SectorConstraintMask, type SectorCorner, sectorMaskAllows, + sectorMaskIntersect, } from '../../../ir/types' import type { Rule, RuleApplication } from '../../types' import { getCellNeighborKeys, getEdgeAdjacentCellKeys, + formatCellKeyLabel, + formatEdgeLabel, + formatSectorLabel, isSlitherCellColor, oppositeSlitherCellColor, type SlitherCellColor, @@ -22,6 +28,7 @@ export const createColorEdgePropagationRule = (): Rule => ({ const decidedEdges = new Map() const decidedCellFills = new Map() const affectedCells = new Set() + let firstReason: string | null = null const getEffectiveCellColor = (key: string): SlitherCellColor | null => { const decided = decidedCellFills.get(key) @@ -87,6 +94,12 @@ export const createColorEdgePropagationRule = (): Rule => ({ } if (decidedEdges.get(edgeKeyValue) === toMark) { affectedCells.add(cell) + if (firstReason === null) { + firstReason = + toMark === 'line' + ? `${formatCellKeyLabel(cell)} is inside, so boundary ${formatEdgeLabel(edgeKeyValue)} must be a line` + : `${formatCellKeyLabel(cell)} is outside, so boundary ${formatEdgeLabel(edgeKeyValue)} must be blank` + } } continue } @@ -103,6 +116,12 @@ export const createColorEdgePropagationRule = (): Rule => ({ if (decidedEdges.get(edgeKeyValue) === toMark) { affectedCells.add(cellA) affectedCells.add(cellB) + if (firstReason === null) { + firstReason = + toMark === 'line' + ? `${formatCellKeyLabel(cellA)} and ${formatCellKeyLabel(cellB)} have different colors, so their shared edge is a line` + : `${formatCellKeyLabel(cellA)} and ${formatCellKeyLabel(cellB)} have the same color, so their shared edge is blank` + } } } @@ -135,6 +154,12 @@ export const createColorEdgePropagationRule = (): Rule => ({ } affectedCells.add(cellA) affectedCells.add(cellB) + if (firstReason === null) { + firstReason = + effectiveMark === 'line' + ? `${formatEdgeLabel(edgeKeyValue)} is a line, so adjacent cells must have opposite colors` + : `${formatEdgeLabel(edgeKeyValue)} is blank, so adjacent cells must have the same color` + } } if (decidedEdges.size === 0 && decidedCellFills.size === 0) { @@ -159,7 +184,7 @@ export const createColorEdgePropagationRule = (): Rule => ({ const edgeCount = decidedEdges.size const colorCount = decidedCellFills.size return { - message: `Color-edge propagation applied (${edgeCount} edge update(s), ${colorCount} color update(s)).`, + message: `${firstReason ?? 'Known color-edge relations propagate across the grid'} (${edgeCount} edge update(s), ${colorCount} color update(s)).`, diffs, affectedCells: [...affectedCells], } @@ -246,6 +271,7 @@ export const createColorOutsideSeedingRule = (): Rule => ({ const decidedCellFills = new Map() const affectedCells = new Set() const anchoredRootColors = new Map() + let firstInferredCell: string | null = null for (let row = 0; row < puzzle.rows; row += 1) { for (let col = 0; col < puzzle.cols; col += 1) { @@ -316,6 +342,9 @@ export const createColorOutsideSeedingRule = (): Rule => ({ const inferredColor = applyParity(rootColor, parity) decidedCellFills.set(key, inferredColor) affectedCells.add(key) + if (firstInferredCell === null) { + firstInferredCell = formatCellKeyLabel(key) + } } } @@ -324,7 +353,7 @@ export const createColorOutsideSeedingRule = (): Rule => ({ } return { - message: `Color outside seeding applied (${decidedCellFills.size} color update(s)).`, + message: `Known colors and boundary edges anchor a parity component, so ${firstInferredCell ?? 'matching cells'} inherit inside/outside color (${decidedCellFills.size} color update(s)).`, diffs: [...decidedCellFills.entries()].map(([k, toFill]) => ({ kind: 'cell' as const, cellKey: k, @@ -342,6 +371,7 @@ export const createColorCluePropagationRule = (): Rule => ({ apply: (puzzle: PuzzleIR): RuleApplication | null => { const decidedCellFills = new Map() const affectedCells = new Set() + let firstReason: string | null = null const getEffectiveCellColor = (key: string): SlitherCellColor | null => { const decided = decidedCellFills.get(key) @@ -372,27 +402,64 @@ export const createColorCluePropagationRule = (): Rule => ({ const clue = Number(cell.clue.value) const neighbors = getCellNeighborKeys(puzzle, cellKeyValue) const innercnt = neighbors.filter((k) => getEffectiveCellColor(k) === 'green').length - const outercnt = neighbors.filter((k) => getEffectiveCellColor(k) === 'yellow').length + const boundaryOutercnt = 4 - neighbors.length + const outercnt = + neighbors.filter((k) => getEffectiveCellColor(k) === 'yellow').length + boundaryOutercnt if (clue < innercnt || 4 - clue < outercnt) { - rememberCellFill(cellKeyValue, 'green') + if (rememberCellFill(cellKeyValue, 'green') && firstReason === null) { + firstReason = `${formatCellKeyLabel(cellKeyValue)} must be inside; the neighboring outside/inside counts would otherwise exceed clue ${clue}` + } } if (clue < outercnt || 4 - clue < innercnt) { - rememberCellFill(cellKeyValue, 'yellow') + if (rememberCellFill(cellKeyValue, 'yellow') && firstReason === null) { + firstReason = `${formatCellKeyLabel(cellKeyValue)} must be outside; the neighboring inside/outside counts would otherwise exceed clue ${clue}` + } + } + + if (clue === 2 && outercnt === 2) { + neighbors.forEach((neighbor) => { + if (rememberCellFill(neighbor, 'green') && firstReason === null) { + firstReason = `${formatCellKeyLabel(cellKeyValue)} has two outside neighbors for clue 2, so remaining neighbors are inside` + } + }) + } + if (clue === 2 && innercnt === 2) { + neighbors.forEach((neighbor) => { + if (rememberCellFill(neighbor, 'yellow') && firstReason === null) { + firstReason = `${formatCellKeyLabel(cellKeyValue)} has two inside neighbors for clue 2, so remaining neighbors are outside` + } + }) } const currentColor = getEffectiveCellColor(cellKeyValue) if (currentColor === 'green' && clue === outercnt) { - neighbors.forEach((neighbor) => rememberCellFill(neighbor, 'green')) + neighbors.forEach((neighbor) => { + if (rememberCellFill(neighbor, 'green') && firstReason === null) { + firstReason = `${formatCellKeyLabel(cellKeyValue)} is inside and already has enough outside neighbors for clue ${clue}, so remaining neighbors are inside` + } + }) } if (currentColor === 'yellow' && clue === innercnt) { - neighbors.forEach((neighbor) => rememberCellFill(neighbor, 'yellow')) + neighbors.forEach((neighbor) => { + if (rememberCellFill(neighbor, 'yellow') && firstReason === null) { + firstReason = `${formatCellKeyLabel(cellKeyValue)} is outside and already has enough inside neighbors for clue ${clue}, so remaining neighbors are outside` + } + }) } if (currentColor === 'yellow' && clue === 4 - outercnt) { - neighbors.forEach((neighbor) => rememberCellFill(neighbor, 'green')) + neighbors.forEach((neighbor) => { + if (rememberCellFill(neighbor, 'green') && firstReason === null) { + firstReason = `${formatCellKeyLabel(cellKeyValue)} is outside and all remaining neighbors must be inside to satisfy clue ${clue}` + } + }) } if (currentColor === 'green' && clue === 4 - innercnt) { - neighbors.forEach((neighbor) => rememberCellFill(neighbor, 'yellow')) + neighbors.forEach((neighbor) => { + if (rememberCellFill(neighbor, 'yellow') && firstReason === null) { + firstReason = `${formatCellKeyLabel(cellKeyValue)} is inside and all remaining neighbors must be outside to satisfy clue ${clue}` + } + }) } } @@ -401,7 +468,7 @@ export const createColorCluePropagationRule = (): Rule => ({ } return { - message: `Color clue propagation applied (${decidedCellFills.size} color update(s)).`, + message: `${firstReason ?? 'A clue tightens neighboring inside/outside colors'} (${decidedCellFills.size} color update(s)).`, diffs: [...decidedCellFills.entries()].map(([k, toFill]) => ({ kind: 'cell' as const, cellKey: k, @@ -419,6 +486,7 @@ export const createColorOrthogonalConsensusPropagationRule = (): Rule => ({ apply: (puzzle: PuzzleIR): RuleApplication | null => { const decidedCellFills = new Map() const affectedCells = new Set() + let firstInferredCell: string | null = null const inBounds = (row: number, col: number): boolean => row >= 0 && row < puzzle.rows && col >= 0 && col < puzzle.cols @@ -484,6 +552,9 @@ export const createColorOrthogonalConsensusPropagationRule = (): Rule => ({ } if (rememberCellFill(currentKey, firstColor)) { + if (firstInferredCell === null) { + firstInferredCell = formatCellKeyLabel(currentKey) + } for (const [neighborRow, neighborCol] of orthogonals) { if (inBounds(neighborRow, neighborCol)) { affectedCells.add(cellKey(neighborRow, neighborCol)) @@ -498,7 +569,7 @@ export const createColorOrthogonalConsensusPropagationRule = (): Rule => ({ } return { - message: `Color orthogonal consensus propagation applied (${decidedCellFills.size} color update(s)).`, + message: `All orthogonal neighbors around ${firstInferredCell ?? 'a cell'} have the same color, so the center cell must match them (${decidedCellFills.size} color update(s)).`, diffs: [...decidedCellFills.entries()].map(([k, toFill]) => ({ kind: 'cell' as const, cellKey: k, @@ -597,7 +668,7 @@ export const createInsideReachabilityColoringRule = (): Rule => ({ } return { - message: `Inside reachability coloring applied (${decidedCellFills.size} color update(s)).`, + message: `Cells unreachable from known inside cells through non-line passages must be outside (${decidedCellFills.size} color update(s)).`, diffs: [...decidedCellFills.entries()].map(([k, toFill]) => ({ kind: 'cell' as const, cellKey: k, @@ -708,7 +779,7 @@ export const createOutsideReachabilityColoringRule = (): Rule => ({ } return { - message: `Outside reachability coloring applied (${decidedCellFills.size} color update(s)).`, + message: `Cells unreachable from the exterior through non-line passages must be inside (${decidedCellFills.size} color update(s)).`, diffs: [...decidedCellFills.entries()].map(([k, toFill]) => ({ kind: 'cell' as const, cellKey: k, @@ -728,10 +799,18 @@ type ConnectivityCutPassOptions = { getEffectiveCellColor: (key: string) => SlitherCellColor | null } -const findConnectivityCutCells = ( +type ConnectivityColorReason = 'cut' | 'unreachable' + +type ConnectivityColorUpdate = { + cellKey: string + toFill: SlitherCellColor + reason: ConnectivityColorReason +} + +const findConnectivityColorUpdates = ( puzzle: PuzzleIR, { target, includeOutsideSource, getEffectiveCellColor }: ConnectivityCutPassOptions, -): Set => { +): ConnectivityColorUpdate[] => { const blocked = oppositeSlitherCellColor(target) const parent = new Map() const rank = new Map() @@ -743,8 +822,7 @@ const findConnectivityCutCells = ( } } - const isCandidateCell = (key: string): boolean => - !isNumberClueThree(puzzle, key) && getEffectiveCellColor(key) !== blocked + const isCandidateCell = (key: string): boolean => getEffectiveCellColor(key) !== blocked const ensureNode = (key: string): void => { if (parent.has(key)) { @@ -820,8 +898,8 @@ const findConnectivityCutCells = ( if (includeOutsideSource) { sourceComponents.add(find(OUTSIDE_COMPONENT)) } - if (sourceComponents.size < 2) { - return new Set() + if (sourceComponents.size === 0) { + return [] } const graph = new Map>() @@ -870,6 +948,7 @@ const findConnectivityCutCells = ( const subtreeSources = new Map() const treeChildren = new Map() const cutComponents = new Set() + const reachableComponents = new Set() let timestamp = 0 const dfs = (node: string, parentNode: string | null, connectedNodes: string[]): void => { @@ -878,6 +957,7 @@ const findConnectivityCutCells = ( timestamp += 1 subtreeSources.set(node, sourceComponents.has(node) ? 1 : 0) connectedNodes.push(node) + reachableComponents.add(node) for (const neighbor of graph.get(node) ?? []) { if (neighbor === parentNode) { @@ -908,7 +988,7 @@ const findConnectivityCutCells = ( } } - for (const node of graph.keys()) { + for (const node of sourceComponents) { if (discovery.has(node)) { continue } @@ -921,15 +1001,28 @@ const findConnectivityCutCells = ( evaluateCuts(node, totalSources) } - const cutCells = new Set() + const updates = new Map() for (const component of cutComponents) { for (const key of componentCells.get(component) ?? []) { if (getEffectiveCellColor(key) === null) { - cutCells.add(key) + updates.set(key, { cellKey: key, toFill: target, reason: 'cut' }) } } } - return cutCells + + const unreachableFill = oppositeSlitherCellColor(target) + for (const [component, cells] of componentCells) { + if (reachableComponents.has(component)) { + continue + } + for (const key of cells) { + if (getEffectiveCellColor(key) === null) { + updates.set(key, { cellKey: key, toFill: unreachableFill, reason: 'unreachable' }) + } + } + } + + return inBoundsCellKeys.flatMap((key) => updates.get(key) ?? []) } export const createColorConnectivityCutColoringRule = (): Rule => ({ @@ -938,6 +1031,12 @@ export const createColorConnectivityCutColoringRule = (): Rule => ({ apply: (puzzle: PuzzleIR): RuleApplication | null => { const decidedCellFills = new Map() const affectedCells = new Set() + const stats = { + greenCuts: 0, + yellowCuts: 0, + greenUnreachable: 0, + yellowUnreachable: 0, + } const getEffectiveCellColor = (key: string): SlitherCellColor | null => { const decided = decidedCellFills.get(key) @@ -948,28 +1047,38 @@ export const createColorConnectivityCutColoringRule = (): Rule => ({ return isSlitherCellColor(current) ? current : null } - const rememberCellFill = (key: string, to: SlitherCellColor): void => { + const rememberCellFill = (update: ConnectivityColorUpdate): void => { + const { cellKey: key, toFill, reason } = update if (getEffectiveCellColor(key) !== null) { return } - decidedCellFills.set(key, to) + decidedCellFills.set(key, toFill) affectedCells.add(key) + if (reason === 'cut' && toFill === 'green') { + stats.greenCuts += 1 + } else if (reason === 'cut' && toFill === 'yellow') { + stats.yellowCuts += 1 + } else if (reason === 'unreachable' && toFill === 'yellow') { + stats.greenUnreachable += 1 + } else if (reason === 'unreachable' && toFill === 'green') { + stats.yellowUnreachable += 1 + } } - for (const key of findConnectivityCutCells(puzzle, { + for (const update of findConnectivityColorUpdates(puzzle, { target: 'green', includeOutsideSource: false, getEffectiveCellColor, })) { - rememberCellFill(key, 'green') + rememberCellFill(update) } - for (const key of findConnectivityCutCells(puzzle, { + for (const update of findConnectivityColorUpdates(puzzle, { target: 'yellow', includeOutsideSource: true, getEffectiveCellColor, })) { - rememberCellFill(key, 'yellow') + rememberCellFill(update) } if (decidedCellFills.size === 0) { @@ -994,7 +1103,7 @@ export const createColorConnectivityCutColoringRule = (): Rule => ({ } return { - message: `Color connectivity cut coloring applied (${decidedCellFills.size} color update(s)).`, + message: `Cell connectivity forces color updates: inside cuts ${stats.greenCuts}, outside cuts ${stats.yellowCuts}, unreachable-from-inside ${stats.greenUnreachable}, unreachable-from-outside ${stats.yellowUnreachable}.`, diffs, affectedCells: [...affectedCells], } @@ -1034,11 +1143,17 @@ export const createColorSectorMaskPropagationRule = (): Rule => ({ apply: (puzzle: PuzzleIR): RuleApplication | null => { const corners: SectorCorner[] = ['nw', 'ne', 'sw', 'se'] const decidedCellFills = new Map() + const decidedSectorMasks = new Map() const affectedCells = new Set() + const affectedSectors = new Set() + let firstReason: string | null = null const inBounds = (row: number, col: number): boolean => row >= 0 && row < puzzle.rows && col >= 0 && col < puzzle.cols + const getEffectiveSectorMask = (key: string): SectorConstraintMask | undefined => + decidedSectorMasks.get(key) ?? puzzle.sectors[key]?.constraintsMask + const getEffectiveCellColor = (key: string): SlitherCellColor | null => { const decided = decidedCellFills.get(key) if (decided) { @@ -1061,18 +1176,28 @@ export const createColorSectorMaskPropagationRule = (): Rule => ({ return true } + const rememberSectorMask = (key: string, targetMask: SectorConstraintMask): boolean => { + const current = getEffectiveSectorMask(key) + if (current === undefined) { + return false + } + const next = sectorMaskIntersect(current, targetMask) + if (next === 0 || next === current) { + return false + } + decidedSectorMasks.set(key, next) + affectedSectors.add(key) + return true + } + for (let row = 0; row < puzzle.rows; row += 1) { for (let col = 0; col < puzzle.cols; col += 1) { const sourceCellKey = cellKey(row, col) for (const corner of corners) { - const mask = puzzle.sectors[sectorKey(row, col, corner)]?.constraintsMask + const currentSectorKey = sectorKey(row, col, corner) + const mask = getEffectiveSectorMask(currentSectorKey) const isOnlyOne = mask === SECTOR_MASK_ONLY_1 const isNotOne = mask !== undefined && !sectorMaskAllows(mask, 1) - if (!isOnlyOne && !isNotOne) { - continue - } - - const relation = isOnlyOne ? 'different' : 'same' const [firstNeighbor, secondNeighbor] = getCornerOutsideNeighbors(row, col, corner) const firstInBounds = inBounds(firstNeighbor.row, firstNeighbor.col) const secondInBounds = inBounds(secondNeighbor.row, secondNeighbor.col) @@ -1083,6 +1208,20 @@ export const createColorSectorMaskPropagationRule = (): Rule => ({ const secondColor: SlitherCellColor | null = secondKey !== null ? getEffectiveCellColor(secondKey) : 'yellow' + if (firstColor !== null && secondColor !== null && (firstInBounds || secondInBounds)) { + const targetMask = firstColor === secondColor ? SECTOR_MASK_NOT_1 : SECTOR_MASK_ONLY_1 + if (rememberSectorMask(currentSectorKey, targetMask) && firstReason === null) { + const relation = firstColor === secondColor ? 'same' : 'different' + firstReason = `${formatSectorLabel(row, col, corner)} sees ${relation} outside-neighbor colors, so its sector count is narrowed` + } + } + + if (!isOnlyOne && !isNotOne) { + continue + } + + const relation = isOnlyOne ? 'different' : 'same' + if (firstColor === null && secondColor === null) { continue } @@ -1100,6 +1239,9 @@ export const createColorSectorMaskPropagationRule = (): Rule => ({ if (secondKey) { affectedCells.add(secondKey) } + if (firstReason === null) { + firstReason = `${formatSectorLabel(row, col, corner)} says the two outside-neighbor cells are ${relation}, so ${formatCellKeyLabel(firstKey)} is ${inferred}` + } continue } @@ -1112,24 +1254,38 @@ export const createColorSectorMaskPropagationRule = (): Rule => ({ if (firstKey) { affectedCells.add(firstKey) } + if (firstReason === null) { + firstReason = `${formatSectorLabel(row, col, corner)} says the two outside-neighbor cells are ${relation}, so ${formatCellKeyLabel(secondKey)} is ${inferred}` + } } } } } - if (decidedCellFills.size === 0) { + if (decidedCellFills.size === 0 && decidedSectorMasks.size === 0) { return null } - return { - message: `Color sector-mask propagation applied (${decidedCellFills.size} color update(s)).`, - diffs: [...decidedCellFills.entries()].map(([k, toFill]) => ({ + const diffs: RuleApplication['diffs'] = [ + ...[...decidedCellFills.entries()].map(([k, toFill]) => ({ kind: 'cell' as const, cellKey: k, fromFill: (puzzle.cells[k]?.fill ?? null) as string | null, toFill, })), + ...[...decidedSectorMasks.entries()].map(([k, toMask]) => ({ + kind: 'sector' as const, + sectorKey: k, + fromMask: puzzle.sectors[k]?.constraintsMask ?? 0, + toMask, + })), + ] + + return { + message: `${firstReason ?? 'Sector color relation propagated'} (${decidedCellFills.size} color update(s), ${decidedSectorMasks.size} sector update(s)).`, + diffs, affectedCells: [...affectedCells], + affectedSectors: [...affectedSectors], } }, }) diff --git a/src/domain/rules/slither/rules/colorAssumptionInference.ts b/src/domain/rules/slither/rules/colorAssumptionInference.ts index 21effcf..61f9492 100644 --- a/src/domain/rules/slither/rules/colorAssumptionInference.ts +++ b/src/domain/rules/slither/rules/colorAssumptionInference.ts @@ -8,11 +8,18 @@ import { type PuzzleIR, type SectorCorner, } from '../../../ir/types' -import { getCellNeighborKeys, isSlitherCellColor, oppositeSlitherCellColor, type SlitherCellColor } from './shared' -import { runTrialUntilFixpoint } from './trial' +import { + formatCellLabel, + getCellNeighborKeys, + getEdgeAdjacentCellKeys, + isSlitherCellColor, + oppositeSlitherCellColor, + type SlitherCellColor, +} from './shared' +import { runTrialUntilFixpoint, type TrialResult } from './trial' -const COLOR_ASSUMPTION_MAX_CANDIDATES = 120 -const COLOR_ASSUMPTION_MAX_TRIAL_STEPS = 120 +const COLOR_ASSUMPTION_MAX_CANDIDATES = 200 +const COLOR_ASSUMPTION_MAX_TRIAL_STEPS = 50 const COLOR_ASSUMPTION_MAX_MS = 2000 type ColorAssumptionInferenceOptions = { @@ -28,9 +35,15 @@ type ColorAssumptionCandidate = { score: number } +type ColorAssumptionCandidateSet = { + candidates: ColorAssumptionCandidate[] + rawCandidateCount: number + componentCount: number +} + const corners: SectorCorner[] = ['nw', 'ne', 'sw', 'se'] -const collectColorAssumptionCandidates = (puzzle: PuzzleIR, maxCandidates: number): ColorAssumptionCandidate[] => { +const collectRawColorAssumptionCandidates = (puzzle: PuzzleIR, maxCandidates: number): ColorAssumptionCandidate[] => { const candidates: ColorAssumptionCandidate[] = [] for (let row = 0; row < puzzle.rows; row += 1) { @@ -77,6 +90,107 @@ const collectColorAssumptionCandidates = (puzzle: PuzzleIR, maxCandidates: numbe .slice(0, maxCandidates) } +const compressColorAssumptionCandidates = ( + puzzle: PuzzleIR, + candidates: ColorAssumptionCandidate[], +): ColorAssumptionCandidate[] => { + type Parity = 0 | 1 + + const parent = new Map() + const rank = new Map() + const parityToParent = new Map() + + const ensureCell = (key: string): void => { + if (parent.has(key)) { + return + } + parent.set(key, key) + rank.set(key, 0) + parityToParent.set(key, 0) + } + + const find = (key: string): { root: string; parity: Parity } => { + ensureCell(key) + const currentParent = parent.get(key) + if (currentParent === undefined || currentParent === key) { + return { root: key, parity: 0 } + } + + const parentResult = find(currentParent) + const currentParity = parityToParent.get(key) ?? 0 + const compressedParity = (currentParity ^ parentResult.parity) as Parity + parent.set(key, parentResult.root) + parityToParent.set(key, compressedParity) + return { root: parentResult.root, parity: compressedParity } + } + + const union = (cellA: string, cellB: string, relation: Parity): void => { + const rootA = find(cellA) + const rootB = find(cellB) + if (rootA.root === rootB.root) { + return + } + + const mergedParity = (rootA.parity ^ rootB.parity ^ relation) as Parity + const rankA = rank.get(rootA.root) ?? 0 + const rankB = rank.get(rootB.root) ?? 0 + if (rankA < rankB) { + parent.set(rootA.root, rootB.root) + parityToParent.set(rootA.root, mergedParity) + return + } + + parent.set(rootB.root, rootA.root) + parityToParent.set(rootB.root, mergedParity) + if (rankA === rankB) { + rank.set(rootA.root, rankA + 1) + } + } + + for (let row = 0; row < puzzle.rows; row += 1) { + for (let col = 0; col < puzzle.cols; col += 1) { + ensureCell(cellKey(row, col)) + } + } + + for (const [edgeKeyValue, edgeState] of Object.entries(puzzle.edges)) { + const mark = edgeState?.mark ?? 'unknown' + if (mark !== 'line' && mark !== 'blank') { + continue + } + const adjacentCells = getEdgeAdjacentCellKeys(puzzle, edgeKeyValue) + if (adjacentCells.length !== 2) { + continue + } + union(adjacentCells[0], adjacentCells[1], mark === 'line' ? 1 : 0) + } + + const representatives = new Map() + for (const candidate of candidates) { + const { root } = find(candidate.cellKey) + const current = representatives.get(root) + if ( + current === undefined || + candidate.score > current.score || + (candidate.score === current.score && (candidate.row < current.row || (candidate.row === current.row && candidate.col < current.col))) + ) { + representatives.set(root, candidate) + } + } + + return [...representatives.values()].sort((a, b) => b.score - a.score || a.row - b.row || a.col - b.col) +} + +const collectColorAssumptionCandidates = (puzzle: PuzzleIR, maxCandidates: number): ColorAssumptionCandidateSet => { + const rawCandidates = collectRawColorAssumptionCandidates(puzzle, maxCandidates) + const candidates = compressColorAssumptionCandidates(puzzle, rawCandidates) + return { + candidates, + rawCandidateCount: rawCandidates.length, + componentCount: candidates.length, + } +} + const applyCellAssumption = (puzzle: PuzzleIR, key: string, toFill: SlitherCellColor): boolean => { const currentFill = puzzle.cells[key]?.fill if (isSlitherCellColor(currentFill)) { @@ -90,7 +204,7 @@ const applyCellAssumption = (puzzle: PuzzleIR, key: string, toFill: SlitherCellC } const describeCandidate = (candidate: ColorAssumptionCandidate): string => - `candidate=cell(${candidate.row}, ${candidate.col})` + formatCellLabel(candidate.row, candidate.col) const getCellAssumptionDiff = ( puzzle: PuzzleIR, @@ -105,6 +219,45 @@ const getCellAssumptionDiff = ( }, ] +const immediateContradictionResult = (puzzle: PuzzleIR): TrialResult => ({ + contradiction: true, + timedOut: false, + exhausted: false, + puzzle, + stepsRun: 0, + elapsedMs: 0, + contradictionReason: { + kind: 'color-edge', + message: 'setup contradiction: the assumed color is already incompatible with the current cell state', + }, +}) + +const formatElapsedMs = (elapsedMs: number): string => `${Math.max(0, Math.round(elapsedMs))} ms` + +const formatTrialStepCount = (stepsRun: number): string => + `${stepsRun} trial ${stepsRun === 1 ? 'step' : 'steps'}` + +const describeTrialBranch = (color: SlitherCellColor, result: TrialResult): string => + `${color} branch: ${result.stepsRun} ${result.stepsRun === 1 ? 'step' : 'steps'}, ${formatElapsedMs(result.elapsedMs)}` + +const describeContradiction = (result: TrialResult): string => + result.contradictionReason?.message ?? 'a contradiction' + +const deriveProbeBudgets = (maxTrialSteps: number): number[] => { + const cappedMax = Math.max(1, maxTrialSteps) + const budgets = [24, 96, 384, cappedMax] + .map((budget) => Math.min(budget, cappedMax)) + .filter((budget, index, arr) => arr.indexOf(budget) === index) + return budgets.length > 0 ? budgets : [cappedMax] +} + +const describeProbeBranch = (color: SlitherCellColor, result: TrialResult): string => { + if (result.contradiction) { + return describeTrialBranch(color, result) + } + return `${color} branch: unresolved after ${result.stepsRun} ${result.stepsRun === 1 ? 'step' : 'steps'}, ${formatElapsedMs(result.elapsedMs)}` +} + export const createColorAssumptionInferenceRule = ( getDeterministicRules: () => Rule[], options: ColorAssumptionInferenceOptions = {}, @@ -113,60 +266,56 @@ export const createColorAssumptionInferenceRule = ( name: 'Color Assumption Inference', apply: (puzzle: PuzzleIR): RuleApplication | null => { const deterministicRules = getDeterministicRules() - const candidates = collectColorAssumptionCandidates( + const candidateSet = collectColorAssumptionCandidates( puzzle, options.maxCandidates ?? COLOR_ASSUMPTION_MAX_CANDIDATES, ) + const { candidates, rawCandidateCount, componentCount } = candidateSet if (candidates.length === 0) { return null } const deadlineMs = Date.now() + (options.maxMs ?? COLOR_ASSUMPTION_MAX_MS) - for (const candidate of candidates) { - if (Date.now() > deadlineMs) { - break - } + const maxTrialSteps = options.maxTrialSteps ?? COLOR_ASSUMPTION_MAX_TRIAL_STEPS + const probeBudgets = deriveProbeBudgets(maxTrialSteps) - const greenBranch = clonePuzzle(puzzle) - const yellowBranch = clonePuzzle(puzzle) - const greenSetupOk = applyCellAssumption(greenBranch, candidate.cellKey, 'green') - const yellowSetupOk = applyCellAssumption(yellowBranch, candidate.cellKey, 'yellow') - - const greenResult = greenSetupOk - ? runTrialUntilFixpoint( - greenBranch, - deterministicRules, - options.maxTrialSteps ?? COLOR_ASSUMPTION_MAX_TRIAL_STEPS, - deadlineMs, - ) - : { contradiction: true, timedOut: false, exhausted: false, puzzle: greenBranch } - const yellowResult = yellowSetupOk - ? runTrialUntilFixpoint( - yellowBranch, - deterministicRules, - options.maxTrialSteps ?? COLOR_ASSUMPTION_MAX_TRIAL_STEPS, - deadlineMs, - ) - : { contradiction: true, timedOut: false, exhausted: false, puzzle: yellowBranch } - - if (greenResult.timedOut || yellowResult.timedOut) { - break - } - if (greenResult.exhausted || yellowResult.exhausted) { - continue - } - if (greenResult.contradiction === yellowResult.contradiction) { - continue - } + for (const budget of probeBudgets) { + let componentsSearched = 0 + for (const candidate of candidates) { + if (Date.now() > deadlineMs) { + return null + } + componentsSearched += 1 + + const greenBranch = clonePuzzle(puzzle) + const yellowBranch = clonePuzzle(puzzle) + const greenSetupOk = applyCellAssumption(greenBranch, candidate.cellKey, 'green') + const yellowSetupOk = applyCellAssumption(yellowBranch, candidate.cellKey, 'yellow') - const failingColor: SlitherCellColor = greenResult.contradiction ? 'green' : 'yellow' - const inferredColor = oppositeSlitherCellColor(failingColor) - const diffs = getCellAssumptionDiff(puzzle, candidate, inferredColor) + const greenResult = greenSetupOk + ? runTrialUntilFixpoint(greenBranch, deterministicRules, budget, deadlineMs) + : immediateContradictionResult(greenBranch) + const yellowResult = yellowSetupOk + ? runTrialUntilFixpoint(yellowBranch, deterministicRules, budget, deadlineMs) + : immediateContradictionResult(yellowBranch) - return { - message: `Color assumption ${describeCandidate(candidate)} result=contradiction: ${failingColor} fails, so ${candidate.cellKey}=${inferredColor}.`, - diffs, - affectedCells: [candidate.cellKey], + if (greenResult.timedOut || yellowResult.timedOut) { + return null + } + if (greenResult.contradiction === yellowResult.contradiction) { + continue + } + + const failingColor: SlitherCellColor = greenResult.contradiction ? 'green' : 'yellow' + const inferredColor = oppositeSlitherCellColor(failingColor) + const failingResult = failingColor === 'green' ? greenResult : yellowResult + const diffs = getCellAssumptionDiff(puzzle, candidate, inferredColor) + + return { + message: `Assume ${describeCandidate(candidate)} is ${failingColor}; after ${formatTrialStepCount(failingResult.stepsRun)} / ${formatElapsedMs(failingResult.elapsedMs)}, deterministic propagation reaches ${describeContradiction(failingResult)}, so ${describeCandidate(candidate)} must be ${inferredColor}. Searched ${componentsSearched} candidate ${componentsSearched === 1 ? 'component' : 'components'} from ${rawCandidateCount} candidate ${rawCandidateCount === 1 ? 'cell' : 'cells'} at probe budget ${budget}; compressed to ${componentCount} ${componentCount === 1 ? 'component' : 'components'}; ${describeProbeBranch('green', greenResult)}; ${describeProbeBranch('yellow', yellowResult)}.`, + diffs, + affectedCells: [candidate.cellKey], + } } } diff --git a/src/domain/rules/slither/rules/core.ts b/src/domain/rules/slither/rules/core.ts index 3401ceb..c7d83e2 100644 --- a/src/domain/rules/slither/rules/core.ts +++ b/src/domain/rules/slither/rules/core.ts @@ -1,6 +1,7 @@ import { getCellEdgeKeys, getVertexIncidentEdges, parseCellKey, parseEdgeKey } from '../../../ir/keys' import type { EdgeMark, PuzzleIR } from '../../../ir/types' import type { Rule, RuleApplication } from '../../types' +import { formatCellLabel, formatEdgeLabel, formatVertexLabel } from './shared' const numberClueCellKeysCache = new WeakMap() @@ -26,6 +27,7 @@ export const createCellCountRule = (): Rule => ({ const decidedEdges = new Map() const affectedCells = new Set() let firstExample: string | null = null + let firstReason: string | null = null for (const key of getNumberClueCellKeys(puzzle)) { const cell = puzzle.cells[key] @@ -50,10 +52,13 @@ export const createCellCountRule = (): Rule => ({ } let toMark: EdgeMark | null = null + let reason: string | null = null if (lineCount === clue) { toMark = 'blank' + reason = `the clue already has ${clue} line(s), so every remaining unknown edge is blank` } else if (lineCount + unknownEdges.length === clue) { toMark = 'line' + reason = `all remaining unknown edge(s) are needed to reach clue ${clue}, so they are lines` } if (toMark === null) continue @@ -67,7 +72,10 @@ export const createCellCountRule = (): Rule => ({ if (addedAny) { affectedCells.add(key) - if (firstExample === null) firstExample = `(${row}, ${col})` + if (firstExample === null) { + firstExample = formatCellLabel(row, col) + firstReason = reason + } } } @@ -77,7 +85,7 @@ export const createCellCountRule = (): Rule => ({ return { message: firstExample !== null - ? `Cell ${firstExample}${extra > 0 ? ` and ${extra} other(s)` : ''}: clue completion applied.` + ? `Cell ${firstExample}${extra > 0 ? ` and ${extra} other(s)` : ''}: ${firstReason}.` : 'Cell clue completion applied.', diffs: [...decidedEdges.entries()].map(([edgeKey, to]) => ({ kind: 'edge' as const, @@ -96,6 +104,7 @@ export const createVertexDegreeRule = (): Rule => ({ apply: (puzzle: PuzzleIR): RuleApplication | null => { const decidedEdges = new Map() let firstVertex: string | null = null + let firstReason: string | null = null for (let r = 0; r <= puzzle.rows; r += 1) { for (let c = 0; c <= puzzle.cols; c += 1) { @@ -119,16 +128,20 @@ export const createVertexDegreeRule = (): Rule => ({ let toMark: EdgeMark | null = null let edgesToDecide: string[] = [] + let reason: string | null = null if (lineCount === 2) { toMark = 'blank' edgesToDecide = unknownEdges + reason = 'it already has degree 2, so all other incident edges are blank' } else if (lineCount === 1 && unknownEdges.length === 1) { toMark = 'line' edgesToDecide = [unknownEdges[0]] + reason = 'one line must continue through the only remaining unknown edge' } else if (lineCount === 0 && unknownEdges.length === 1) { toMark = 'blank' edgesToDecide = [unknownEdges[0]] + reason = 'using the only remaining unknown edge would leave a dead-end degree 1 vertex' } if (toMark === null) continue @@ -142,7 +155,8 @@ export const createVertexDegreeRule = (): Rule => ({ } if (addedAny && firstVertex === null) { - firstVertex = `(${r}, ${c})` + firstVertex = formatVertexLabel(r, c) + firstReason = reason } } } @@ -151,7 +165,7 @@ export const createVertexDegreeRule = (): Rule => ({ return { message: - firstVertex !== null ? `Vertex ${firstVertex}: degree rule applied.` : 'Vertex degree rule applied.', + firstVertex !== null ? `Vertex ${firstVertex}: ${firstReason}.` : 'Vertex degree rule applied.', diffs: [...decidedEdges.entries()].map(([edgeKey, to]) => ({ kind: 'edge' as const, edgeKey, @@ -215,7 +229,7 @@ export const createPreventPrematureLoopRule = (): Rule => ({ } decidedEdges.set(edgeKeyValue, 'blank') if (firstExample === null) { - firstExample = edgeKeyValue + firstExample = formatEdgeLabel(edgeKeyValue) } } @@ -226,8 +240,8 @@ export const createPreventPrematureLoopRule = (): Rule => ({ return { message: firstExample !== null - ? `Edge ${firstExample} would close a premature loop, so matching edges are blanked.` - : 'Edges that would close a premature loop are blanked.', + ? `${firstExample} would close the current path into a smaller loop, so it must be blank.` + : 'Edges that would close the current path into a smaller loop are blank.', diffs: [...decidedEdges.entries()].map(([edgeKeyValue, to]) => ({ kind: 'edge' as const, edgeKey: edgeKeyValue, diff --git a/src/domain/rules/slither/rules/patterns.ts b/src/domain/rules/slither/rules/patterns.ts index 2dcbed6..06a991a 100644 --- a/src/domain/rules/slither/rules/patterns.ts +++ b/src/domain/rules/slither/rules/patterns.ts @@ -1,7 +1,7 @@ import { cellKey, edgeKey } from '../../../ir/keys' import type { EdgeMark, PuzzleIR } from '../../../ir/types' import type { Rule, RuleApplication } from '../../types' -import { isClueThree } from './shared' +import { formatCellRunLabel, isClueThree } from './shared' export const createContiguousThreeRunBoundariesRule = (): Rule => ({ id: 'contiguous-three-run-boundaries', @@ -59,7 +59,7 @@ export const createContiguousThreeRunBoundariesRule = (): Rule => ({ if (runAddedAny) { for (let col = cStart; col <= cEnd; col += 1) allAffectedCells.add(cellKey(r, col)) - if (firstExample === null) firstExample = `row ${r} cols ${cStart}-${cEnd}` + if (firstExample === null) firstExample = formatCellRunLabel('row', r, cStart, cEnd) } } } @@ -99,7 +99,7 @@ export const createContiguousThreeRunBoundariesRule = (): Rule => ({ if (runAddedAny) { for (let row = rStart; row <= rEnd; row += 1) allAffectedCells.add(cellKey(row, c)) - if (firstExample === null) firstExample = `col ${c} rows ${rStart}-${rEnd}` + if (firstExample === null) firstExample = formatCellRunLabel('col', c, rStart, rEnd) } } } @@ -109,8 +109,8 @@ export const createContiguousThreeRunBoundariesRule = (): Rule => ({ return { message: firstExample !== null - ? `Contiguous 3-run pattern forced boundary lines and same-direction extension blanks (e.g., ${firstExample}).` - : 'Contiguous 3-run pattern forced boundary lines and same-direction extension blanks.', + ? `Contiguous 3-run at ${firstExample}: the 3-clues need the run boundary lines, so straight extensions outside the run are blank.` + : 'Contiguous 3-run: the 3-clues need the run boundary lines, so straight extensions outside the run are blank.', diffs: [...decidedEdges.entries()].map(([k, to]) => ({ kind: 'edge' as const, edgeKey: k, @@ -177,7 +177,7 @@ export const createDiagonalAdjacentThreeOuterCornersRule = (): Rule => ({ if (decidedEdges.size === 0) return null return { - message: 'Diagonal adjacent 3s force outer-corner boundary edges to be lines.', + message: 'Diagonal adjacent 3s force their outside corner edges to be lines; otherwise one of the 3-clues cannot reach three lines.', diffs: [...decidedEdges.entries()].map(([k, to]) => ({ kind: 'edge' as const, edgeKey: k, @@ -188,3 +188,133 @@ export const createDiagonalAdjacentThreeOuterCornersRule = (): Rule => ({ } }, }) + +const getNumberClueValue = (puzzle: PuzzleIR, row: number, col: number): number | null => { + const clue = puzzle.cells[cellKey(row, col)]?.clue + return clue?.kind === 'number' && clue.value !== '?' ? Number(clue.value) : null +} + +export const createAdjacentTwoThreeOppositeCrossRule = (): Rule => ({ + id: 'adjacent-two-three-opposite-cross', + name: 'Adjacent 2-3 Opposite Cross', + apply: (puzzle: PuzzleIR): RuleApplication | null => { + const decidedEdges = new Map() + const affectedCells = new Set() + let firstExample: string | null = null + + const decideUnknownEdge = (key: string, to: EdgeMark): boolean => { + if (!puzzle.edges[key]) { + return false + } + if ((puzzle.edges[key]?.mark ?? 'unknown') !== 'unknown') { + return false + } + if (decidedEdges.has(key)) { + return false + } + decidedEdges.set(key, to) + return true + } + + const verticalEdge = (row: number, col: number): string => edgeKey([row, col], [row + 1, col]) + const horizontalEdge = (row: number, col: number): string => edgeKey([row, col], [row, col + 1]) + + const applyPair = ( + twoRow: number, + twoCol: number, + threeRow: number, + threeCol: number, + rowDelta: number, + colDelta: number, + ): void => { + let twoOpposite: string + let threeOpposite: string + const extensionEdges: string[] = [] + + if (rowDelta === 0) { + const sharedCol = colDelta === 1 ? twoCol + 1 : twoCol + twoOpposite = verticalEdge(twoRow, colDelta === 1 ? twoCol : twoCol + 1) + threeOpposite = verticalEdge(threeRow, colDelta === 1 ? threeCol + 1 : threeCol) + if (twoRow > 0) { + extensionEdges.push(verticalEdge(twoRow - 1, sharedCol)) + } + if (twoRow + 2 <= puzzle.rows) { + extensionEdges.push(verticalEdge(twoRow + 1, sharedCol)) + } + } else { + const sharedRow = rowDelta === 1 ? twoRow + 1 : twoRow + twoOpposite = horizontalEdge(rowDelta === 1 ? twoRow : twoRow + 1, twoCol) + threeOpposite = horizontalEdge(rowDelta === 1 ? threeRow + 1 : threeRow, threeCol) + if (twoCol > 0) { + extensionEdges.push(horizontalEdge(sharedRow, twoCol - 1)) + } + if (twoCol + 2 <= puzzle.cols) { + extensionEdges.push(horizontalEdge(sharedRow, twoCol + 1)) + } + } + + if ((puzzle.edges[twoOpposite]?.mark ?? 'unknown') !== 'blank') { + return + } + + let addedAny = false + addedAny = decideUnknownEdge(threeOpposite, 'line') || addedAny + for (const extensionEdge of extensionEdges) { + addedAny = decideUnknownEdge(extensionEdge, 'blank') || addedAny + } + + if (addedAny) { + affectedCells.add(cellKey(twoRow, twoCol)) + affectedCells.add(cellKey(threeRow, threeCol)) + if (firstExample === null) { + firstExample = `${cellKey(twoRow, twoCol)} and ${cellKey(threeRow, threeCol)}` + } + } + } + + const inspectAdjacentCells = ( + row: number, + col: number, + neighborRow: number, + neighborCol: number, + rowDelta: number, + colDelta: number, + ): void => { + const currentClue = getNumberClueValue(puzzle, row, col) + const neighborClue = getNumberClueValue(puzzle, neighborRow, neighborCol) + if (currentClue === 2 && neighborClue === 3) { + applyPair(row, col, neighborRow, neighborCol, rowDelta, colDelta) + } + if (currentClue === 3 && neighborClue === 2) { + applyPair(neighborRow, neighborCol, row, col, -rowDelta, -colDelta) + } + } + + for (let row = 0; row < puzzle.rows; row += 1) { + for (let col = 0; col < puzzle.cols; col += 1) { + if (col + 1 < puzzle.cols) { + inspectAdjacentCells(row, col, row, col + 1, 0, 1) + } + if (row + 1 < puzzle.rows) { + inspectAdjacentCells(row, col, row + 1, col, 1, 0) + } + } + } + + if (decidedEdges.size === 0) return null + + return { + message: + firstExample !== null + ? `Adjacent 2-3 at ${firstExample}: the crossed edge opposite the shared side of the 2 forces the 3's opposite edge to be a line and the shared-side extensions to be blank.` + : "Adjacent 2-3: a crossed opposite edge on the 2 forces the 3's opposite edge and shared-side extensions.", + diffs: [...decidedEdges.entries()].map(([k, to]) => ({ + kind: 'edge' as const, + edgeKey: k, + from: 'unknown' as const, + to, + })), + affectedCells: [...affectedCells], + } + }, +}) diff --git a/src/domain/rules/slither/rules/sectorInference.ts b/src/domain/rules/slither/rules/sectorInference.ts index 6e0899c..b9aaaca 100644 --- a/src/domain/rules/slither/rules/sectorInference.ts +++ b/src/domain/rules/slither/rules/sectorInference.ts @@ -18,7 +18,7 @@ import { sectorMaskIntersect, } from '../../../ir/types' import type { Rule, RuleApplication } from '../../types' -import { maskForExactLineCount } from './shared' +import { formatSectorLabel, maskForExactLineCount } from './shared' const inferSectorMaskByVertex = ( puzzle: PuzzleIR, @@ -144,6 +144,7 @@ export const createApplySectorsInference = (): Rule => ({ const diffs: RuleApplication['diffs'] = [] const affectedCells = new Set() const affectedSectors: string[] = [] + let firstSector: string | null = null for (let r = 0; r < puzzle.rows; r += 1) { for (let c = 0; c < puzzle.cols; c += 1) { for (const corner of corners) { @@ -162,6 +163,9 @@ export const createApplySectorsInference = (): Rule => ({ }) affectedCells.add(cellKey(r, c)) affectedSectors.push(key) + if (firstSector === null) { + firstSector = formatSectorLabel(r, c, corner) + } } } } @@ -169,7 +173,10 @@ export const createApplySectorsInference = (): Rule => ({ return null } return { - message: 'Apply Sectors from Vertex: inferred corner sector constraints from current edges.', + message: + firstSector !== null + ? `Sector ${firstSector}: existing edge, vertex, and clue constraints narrow the allowed corner line counts.` + : 'Existing edge, vertex, and clue constraints narrow allowed corner sector line counts.', diffs, affectedCells: [...affectedCells], affectedSectors, diff --git a/src/domain/rules/slither/rules/sectorParityInference.ts b/src/domain/rules/slither/rules/sectorParityInference.ts index c9141a9..07c5269 100644 --- a/src/domain/rules/slither/rules/sectorParityInference.ts +++ b/src/domain/rules/slither/rules/sectorParityInference.ts @@ -6,10 +6,11 @@ import { type EdgeMark, type PuzzleIR, } from '../../../ir/types' -import { applyEdgeAssumption, runTrialUntilFixpoint } from './trial' +import { applyEdgeAssumption, runTrialUntilFixpoint, type TrialResult } from './trial' +import { formatEdgeLabel, formatSectorKeyLabel } from './shared' -const SECTOR_PARITY_MAX_CANDIDATES = 160 -const SECTOR_PARITY_MAX_TRIAL_STEPS = 120 +const SECTOR_PARITY_MAX_CANDIDATES = 200 +const SECTOR_PARITY_MAX_TRIAL_STEPS = 50 const SECTOR_PARITY_MAX_MS = 2000 type SectorParityInferenceOptions = { @@ -31,6 +32,27 @@ type SectorParityBranch = { diffs: RuleApplication['diffs'] } +const immediateContradictionResult = (puzzle: PuzzleIR): TrialResult => ({ + contradiction: true, + timedOut: false, + exhausted: false, + puzzle, + stepsRun: 0, + elapsedMs: 0, + contradictionReason: { + kind: 'sector-mask', + message: 'setup contradiction: this parity branch is already incompatible with the current edge state', + }, +}) + +const deriveProbeBudgets = (maxTrialSteps: number): number[] => { + const cappedMax = Math.max(1, maxTrialSteps) + const budgets = [24, 96, 384, cappedMax] + .map((budget) => Math.min(budget, cappedMax)) + .filter((budget, index, arr) => arr.indexOf(budget) === index) + return budgets.length > 0 ? budgets : [cappedMax] +} + const collectSectorParityCandidates = (puzzle: PuzzleIR, maxCandidates: number): SectorParityCandidate[] => { const candidates: SectorParityCandidate[] = [] @@ -103,21 +125,28 @@ const collectSharedEdgeDiffs = (basePuzzle: PuzzleIR, branchA: PuzzleIR, branchB const describeBranch = (diffs: RuleApplication['diffs']): string => diffs .filter((diff): diff is Extract<(typeof diffs)[number], { kind: 'edge' }> => diff.kind === 'edge') - .map((diff) => `${diff.edgeKey}=${diff.to}`) + .map((diff) => `${formatEdgeLabel(diff.edgeKey)} ${diff.to}`) .join(', ') const summarizeFixedDiffs = (diffs: RuleApplication['diffs']): string => { const edgeDiffs = diffs.filter((diff): diff is Extract<(typeof diffs)[number], { kind: 'edge' }> => diff.kind === 'edge') if (edgeDiffs.length <= 3) { - return `fixed ${edgeDiffs.map((diff) => `${diff.edgeKey}=${diff.to}`).join(', ')}` + return `fixed ${edgeDiffs.map((diff) => `${formatEdgeLabel(diff.edgeKey)} ${diff.to}`).join(', ')}` } const preview = edgeDiffs .slice(0, 3) - .map((diff) => `${diff.edgeKey}=${diff.to}`) + .map((diff) => `${formatEdgeLabel(diff.edgeKey)} ${diff.to}`) .join(', ') return `fixed ${edgeDiffs.length} edges (${preview}, ...)` } +const describeTrialBranch = (label: string, result: TrialResult): string => { + if (result.contradiction) { + return `${label} branch contradicted after ${result.stepsRun} ${result.stepsRun === 1 ? 'step' : 'steps'}` + } + return `${label} branch unresolved after ${result.stepsRun} ${result.stepsRun === 1 ? 'step' : 'steps'}` +} + export const createSectorParityInferenceRule = ( getDeterministicRules: () => Rule[], options: SectorParityInferenceOptions = {}, @@ -135,62 +164,66 @@ export const createSectorParityInferenceRule = ( } const deadlineMs = Date.now() + (options.maxMs ?? SECTOR_PARITY_MAX_MS) - for (const candidate of candidates) { - if (Date.now() > deadlineMs) { - break - } + const maxTrialSteps = options.maxTrialSteps ?? SECTOR_PARITY_MAX_TRIAL_STEPS + const probeBudgets = deriveProbeBudgets(maxTrialSteps) + for (const budget of probeBudgets) { + for (const candidate of candidates) { + if (Date.now() > deadlineMs) { + return null + } - const lineBranch = buildParityBranch(puzzle, candidate.edgeA, candidate.edgeB, 'line') - const blankBranch = buildParityBranch(puzzle, candidate.edgeA, candidate.edgeB, 'blank') - - const lineResult = lineBranch.info.setupOk - ? runTrialUntilFixpoint( - lineBranch.branch, - deterministicRules, - options.maxTrialSteps ?? SECTOR_PARITY_MAX_TRIAL_STEPS, - deadlineMs, - ) - : { contradiction: true, timedOut: false, exhausted: false, puzzle: lineBranch.branch } - const blankResult = blankBranch.info.setupOk - ? runTrialUntilFixpoint( - blankBranch.branch, - deterministicRules, - options.maxTrialSteps ?? SECTOR_PARITY_MAX_TRIAL_STEPS, - deadlineMs, - ) - : { contradiction: true, timedOut: false, exhausted: false, puzzle: blankBranch.branch } - - if (lineResult.timedOut || blankResult.timedOut) { - break - } - if (lineResult.exhausted || blankResult.exhausted) { - continue - } + const lineBranch = buildParityBranch(puzzle, candidate.edgeA, candidate.edgeB, 'line') + const blankBranch = buildParityBranch(puzzle, candidate.edgeA, candidate.edgeB, 'blank') + + const lineResult = lineBranch.info.setupOk + ? runTrialUntilFixpoint( + lineBranch.branch, + deterministicRules, + budget, + deadlineMs, + ) + : immediateContradictionResult(lineBranch.branch) + const blankResult = blankBranch.info.setupOk + ? runTrialUntilFixpoint( + blankBranch.branch, + deterministicRules, + budget, + deadlineMs, + ) + : immediateContradictionResult(blankBranch.branch) + + if (lineResult.timedOut || blankResult.timedOut) { + return null + } + + const candidateLabel = formatSectorKeyLabel(candidate.sectorKey) + if (lineResult.contradiction !== blankResult.contradiction) { + const contradictionBranch = lineResult.contradiction ? lineBranch.info : blankBranch.info + const survivingBranch = lineResult.contradiction ? blankBranch.info : lineBranch.info + + return { + message: `Sector ${candidateLabel} cannot have exactly one line. The branch ${describeBranch(contradictionBranch.diffs)} contradicts the puzzle at probe budget ${budget}, so the other parity branch is forced and ${summarizeFixedDiffs(survivingBranch.diffs)}. ${describeTrialBranch('line', lineResult)}; ${describeTrialBranch('blank', blankResult)}.`, + diffs: survivingBranch.diffs, + affectedCells: [cellKey(candidate.row, candidate.col)], + affectedSectors: [candidate.sectorKey], + } + } + if (lineResult.contradiction && blankResult.contradiction) { + continue + } - const candidateLabel = `candidate=sector-not-one(${candidate.sectorKey})` - if (lineResult.contradiction !== blankResult.contradiction) { - const contradictionBranch = lineResult.contradiction ? lineBranch.info : blankBranch.info - const survivingBranch = lineResult.contradiction ? blankBranch.info : lineBranch.info + const diffs = collectSharedEdgeDiffs(puzzle, lineResult.puzzle, blankResult.puzzle) + if (diffs.length === 0) { + continue + } return { - message: `Sector parity inference ${candidateLabel} result=contradiction: branch ${describeBranch(contradictionBranch.diffs)} fails, so ${summarizeFixedDiffs(survivingBranch.diffs)}.`, - diffs: survivingBranch.diffs, + message: `Sector ${candidateLabel} cannot have exactly one line. Both parity branches lead to the same consequence at probe budget ${budget}, so ${summarizeFixedDiffs(diffs)}. ${describeTrialBranch('line', lineResult)}; ${describeTrialBranch('blank', blankResult)}.`, + diffs, affectedCells: [cellKey(candidate.row, candidate.col)], affectedSectors: [candidate.sectorKey], } } - - const diffs = collectSharedEdgeDiffs(puzzle, lineResult.puzzle, blankResult.puzzle) - if (diffs.length === 0) { - continue - } - - return { - message: `Sector parity inference ${candidateLabel} result=shared-consequence: both branches agree and ${summarizeFixedDiffs(diffs)}.`, - diffs, - affectedCells: [cellKey(candidate.row, candidate.col)], - affectedSectors: [candidate.sectorKey], - } } return null diff --git a/src/domain/rules/slither/rules/sectorPropagation.ts b/src/domain/rules/slither/rules/sectorPropagation.ts index b9af542..4e57c15 100644 --- a/src/domain/rules/slither/rules/sectorPropagation.ts +++ b/src/domain/rules/slither/rules/sectorPropagation.ts @@ -21,6 +21,7 @@ import { type VertexCandidate, } from '../../../ir/types' import type { Rule, RuleApplication } from '../../types' +import { formatCellLabel, formatSectorLabel, formatVertexLabel } from './shared' const CORNERS: SectorCorner[] = ['nw', 'ne', 'sw', 'se'] @@ -165,7 +166,8 @@ export const createVertexCandidateEdgePruningRule = (): Rule => ({ } return { - message: 'Vertex candidate pruning removed impossible vertex states and propagated forced edges.', + message: + 'Vertex candidate pruning removed degree-0/degree-2 states that conflict with known edges, then forced edges shared by every remaining state.', diffs, affectedCells: [], } @@ -339,7 +341,8 @@ export const createClueVertexCandidateCombinationPruningRule = (): Rule => ({ } return { - message: 'Clue vertex-candidate combinations pruned impossible corner states.', + message: + 'Only clue-compatible corner combinations survive, so unsupported vertex states and sector counts are removed.', diffs, affectedCells: [...affectedCells], affectedSectors: [...affectedSectors], @@ -418,7 +421,8 @@ export const createSectorDiagonalSharedVertexPropagationRule = (): Rule => ({ } return { - message: 'Diagonal shared-vertex sectors propagate corner constraints to opposite diagonal sectors.', + message: + 'Diagonal sectors sharing a vertex must agree on compatible corner counts, so the opposite sector constraint is narrowed.', diffs, affectedCells: [...affectedCells], affectedSectors: [...affectedSectors], @@ -434,6 +438,7 @@ export const createSectorClueOneThreeIntraCellPropagationRule = (): Rule => ({ const affectedCells = new Set() const affectedSectors = new Set() let firstExample: string | null = null + let firstReason: string | null = null for (let r = 0; r < puzzle.rows; r += 1) { for (let c = 0; c < puzzle.cols; c += 1) { @@ -464,7 +469,13 @@ export const createSectorClueOneThreeIntraCellPropagationRule = (): Rule => ({ decidedEdges.set(edge, toMark) affectedCells.add(cellKey(r, c)) affectedSectors.add(sk) - if (firstExample === null) firstExample = `(${r}, ${c}, ${corner})` + if (firstExample === null) { + firstExample = formatSectorLabel(r, c, corner) + firstReason = + clueValue === 1 + ? 'this clue-1 sector already has exactly one line, so the opposite cell edges are blank' + : 'this clue-3 sector already has exactly one line, so the opposite cell edges must be lines' + } } } } @@ -477,7 +488,7 @@ export const createSectorClueOneThreeIntraCellPropagationRule = (): Rule => ({ return { message: firstExample !== null - ? `Cell ${firstExample}${extra > 0 ? ` and ${extra} other(s)` : ''}: clue-1/3 onlyOne forces opposite cell edges.` + ? `Sector ${firstExample}${extra > 0 ? ` and ${extra} other(s)` : ''}: ${firstReason}.` : 'Clue-1/3 onlyOne opposite edges applied.', diffs: [...decidedEdges.entries()].map(([edgeKey, to]) => ({ kind: 'edge' as const, @@ -499,6 +510,7 @@ export const createVertexOnlyOneNonSectorBalanceRule = (): Rule => ({ const affectedCells = new Set() const affectedSectors = new Set() let firstExample: string | null = null + let firstReason: string | null = null const { rows, cols } = puzzle @@ -537,7 +549,10 @@ export const createVertexOnlyOneNonSectorBalanceRule = (): Rule => ({ decidedEdges.set(forcedEdge, 'line') affectedCells.add(cellKey(row, col)) affectedSectors.add(sk) - if (firstExample === null) firstExample = `(${vr}, ${vc})` + if (firstExample === null) { + firstExample = formatVertexLabel(vr, vc) + firstReason = 'an exactly-one sector uses one line at this vertex, so the only outside edge must complete degree 2' + } } continue } @@ -572,7 +587,13 @@ export const createVertexOnlyOneNonSectorBalanceRule = (): Rule => ({ decidedEdges.set(unknownEdge, toMark) affectedCells.add(cellKey(row, col)) affectedSectors.add(sk) - if (firstExample === null) firstExample = `(${vr}, ${vc})` + if (firstExample === null) { + firstExample = formatVertexLabel(vr, vc) + firstReason = + toMark === 'line' + ? 'the outside sector balance needs one more line at this vertex' + : 'the outside sector balance already has its needed line, so the other outside edge is blank' + } } } } @@ -583,7 +604,7 @@ export const createVertexOnlyOneNonSectorBalanceRule = (): Rule => ({ return { message: firstExample !== null - ? `Vertex ${firstExample}: onlyOne non-sector balance applied.` + ? `Vertex ${firstExample}: ${firstReason}.` : 'Vertex onlyOne non-sector balance applied.', diffs: [...decidedEdges.entries()].map(([edgeKey, to]) => ({ kind: 'edge' as const, @@ -605,6 +626,7 @@ export const createSectorConstraintEdgePropagationRule = (): Rule => ({ const affectedCells = new Set() const affectedSectors = new Set() let firstExample: string | null = null + let firstReason: string | null = null for (let r = 0; r < puzzle.rows; r += 1) { for (let c = 0; c < puzzle.cols; c += 1) { @@ -624,28 +646,35 @@ export const createSectorConstraintEdgePropagationRule = (): Rule => ({ let toMark: EdgeMark | null = null let edgesToDecide: string[] = [] + let reason: string | null = null if (mask === SECTOR_MASK_ONLY_2) { toMark = 'line' edgesToDecide = unknownEdges + reason = 'the sector must contain two lines, so every unknown sector edge is a line' } else if (mask === SECTOR_MASK_ONLY_0) { toMark = 'blank' edgesToDecide = unknownEdges + reason = 'the sector must contain zero lines, so every unknown sector edge is blank' } else if (mask === SECTOR_MASK_ONLY_1) { if (lineCount === 1 && blankCount === 0 && unknownEdges.length === 1) { toMark = 'blank' edgesToDecide = [unknownEdges[0]] + reason = 'the sector already has its one line, so the remaining sector edge is blank' } else if (blankCount === 1 && lineCount === 0 && unknownEdges.length === 1) { toMark = 'line' edgesToDecide = [unknownEdges[0]] + reason = 'the sector needs exactly one line, so the remaining sector edge is a line' } } else if (mask === SECTOR_MASK_NOT_1) { if (lineCount === 1 && blankCount === 0 && unknownEdges.length === 1) { toMark = 'line' edgesToDecide = [unknownEdges[0]] + reason = 'the sector cannot have exactly one line, so the remaining sector edge is also a line' } else if (blankCount === 1 && lineCount === 0 && unknownEdges.length === 1) { toMark = 'blank' edgesToDecide = [unknownEdges[0]] + reason = 'the sector cannot have exactly one line, so the remaining sector edge is also blank' } } @@ -662,7 +691,10 @@ export const createSectorConstraintEdgePropagationRule = (): Rule => ({ if (addedAny) { affectedCells.add(cellKey(r, c)) affectedSectors.add(key) - if (firstExample === null) firstExample = `(${r}, ${c}, ${corner})` + if (firstExample === null) { + firstExample = formatSectorLabel(r, c, corner) + firstReason = reason + } } } } @@ -674,7 +706,7 @@ export const createSectorConstraintEdgePropagationRule = (): Rule => ({ return { message: firstExample !== null - ? `Sector ${firstExample}${extra > 0 ? ` and ${extra} other(s)` : ''}: constraint propagated to edges.` + ? `Sector ${firstExample}${extra > 0 ? ` and ${extra} other(s)` : ''}: ${firstReason}.` : 'Sector constraint edge propagation applied.', diffs: [...decidedEdges.entries()].map(([edgeKey, to]) => ({ kind: 'edge' as const, @@ -744,7 +776,7 @@ export const createSectorNotOneClueTwoPropagationRule = (): Rule => ({ affectedCells.add(cellKey(r, c)) affectedSectors.add(targetSectorKey) affectedSectors.add(sectorKey(r, c, opposite)) - if (firstExample === null) firstExample = `(${r}, ${c})` + if (firstExample === null) firstExample = formatCellLabel(r, c) } } } @@ -755,7 +787,7 @@ export const createSectorNotOneClueTwoPropagationRule = (): Rule => ({ return { message: firstExample !== null - ? `Cell ${firstExample}${extra > 0 ? ` and ${extra} other(s)` : ''}: clue-2 notOne propagation applied.` + ? `Cell ${firstExample}${extra > 0 ? ` and ${extra} other(s)` : ''}: with clue 2, a not-one sector opposite an existing line cannot take any line, so its edges are blank.` : 'Clue-2 notOne propagation applied.', diffs: [...decidedEdges.entries()].map(([edgeKey, to]) => ({ kind: 'edge' as const, diff --git a/src/domain/rules/slither/rules/shared.ts b/src/domain/rules/slither/rules/shared.ts index 6be2fa8..2b28cd0 100644 --- a/src/domain/rules/slither/rules/shared.ts +++ b/src/domain/rules/slither/rules/shared.ts @@ -1,9 +1,10 @@ -import { cellKey, parseCellKey, parseEdgeKey } from '../../../ir/keys' +import { cellKey, parseCellKey, parseEdgeKey, parseSectorKey } from '../../../ir/keys' import { SECTOR_MASK_ONLY_0, SECTOR_MASK_ONLY_1, SECTOR_MASK_ONLY_2, type PuzzleIR, + type SectorCorner, type SectorConstraintMask, } from '../../../ir/types' @@ -11,6 +12,42 @@ export type SlitherCellColor = 'green' | 'yellow' const adjacentCellsByEdgeCache = new Map() +export const formatCellLabel = (row: number, col: number): string => `(R${row + 1}, C${col + 1})` + +export const formatCellKeyLabel = (key: string): string => { + const [row, col] = parseCellKey(key) + return formatCellLabel(row, col) +} + +export const formatCellRunLabel = ( + orientation: 'row' | 'col', + fixedIndex: number, + startIndex: number, + endIndex: number, +): string => { + if (orientation === 'row') { + return `R${fixedIndex + 1} C${startIndex + 1}-C${endIndex + 1}` + } + return `C${fixedIndex + 1} R${startIndex + 1}-R${endIndex + 1}` +} + +export const formatVertexLabel = (row: number, col: number): string => `V(${row}, ${col})` + +export const formatEdgeLabel = (edgeKeyValue: string): string => { + const [left, right] = parseEdgeKey(edgeKeyValue) + return `edge ${formatVertexLabel(left[0], left[1])}-${formatVertexLabel(right[0], right[1])}` +} + +export const formatCornerLabel = (corner: SectorCorner): string => corner.toUpperCase() + +export const formatSectorLabel = (row: number, col: number, corner: SectorCorner): string => + `(R${row + 1}, C${col + 1}, ${formatCornerLabel(corner)})` + +export const formatSectorKeyLabel = (key: string): string => { + const [row, col, corner] = parseSectorKey(key) + return formatSectorLabel(row, col, corner) +} + export const isSlitherCellColor = (fill: string | undefined): fill is SlitherCellColor => fill === 'green' || fill === 'yellow' diff --git a/src/domain/rules/slither/rules/strongInference.ts b/src/domain/rules/slither/rules/strongInference.ts index 8ee226a..63d028a 100644 --- a/src/domain/rules/slither/rules/strongInference.ts +++ b/src/domain/rules/slither/rules/strongInference.ts @@ -6,14 +6,15 @@ import { sectorMaskSingleValue, type PuzzleIR, } from '../../../ir/types' -import { applyEdgeAssumption, runTrialUntilFixpoint } from './trial' +import { applyEdgeAssumption, runTrialUntilFixpoint, type TrialResult } from './trial' +import { formatEdgeLabel, formatSectorKeyLabel, formatVertexLabel } from './shared' // const STRONG_MAX_CANDIDATES = 1000 // const STRONG_MAX_TRIAL_STEPS = 2000 // const STRONG_MAX_MS = 1000 -const STRONG_MAX_CANDIDATES = 400 -const STRONG_MAX_TRIAL_STEPS = 120 -const STRONG_MAX_MS = 4000 +const STRONG_MAX_CANDIDATES = 200 +const STRONG_MAX_TRIAL_STEPS = 50 +const STRONG_MAX_MS = 3000 type StrongInferenceOptions = { maxCandidates?: number @@ -129,6 +130,27 @@ type StrongCandidateBranch = { diffs: RuleApplication['diffs'] } +const immediateContradictionResult = (puzzle: PuzzleIR): TrialResult => ({ + contradiction: true, + timedOut: false, + exhausted: false, + puzzle, + stepsRun: 0, + elapsedMs: 0, + contradictionReason: { + kind: 'sector-mask', + message: 'setup contradiction: this branch is already incompatible with the current edge state', + }, +}) + +const deriveProbeBudgets = (maxTrialSteps: number): number[] => { + const cappedMax = Math.max(1, maxTrialSteps) + const budgets = [24, 96, 384, cappedMax] + .map((budget) => Math.min(budget, cappedMax)) + .filter((budget, index, arr) => arr.indexOf(budget) === index) + return budgets.length > 0 ? budgets : [cappedMax] +} + const buildBinaryCandidateBranches = ( puzzle: PuzzleIR, edgeA: string, @@ -183,18 +205,18 @@ const collectSharedEdgeDiffs = (basePuzzle: PuzzleIR, branchA: PuzzleIR, branchB const describeCandidate = (candidate: StrongCandidate): string => { if (candidate.kind === 'sector-only-one') { - return `candidate=sector-only-one(${candidate.sectorKey})` + return `sector ${formatSectorKeyLabel(candidate.sectorKey)} must have exactly one line` } if (candidate.kind === 'vertex-two-choice') { - return `candidate=vertex-two-choice((${candidate.vertexRow}, ${candidate.vertexCol}))` + return `vertex ${formatVertexLabel(candidate.vertexRow, candidate.vertexCol)} has two possible continuations` } - return `candidate=edge(${candidate.edgeKey})` + return `${formatEdgeLabel(candidate.edgeKey)} is undecided` } const describeBranch = (diffs: RuleApplication['diffs']): string => diffs .filter((diff): diff is Extract<(typeof diffs)[number], { kind: 'edge' }> => diff.kind === 'edge') - .map((diff) => `${diff.edgeKey}=${diff.to}`) + .map((diff) => `${formatEdgeLabel(diff.edgeKey)} ${diff.to}`) .join(', ') const summarizeFixedDiffs = (diffs: RuleApplication['diffs']): string => { @@ -203,15 +225,22 @@ const summarizeFixedDiffs = (diffs: RuleApplication['diffs']): string => { return 'fixed no edges' } if (edgeDiffs.length <= 3) { - return `fixed ${edgeDiffs.map((diff) => `${diff.edgeKey}=${diff.to}`).join(', ')}` + return `fixed ${edgeDiffs.map((diff) => `${formatEdgeLabel(diff.edgeKey)} ${diff.to}`).join(', ')}` } const preview = edgeDiffs .slice(0, 3) - .map((diff) => `${diff.edgeKey}=${diff.to}`) + .map((diff) => `${formatEdgeLabel(diff.edgeKey)} ${diff.to}`) .join(', ') return `fixed ${edgeDiffs.length} edges (${preview}, ...)` } +const describeTrialBranch = (label: string, result: TrialResult): string => { + if (result.contradiction) { + return `${label} branch contradicted after ${result.stepsRun} ${result.stepsRun === 1 ? 'step' : 'steps'}` + } + return `${label} branch unresolved after ${result.stepsRun} ${result.stepsRun === 1 ? 'step' : 'steps'}` +} + export const createStrongInferenceRule = ( getDeterministicRules: () => Rule[], options: StrongInferenceOptions = {}, @@ -226,80 +255,84 @@ export const createStrongInferenceRule = ( } const deadlineMs = Date.now() + (options.maxMs ?? STRONG_MAX_MS) - for (const candidate of candidates) { - if (Date.now() > deadlineMs) { - break - } + const maxTrialSteps = options.maxTrialSteps ?? STRONG_MAX_TRIAL_STEPS + const probeBudgets = deriveProbeBudgets(maxTrialSteps) + for (const budget of probeBudgets) { + for (const candidate of candidates) { + if (Date.now() > deadlineMs) { + return null + } - let branchA: PuzzleIR - let branchB: PuzzleIR - let branchAInfo: StrongCandidateBranch - let branchBInfo: StrongCandidateBranch + let branchA: PuzzleIR + let branchB: PuzzleIR + let branchAInfo: StrongCandidateBranch + let branchBInfo: StrongCandidateBranch - if (candidate.kind === 'sector-only-one' || candidate.kind === 'vertex-two-choice') { - ;({ branchA, branchB, branchAInfo, branchBInfo } = buildBinaryCandidateBranches( - puzzle, - candidate.edgeA, - candidate.edgeB, - )) - } else { - branchA = clonePuzzle(puzzle) - branchB = clonePuzzle(puzzle) - branchAInfo = { - setupOk: applyEdgeAssumption(branchA, candidate.edgeKey, 'line'), - diffs: [{ kind: 'edge', edgeKey: candidate.edgeKey, from: 'unknown', to: 'line' }], - } - branchBInfo = { - setupOk: applyEdgeAssumption(branchB, candidate.edgeKey, 'blank'), - diffs: [{ kind: 'edge', edgeKey: candidate.edgeKey, from: 'unknown', to: 'blank' }], + if (candidate.kind === 'sector-only-one' || candidate.kind === 'vertex-two-choice') { + ;({ branchA, branchB, branchAInfo, branchBInfo } = buildBinaryCandidateBranches( + puzzle, + candidate.edgeA, + candidate.edgeB, + )) + } else { + branchA = clonePuzzle(puzzle) + branchB = clonePuzzle(puzzle) + branchAInfo = { + setupOk: applyEdgeAssumption(branchA, candidate.edgeKey, 'line'), + diffs: [{ kind: 'edge', edgeKey: candidate.edgeKey, from: 'unknown', to: 'line' }], + } + branchBInfo = { + setupOk: applyEdgeAssumption(branchB, candidate.edgeKey, 'blank'), + diffs: [{ kind: 'edge', edgeKey: candidate.edgeKey, from: 'unknown', to: 'blank' }], + } } - } - const branchAResult = branchAInfo.setupOk - ? runTrialUntilFixpoint(branchA, deterministicRules, options.maxTrialSteps ?? STRONG_MAX_TRIAL_STEPS, deadlineMs) - : { contradiction: true, timedOut: false, exhausted: false, puzzle: branchA } - const branchBResult = branchBInfo.setupOk - ? runTrialUntilFixpoint(branchB, deterministicRules, options.maxTrialSteps ?? STRONG_MAX_TRIAL_STEPS, deadlineMs) - : { contradiction: true, timedOut: false, exhausted: false, puzzle: branchB } + const branchAResult = branchAInfo.setupOk + ? runTrialUntilFixpoint(branchA, deterministicRules, budget, deadlineMs) + : immediateContradictionResult(branchA) + const branchBResult = branchBInfo.setupOk + ? runTrialUntilFixpoint(branchB, deterministicRules, budget, deadlineMs) + : immediateContradictionResult(branchB) - if (branchAResult.timedOut || branchBResult.timedOut) { - break - } - if (branchAResult.exhausted || branchBResult.exhausted) { - continue - } - if (branchAResult.contradiction !== branchBResult.contradiction) { - const contradictionBranch = branchAResult.contradiction ? branchAInfo : branchBInfo - const survivingBranch = branchAResult.contradiction ? branchBInfo : branchAInfo - const diffs = survivingBranch.diffs.filter((diff) => { - if (diff.kind !== 'edge') { - return false + if (branchAResult.timedOut || branchBResult.timedOut) { + return null + } + if (branchAResult.contradiction !== branchBResult.contradiction) { + const contradictionBranch = branchAResult.contradiction ? branchAInfo : branchBInfo + const survivingBranch = branchAResult.contradiction ? branchBInfo : branchAInfo + const diffs = survivingBranch.diffs.filter((diff) => { + if (diff.kind !== 'edge') { + return false + } + return (puzzle.edges[diff.edgeKey]?.mark ?? 'unknown') === 'unknown' + }) + if (diffs.length === 0) { + continue } - return (puzzle.edges[diff.edgeKey]?.mark ?? 'unknown') === 'unknown' - }) + + return { + message: `Strong inference: ${describeCandidate(candidate)}. The branch ${describeBranch(contradictionBranch.diffs)} contradicts the puzzle at probe budget ${budget}, so the alternative is forced and ${summarizeFixedDiffs(diffs)}. ${describeTrialBranch('A', branchAResult)}; ${describeTrialBranch('B', branchBResult)}.`, + diffs, + affectedCells: candidate.kind === 'sector-only-one' ? [cellKey(candidate.row, candidate.col)] : [], + affectedSectors: candidate.kind === 'sector-only-one' ? [candidate.sectorKey] : [], + } + } + if (branchAResult.contradiction && branchBResult.contradiction) { + continue + } + + const diffs = collectSharedEdgeDiffs(puzzle, branchAResult.puzzle, branchBResult.puzzle) if (diffs.length === 0) { continue } return { - message: `Strong inference ${describeCandidate(candidate)} result=contradiction: branch ${describeBranch(contradictionBranch.diffs)} fails, so ${summarizeFixedDiffs(diffs)}.`, + message: `Strong inference: ${describeCandidate(candidate)}. Both branches lead to the same consequence at probe budget ${budget}, so ${summarizeFixedDiffs(diffs)}. ${describeTrialBranch('A', branchAResult)}; ${describeTrialBranch('B', branchBResult)}.`, diffs, affectedCells: candidate.kind === 'sector-only-one' ? [cellKey(candidate.row, candidate.col)] : [], affectedSectors: candidate.kind === 'sector-only-one' ? [candidate.sectorKey] : [], } } - - const diffs = collectSharedEdgeDiffs(puzzle, branchAResult.puzzle, branchBResult.puzzle) - if (diffs.length === 0) { - continue - } - - return { - message: `Strong inference ${describeCandidate(candidate)} result=shared-consequence: both branches agree and ${summarizeFixedDiffs(diffs)}.`, - diffs, - affectedCells: candidate.kind === 'sector-only-one' ? [cellKey(candidate.row, candidate.col)] : [], - affectedSectors: candidate.kind === 'sector-only-one' ? [candidate.sectorKey] : [], - } } return null diff --git a/src/domain/rules/slither/rules/trial.ts b/src/domain/rules/slither/rules/trial.ts index cd46323..ead59a4 100644 --- a/src/domain/rules/slither/rules/trial.ts +++ b/src/domain/rules/slither/rules/trial.ts @@ -8,13 +8,36 @@ import { type EdgeMark, type PuzzleIR, } from '../../../ir/types' -import { getEdgeAdjacentCellKeys, isSlitherCellColor } from './shared' +import { + formatCellKeyLabel, + formatCellLabel, + formatEdgeLabel, + formatSectorKeyLabel, + formatVertexLabel, + getEdgeAdjacentCellKeys, + isSlitherCellColor, +} from './shared' + +export type TrialContradictionReason = { + kind: + | 'vertex-degree' + | 'cell-clue' + | 'sector-mask' + | 'vertex-candidates' + | 'color-edge' + | 'line-loop' + | 'disconnected-green' + message: string +} export type TrialResult = { contradiction: boolean timedOut: boolean exhausted: boolean puzzle: PuzzleIR + stepsRun: number + elapsedMs: number + contradictionReason?: TrialContradictionReason } export const applyEdgeAssumption = (puzzle: PuzzleIR, edgeKeyValue: string, to: EdgeMark): boolean => { @@ -26,7 +49,7 @@ export const applyEdgeAssumption = (puzzle: PuzzleIR, edgeKeyValue: string, to: return true } -const detectVertexContradiction = (puzzle: PuzzleIR): boolean => { +const detectVertexContradiction = (puzzle: PuzzleIR): TrialContradictionReason | null => { for (let r = 0; r <= puzzle.rows; r += 1) { for (let c = 0; c <= puzzle.cols; c += 1) { const incident = getVertexIncidentEdges(r, c, puzzle.rows, puzzle.cols) @@ -41,17 +64,23 @@ const detectVertexContradiction = (puzzle: PuzzleIR): boolean => { else if (mark === 'unknown') unknownCount += 1 } if (lineCount > 2) { - return true + return { + kind: 'vertex-degree', + message: `vertex-degree contradiction at ${formatVertexLabel(r, c)}: ${lineCount} line edges meet there`, + } } if (unknownCount === 0 && lineCount !== 0 && lineCount !== 2) { - return true + return { + kind: 'vertex-degree', + message: `vertex-degree contradiction at ${formatVertexLabel(r, c)}: closed vertex has ${lineCount} line edge`, + } } } } - return false + return null } -const detectCellClueContradiction = (puzzle: PuzzleIR): boolean => { +const detectCellClueContradiction = (puzzle: PuzzleIR): TrialContradictionReason | null => { for (let r = 0; r < puzzle.rows; r += 1) { for (let c = 0; c < puzzle.cols; c += 1) { const clue = puzzle.cells[cellKey(r, c)]?.clue @@ -67,19 +96,31 @@ const detectCellClueContradiction = (puzzle: PuzzleIR): boolean => { if (mark === 'line') lineCount += 1 else if (mark === 'unknown') unknownCount += 1 } - if (lineCount > target || lineCount + unknownCount < target) { - return true + if (lineCount > target) { + return { + kind: 'cell-clue', + message: `cell-clue contradiction at ${formatCellLabel(r, c)}: clue ${target} already has ${lineCount} line edges`, + } + } + if (lineCount + unknownCount < target) { + return { + kind: 'cell-clue', + message: `cell-clue contradiction at ${formatCellLabel(r, c)}: clue ${target} can reach at most ${lineCount + unknownCount} line edges`, + } } } } - return false + return null } -const detectSectorContradiction = (puzzle: PuzzleIR): boolean => { +const detectSectorContradiction = (puzzle: PuzzleIR): TrialContradictionReason | null => { for (const [sectorKeyValue, sectorState] of Object.entries(puzzle.sectors)) { const mask = sectorState?.constraintsMask ?? SECTOR_MASK_ALL if (!sectorMaskIsValid(mask)) { - return true + return { + kind: 'sector-mask', + message: `sector-mask contradiction at ${formatSectorKeyLabel(sectorKeyValue)}: no corner line count remains allowed`, + } } const [row, col, corner] = parseSectorKey(sectorKeyValue) const sectorEdges = getCornerEdgeKeys(row, col, corner) @@ -91,7 +132,10 @@ const detectSectorContradiction = (puzzle: PuzzleIR): boolean => { else if (mark === 'unknown') unknownCount += 1 } if (unknownCount === 0 && !sectorMaskAllows(mask, lineCount as 0 | 1 | 2)) { - return true + return { + kind: 'sector-mask', + message: `sector-mask contradiction at ${formatSectorKeyLabel(sectorKeyValue)}: fixed corner has ${lineCount} line edges, which the sector mask forbids`, + } } let hasFeasible = false for (let value = lineCount; value <= lineCount + unknownCount; value += 1) { @@ -101,22 +145,29 @@ const detectSectorContradiction = (puzzle: PuzzleIR): boolean => { } } if (!hasFeasible) { - return true + return { + kind: 'sector-mask', + message: `sector-mask contradiction at ${formatSectorKeyLabel(sectorKeyValue)}: ${lineCount} fixed line edges and ${unknownCount} unknown edges leave no allowed corner count`, + } } } - return false + return null } -const detectVertexCandidateContradiction = (puzzle: PuzzleIR): boolean => { - for (const vertexState of Object.values(puzzle.vertices ?? {})) { +const detectVertexCandidateContradiction = (puzzle: PuzzleIR): TrialContradictionReason | null => { + for (const [vertexKeyValue, vertexState] of Object.entries(puzzle.vertices ?? {})) { if (vertexState.candidateEdgeSets.length === 0) { - return true + const [row, col] = vertexKeyValue.split(',').map(Number) + return { + kind: 'vertex-candidates', + message: `vertex-candidates contradiction at ${formatVertexLabel(row, col)}: no feasible degree state remains`, + } } } - return false + return null } -const detectColorEdgeContradiction = (puzzle: PuzzleIR): boolean => { +const detectColorEdgeContradiction = (puzzle: PuzzleIR): TrialContradictionReason | null => { for (const [edgeKeyValue, edgeState] of Object.entries(puzzle.edges)) { const mark = edgeState?.mark ?? 'unknown' if (mark !== 'line' && mark !== 'blank') { @@ -130,7 +181,10 @@ const detectColorEdgeContradiction = (puzzle: PuzzleIR): boolean => { } const expected = mark === 'line' ? 'green' : 'yellow' if (color !== expected) { - return true + return { + kind: 'color-edge', + message: `color-edge contradiction at ${formatEdgeLabel(edgeKeyValue)}: boundary ${mark} requires ${formatCellKeyLabel(adjacentCells[0])} to be ${expected}, but it is ${color}`, + } } continue } @@ -143,19 +197,25 @@ const detectColorEdgeContradiction = (puzzle: PuzzleIR): boolean => { continue } if (mark === 'line' && colorA === colorB) { - return true + return { + kind: 'color-edge', + message: `color-edge contradiction at ${formatEdgeLabel(edgeKeyValue)}: a line edge separates equal-colored cells ${formatCellKeyLabel(adjacentCells[0])} and ${formatCellKeyLabel(adjacentCells[1])}`, + } } if (mark === 'blank' && colorA !== colorB) { - return true + return { + kind: 'color-edge', + message: `color-edge contradiction at ${formatEdgeLabel(edgeKeyValue)}: a blank edge connects different-colored cells ${formatCellKeyLabel(adjacentCells[0])} and ${formatCellKeyLabel(adjacentCells[1])}`, + } } } - return false + return null } -const detectLineLoopContradiction = (puzzle: PuzzleIR): boolean => { +const detectLineLoopContradiction = (puzzle: PuzzleIR): TrialContradictionReason | null => { const lineEdges = Object.entries(puzzle.edges).filter(([, edgeState]) => (edgeState?.mark ?? 'unknown') === 'line') if (lineEdges.length === 0) { - return false + return null } const vertexCols = puzzle.cols + 1 const vertexCount = (puzzle.rows + 1) * vertexCols @@ -228,16 +288,19 @@ const detectLineLoopContradiction = (puzzle: PuzzleIR): boolean => { closedLoopEdges += edgeCount closedLoopComponents += 1 } - if (closedLoopComponents > 1) { - return true - } - if (closedLoopComponents === 1 && closedLoopEdges < lineEdges.length) { - return true + if (closedLoopComponents > 1 || (closedLoopComponents === 1 && closedLoopEdges < lineEdges.length)) { + return { + kind: 'line-loop', + message: + closedLoopComponents > 1 + ? `line-loop contradiction: ${closedLoopComponents} separate closed loops are present` + : `line-loop contradiction: a closed loop of ${closedLoopEdges} edges exists while other line edges remain outside it`, + } } - return false + return null } -const detectDisconnectedGreenContradiction = (puzzle: PuzzleIR): boolean => { +const detectDisconnectedGreenContradiction = (puzzle: PuzzleIR): TrialContradictionReason | null => { const greenCells: string[] = [] for (let row = 0; row < puzzle.rows; row += 1) { for (let col = 0; col < puzzle.cols; col += 1) { @@ -248,7 +311,7 @@ const detectDisconnectedGreenContradiction = (puzzle: PuzzleIR): boolean => { } } if (greenCells.length < 2) { - return false + return null } const inBounds = (row: number, col: number): boolean => @@ -284,46 +347,93 @@ const detectDisconnectedGreenContradiction = (puzzle: PuzzleIR): boolean => { } } - return greenCells.some((key) => !reachable.has(key)) + const disconnectedCell = greenCells.find((key) => !reachable.has(key)) + if (!disconnectedCell) { + return null + } + return { + kind: 'disconnected-green', + message: `disconnected-green contradiction: ${formatCellKeyLabel(disconnectedCell)} cannot connect to ${formatCellKeyLabel(greenCells[0])} through non-line edges`, + } } -export const detectHardContradiction = (puzzle: PuzzleIR): boolean => - detectVertexContradiction(puzzle) || - detectCellClueContradiction(puzzle) || - detectSectorContradiction(puzzle) || - detectVertexCandidateContradiction(puzzle) || - detectColorEdgeContradiction(puzzle) || - detectLineLoopContradiction(puzzle) || +export const findHardContradictionReason = (puzzle: PuzzleIR): TrialContradictionReason | null => + detectVertexContradiction(puzzle) ?? + detectCellClueContradiction(puzzle) ?? + detectSectorContradiction(puzzle) ?? + detectVertexCandidateContradiction(puzzle) ?? + detectColorEdgeContradiction(puzzle) ?? + detectLineLoopContradiction(puzzle) ?? detectDisconnectedGreenContradiction(puzzle) +export const detectHardContradiction = (puzzle: PuzzleIR): boolean => + findHardContradictionReason(puzzle) !== null + export const runTrialUntilFixpoint = ( puzzle: PuzzleIR, deterministicRules: Rule[], maxTrialSteps: number, deadlineMs: number, ): TrialResult => { - if (detectHardContradiction(puzzle)) { - return { contradiction: true, timedOut: false, exhausted: false, puzzle } + const startedAt = performance.now() + const initialContradictionReason = findHardContradictionReason(puzzle) + if (initialContradictionReason) { + return { + contradiction: true, + timedOut: false, + exhausted: false, + puzzle, + stepsRun: 0, + elapsedMs: Math.max(0, performance.now() - startedAt), + contradictionReason: initialContradictionReason, + } } let trial = puzzle for (let stepNumber = 1; stepNumber <= maxTrialSteps; stepNumber += 1) { if (Date.now() > deadlineMs) { - return { contradiction: false, timedOut: true, exhausted: false, puzzle: trial } + return { + contradiction: false, + timedOut: true, + exhausted: false, + puzzle: trial, + stepsRun: stepNumber - 1, + elapsedMs: Math.max(0, performance.now() - startedAt), + } } const { nextPuzzle, step } = runNextRule(trial, deterministicRules, stepNumber) if (!step) { + const contradictionReason = findHardContradictionReason(trial) return { - contradiction: detectHardContradiction(trial), + contradiction: contradictionReason !== null, timedOut: false, exhausted: false, puzzle: trial, + stepsRun: stepNumber - 1, + elapsedMs: Math.max(0, performance.now() - startedAt), + contradictionReason: contradictionReason ?? undefined, } } trial = nextPuzzle - if (detectHardContradiction(trial)) { - return { contradiction: true, timedOut: false, exhausted: false, puzzle: trial } + const contradictionReason = findHardContradictionReason(trial) + if (contradictionReason) { + return { + contradiction: true, + timedOut: false, + exhausted: false, + puzzle: trial, + stepsRun: stepNumber, + elapsedMs: Math.max(0, performance.now() - startedAt), + contradictionReason, + } } } - return { contradiction: false, timedOut: false, exhausted: true, puzzle: trial } + return { + contradiction: false, + timedOut: false, + exhausted: true, + puzzle: trial, + stepsRun: maxTrialSteps, + elapsedMs: Math.max(0, performance.now() - startedAt), + } } diff --git a/src/features/board/BoardLegendButton.tsx b/src/features/board/BoardLegendButton.tsx new file mode 100644 index 0000000..d548f8b --- /dev/null +++ b/src/features/board/BoardLegendButton.tsx @@ -0,0 +1,244 @@ +import { useEffect, useMemo, useRef, useState } from 'react' +import { puzzleRegistry } from '../../domain/plugins/registry' +import type { + PuzzleHelpExampleEdge, + PuzzleLegendExample, + PuzzleLegendSectorMarker, +} from '../../domain/plugins/types' + +type Props = { + pluginId: string +} + +const LEGEND_WIDTH = 132 +const LEGEND_HEIGHT = 112 +const LEGEND_PADDING = 16 + +const edgeKey = (edge: PuzzleHelpExampleEdge['edge']): string => { + const [a, b] = edge + return `${a[0]},${a[1]}-${b[0]},${b[1]}` +} + +const getSectorArcAngles = (corner: PuzzleLegendSectorMarker['corner']): [number, number] => { + if (corner === 'nw') { + return [0, Math.PI / 2] + } + if (corner === 'ne') { + return [Math.PI / 2, Math.PI] + } + if (corner === 'sw') { + return [Math.PI * 1.5, Math.PI * 2] + } + return [Math.PI, Math.PI * 1.5] +} + +const getSectorStyle = ( + kind: PuzzleLegendSectorMarker['kind'], +): { color: string; dash: number[]; lineWidth: number; radiusScale: number } => { + if (kind === 'onlyOne') { + return { color: '#ef4444', dash: [], lineWidth: 2.4, radiusScale: 0.34 } + } + if (kind === 'notOne') { + return { color: '#3b82f6', dash: [], lineWidth: 2, radiusScale: 0.24 } + } + if (kind === 'notZero') { + return { color: '#22c55e', dash: [4, 3], lineWidth: 2, radiusScale: 0.19 } + } + return { color: '#f59e0b', dash: [4, 3], lineWidth: 2, radiusScale: 0.29 } +} + +const BoardLegendCanvas = ({ example, label }: { example: PuzzleLegendExample; label: string }) => { + const canvasRef = useRef(null) + const edgeMarks = useMemo( + () => new Map((example.edges ?? []).map((edge) => [edgeKey(edge.edge), edge.mark])), + [example.edges], + ) + + useEffect(() => { + const canvas = canvasRef.current + const ctx = canvas?.getContext('2d') + if (!canvas || !ctx) { + return + } + + canvas.width = LEGEND_WIDTH + canvas.height = LEGEND_HEIGHT + const boardWidth = LEGEND_WIDTH - LEGEND_PADDING * 2 + const boardHeight = LEGEND_HEIGHT - LEGEND_PADDING * 2 + const cellSize = Math.min(boardWidth / example.cols, boardHeight / example.rows) + const gridWidth = cellSize * example.cols + const gridHeight = cellSize * example.rows + const offsetX = (LEGEND_WIDTH - gridWidth) / 2 + const offsetY = (LEGEND_HEIGHT - gridHeight) / 2 + + ctx.clearRect(0, 0, LEGEND_WIDTH, LEGEND_HEIGHT) + ctx.fillStyle = '#ffffff' + ctx.fillRect(0, 0, LEGEND_WIDTH, LEGEND_HEIGHT) + + for (const cell of example.filledCells ?? []) { + ctx.fillStyle = + cell.fill === 'green' ? 'rgba(34, 197, 94, 0.24)' : 'rgba(245, 158, 11, 0.24)' + ctx.fillRect(offsetX + cell.col * cellSize, offsetY + cell.row * cellSize, cellSize, cellSize) + } + + ctx.strokeStyle = '#cbd5e1' + ctx.lineWidth = 1 + ctx.setLineDash([]) + for (let row = 0; row <= example.rows; row += 1) { + const y = offsetY + row * cellSize + ctx.beginPath() + ctx.moveTo(offsetX, y) + ctx.lineTo(offsetX + gridWidth, y) + ctx.stroke() + } + for (let col = 0; col <= example.cols; col += 1) { + const x = offsetX + col * cellSize + ctx.beginPath() + ctx.moveTo(x, offsetY) + ctx.lineTo(x, offsetY + gridHeight) + ctx.stroke() + } + + for (const marker of example.sectors ?? []) { + const baseX = offsetX + marker.col * cellSize + const baseY = offsetY + marker.row * cellSize + const cornerX = marker.corner === 'ne' || marker.corner === 'se' ? baseX + cellSize : baseX + const cornerY = marker.corner === 'sw' || marker.corner === 'se' ? baseY + cellSize : baseY + const [start, end] = getSectorArcAngles(marker.corner) + const style = getSectorStyle(marker.kind) + + ctx.strokeStyle = style.color + ctx.lineWidth = style.lineWidth + ctx.setLineDash(style.dash) + ctx.beginPath() + ctx.arc(cornerX, cornerY, cellSize * style.radiusScale, start, end) + ctx.stroke() + } + ctx.setLineDash([]) + + for (const [key, mark] of edgeMarks) { + const [start, end] = key.split('-') + const [rowA, colA] = start.split(',').map(Number) + const [rowB, colB] = end.split(',').map(Number) + const x1 = offsetX + colA * cellSize + const y1 = offsetY + rowA * cellSize + const x2 = offsetX + colB * cellSize + const y2 = offsetY + rowB * cellSize + + if (mark === 'line') { + ctx.strokeStyle = '#0284c7' + ctx.lineWidth = 3 + ctx.beginPath() + ctx.moveTo(x1, y1) + ctx.lineTo(x2, y2) + ctx.stroke() + } else { + const midX = (x1 + x2) / 2 + const midY = (y1 + y2) / 2 + const crossSize = 4 + ctx.strokeStyle = '#94a3b8' + ctx.lineWidth = 1.8 + ctx.beginPath() + ctx.moveTo(midX - crossSize, midY - crossSize) + ctx.lineTo(midX + crossSize, midY + crossSize) + ctx.moveTo(midX + crossSize, midY - crossSize) + ctx.lineTo(midX - crossSize, midY + crossSize) + ctx.stroke() + } + } + + ctx.fillStyle = '#111827' + ctx.font = `700 ${Math.max(13, cellSize * 0.42)}px Inter, sans-serif` + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + for (const clue of example.clues ?? []) { + ctx.fillText( + String(clue.value), + offsetX + clue.col * cellSize + cellSize / 2, + offsetY + clue.row * cellSize + cellSize / 2, + ) + } + + ctx.fillStyle = '#111827' + for (let row = 0; row <= example.rows; row += 1) { + for (let col = 0; col <= example.cols; col += 1) { + ctx.beginPath() + ctx.arc(offsetX + col * cellSize, offsetY + row * cellSize, 1.7, 0, Math.PI * 2) + ctx.fill() + } + } + }, [edgeMarks, example]) + + return +} + +export const BoardLegendButton = ({ pluginId }: Props) => { + const [openPluginId, setOpenPluginId] = useState(null) + const plugin = puzzleRegistry.get(pluginId) + const legend = plugin?.legend + const isOpen = openPluginId === pluginId + const titleId = `${pluginId}-board-legend-title` + + useEffect(() => { + if (!isOpen) { + return + } + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + setOpenPluginId(null) + } + } + document.addEventListener('keydown', handleKeyDown) + return () => document.removeEventListener('keydown', handleKeyDown) + }, [isOpen]) + + if (!plugin || !legend) { + return null + } + + return ( +
+ + {isOpen ? ( +
+
+

{legend.title}

+ +
+
+ {legend.items.map((item) => ( +
+ +
+

{item.label}

+

{item.description}

+
+
+ ))} +
+
+ ) : null} +
+ ) +} diff --git a/src/features/board/CanvasBoard.tsx b/src/features/board/CanvasBoard.tsx index 376d923..ba231f5 100644 --- a/src/features/board/CanvasBoard.tsx +++ b/src/features/board/CanvasBoard.tsx @@ -8,8 +8,8 @@ import { } from '../../domain/ir/keys' import { SECTOR_MASK_ALL, + SECTOR_MASK_ONLY_1, sectorMaskAllows, - sectorMaskIsSingle, type SectorCorner, } from '../../domain/ir/types' import type { PuzzleIR } from '../../domain/ir/types' @@ -204,14 +204,8 @@ export const CanvasBoard = ({ drawArc(sectorRadii.notTwo, '#f59e0b', 1.8, [4, 3]) } - if (sectorMaskIsSingle(mask)) { - // Emphasize sectors that have been reduced to a single exact count. - ctx.strokeStyle = '#ef4444' - ctx.lineWidth = 2.4 - ctx.setLineDash([]) - ctx.beginPath() - ctx.arc(cornerX, cornerY, sectorRadii.single, start, end) - ctx.stroke() + if (mask === SECTOR_MASK_ONLY_1) { + drawArc(sectorRadii.single, '#ef4444', 2.4) } ctx.restore() } diff --git a/src/features/puzzleInfo/PuzzleInfoButton.tsx b/src/features/puzzleInfo/PuzzleInfoButton.tsx new file mode 100644 index 0000000..7568818 --- /dev/null +++ b/src/features/puzzleInfo/PuzzleInfoButton.tsx @@ -0,0 +1,218 @@ +import { useEffect, useMemo, useRef, useState } from 'react' +import { puzzleRegistry } from '../../domain/plugins/registry' +import type { PuzzleHelpExample, PuzzleHelpExampleEdge } from '../../domain/plugins/types' + +type Props = { + pluginId: string +} + +const EXAMPLE_WIDTH = 142 +const EXAMPLE_HEIGHT = 116 +const EXAMPLE_PADDING = 18 + +const edgeKey = (edge: PuzzleHelpExampleEdge['edge']): string => { + const [a, b] = edge + return `${a[0]},${a[1]}-${b[0]},${b[1]}` +} + +const PuzzleInfoExampleCanvas = ({ example }: { example: PuzzleHelpExample }) => { + const canvasRef = useRef(null) + const edgeMarks = useMemo( + () => new Map(example.edges.map((edge) => [edgeKey(edge.edge), edge.mark])), + [example.edges], + ) + + useEffect(() => { + const canvas = canvasRef.current + const ctx = canvas?.getContext('2d') + if (!canvas || !ctx) { + return + } + + canvas.width = EXAMPLE_WIDTH + canvas.height = EXAMPLE_HEIGHT + const boardWidth = EXAMPLE_WIDTH - EXAMPLE_PADDING * 2 + const boardHeight = EXAMPLE_HEIGHT - EXAMPLE_PADDING * 2 + const cellSize = Math.min(boardWidth / example.cols, boardHeight / example.rows) + const gridWidth = cellSize * example.cols + const gridHeight = cellSize * example.rows + const offsetX = (EXAMPLE_WIDTH - gridWidth) / 2 + const offsetY = (EXAMPLE_HEIGHT - gridHeight) / 2 + + ctx.clearRect(0, 0, EXAMPLE_WIDTH, EXAMPLE_HEIGHT) + ctx.fillStyle = '#ffffff' + ctx.fillRect(0, 0, EXAMPLE_WIDTH, EXAMPLE_HEIGHT) + + ctx.strokeStyle = '#cbd5e1' + ctx.lineWidth = 1 + for (let row = 0; row <= example.rows; row += 1) { + const y = offsetY + row * cellSize + ctx.beginPath() + ctx.moveTo(offsetX, y) + ctx.lineTo(offsetX + gridWidth, y) + ctx.stroke() + } + for (let col = 0; col <= example.cols; col += 1) { + const x = offsetX + col * cellSize + ctx.beginPath() + ctx.moveTo(x, offsetY) + ctx.lineTo(x, offsetY + gridHeight) + ctx.stroke() + } + + for (const [key, mark] of edgeMarks) { + const [start, end] = key.split('-') + const [rowA, colA] = start.split(',').map(Number) + const [rowB, colB] = end.split(',').map(Number) + const x1 = offsetX + colA * cellSize + const y1 = offsetY + rowA * cellSize + const x2 = offsetX + colB * cellSize + const y2 = offsetY + rowB * cellSize + + if (mark === 'line') { + ctx.strokeStyle = '#0284c7' + ctx.lineWidth = 3 + ctx.beginPath() + ctx.moveTo(x1, y1) + ctx.lineTo(x2, y2) + ctx.stroke() + } else { + const midX = (x1 + x2) / 2 + const midY = (y1 + y2) / 2 + const crossSize = 4 + ctx.strokeStyle = '#94a3b8' + ctx.lineWidth = 1.7 + ctx.beginPath() + ctx.moveTo(midX - crossSize, midY - crossSize) + ctx.lineTo(midX + crossSize, midY + crossSize) + ctx.moveTo(midX + crossSize, midY - crossSize) + ctx.lineTo(midX - crossSize, midY + crossSize) + ctx.stroke() + } + } + + ctx.fillStyle = '#111827' + ctx.font = `700 ${Math.max(14, cellSize * 0.48)}px Inter, sans-serif` + ctx.textAlign = 'center' + ctx.textBaseline = 'middle' + for (const clue of example.clues) { + ctx.fillText( + String(clue.value), + offsetX + clue.col * cellSize + cellSize / 2, + offsetY + clue.row * cellSize + cellSize / 2, + ) + } + + ctx.fillStyle = '#111827' + for (let row = 0; row <= example.rows; row += 1) { + for (let col = 0; col <= example.cols; col += 1) { + ctx.beginPath() + ctx.arc(offsetX + col * cellSize, offsetY + row * cellSize, 1.8, 0, Math.PI * 2) + ctx.fill() + } + } + }, [edgeMarks, example]) + + return ( +
+ +
+ {example.label} + {example.description} +
+
+ ) +} + +export const PuzzleInfoButton = ({ pluginId }: Props) => { + const [openPluginId, setOpenPluginId] = useState(null) + const plugin = puzzleRegistry.get(pluginId) + const help = plugin?.help + const titleId = `${pluginId}-puzzle-info-title` + const isOpen = openPluginId === pluginId + + useEffect(() => { + if (!isOpen) { + return + } + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + setOpenPluginId(null) + } + } + document.addEventListener('keydown', handleKeyDown) + return () => document.removeEventListener('keydown', handleKeyDown) + }, [isOpen]) + + if (!plugin || !help) { + return null + } + + return ( +
+ + {isOpen ? ( +
+
+

{help.title}

+ +
+

{help.summary}

+
+

Core Rules

+
    + {help.rules.map((rule) => ( +
  • {rule}
  • + ))} +
+
+ {help.notes && help.notes.length > 0 ? ( +
+

In PuzzleKit

+
    + {help.notes.map((note) => ( +
  • {note}
  • + ))} +
+
+ ) : null} + {help.example ? ( +
+

{help.example.title}

+
+ + +
+
+ ) : null} +
+ ) : null} +
+ ) +} diff --git a/src/features/solver/ControlPanel.tsx b/src/features/solver/ControlPanel.tsx index 9134870..c669558 100644 --- a/src/features/solver/ControlPanel.tsx +++ b/src/features/solver/ControlPanel.tsx @@ -2,6 +2,8 @@ import { useEffect, useMemo, useState } from 'react' import { exportPuzzle, exporters, tryEncodePuzzlink } from '../../domain/exporters' import type { ExportFormat } from '../../domain/exporters/types' import { puzzleRegistry } from '../../domain/plugins/registry' +import { BoardLegendButton } from '../board/BoardLegendButton' +import { PuzzleInfoButton } from '../puzzleInfo/PuzzleInfoButton' import { buildDifficultySnapshot, MAX_SOLVE_CHUNK_SIZE, useSolverStore } from './solverStore' export const ControlPanel = () => { @@ -87,6 +89,8 @@ export const ControlPanel = () => { ))} + +