From 62bf90409fdefe1b05f33074e3b2dacab929c594 Mon Sep 17 00:00:00 2001 From: brandonperfetti Date: Fri, 25 Sep 2026 13:45:21 -0700 Subject: [PATCH 1/5] Add Vitest suite for chart data shaping and a home-page smoke render Covers the pure shaping helpers in lib/github.ts (levels, streaks, calendar weeks, weekly windows, sparkline scaling, push cadence), the GitHub loaders behind the charts (contribution scraping, repo listing with pagination and fallbacks, pinned repos over GraphQL, commit activity summary, commit timing heatmap with timezone bucketing, repo risk buckets, release cadence, PR cycle time and merge rate, issue backlog delta), the URL and timezone helpers, and a static render of the home route from stubbed GitHub responses, including the case where every upstream request fails. Every GitHub request goes through a fetch router that throws on unrouted URLs, so the suite cannot reach the network. Vitest 4 is used rather than 5 because 5 requires @types/node 22 or newer and this repo pins 20. --- package-lock.json | 1183 ++++++++++++++++++++++-- package.json | 5 +- tests/app/home-page.test.tsx | 134 +++ tests/helpers/github-fixtures.ts | 234 +++++ tests/lib/contribution-shaping.test.ts | 168 ++++ tests/lib/github-api.test.ts | 552 +++++++++++ tests/lib/site-helpers.test.ts | 83 ++ tests/setup.ts | 20 + vitest.config.mts | 20 + 9 files changed, 2342 insertions(+), 57 deletions(-) create mode 100644 tests/app/home-page.test.tsx create mode 100644 tests/helpers/github-fixtures.ts create mode 100644 tests/lib/contribution-shaping.test.ts create mode 100644 tests/lib/github-api.test.ts create mode 100644 tests/lib/site-helpers.test.ts create mode 100644 tests/setup.ts create mode 100644 vitest.config.mts diff --git a/package-lock.json b/package-lock.json index 97245d7..9e652b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -26,7 +26,8 @@ "prettier": "^3.8.1", "prettier-plugin-tailwindcss": "^0.7.2", "tailwindcss": "^4", - "typescript": "^5" + "typescript": "^5", + "vitest": "^4.1.11" } }, "node_modules/@alloc/quick-lru": { @@ -1232,6 +1233,16 @@ "node": ">=12.4.0" } }, + "node_modules/@oxc-project/types": { + "version": "0.151.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.151.0.tgz", + "integrity": "sha512-J1yXrIlNDZVzE3ada310xeAw7nH8yCAyLPuUIsjKatFPmfn5bS1oW+cM+QsGOtVWd5nhSpbwZWx/rue+r5Z+PA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, "node_modules/@reduxjs/toolkit": { "version": "2.11.2", "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", @@ -1268,6 +1279,286 @@ "url": "https://opencollective.com/immer" } }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.11.tgz", + "integrity": "sha512-A5kXfGKvKWWZE0TtPrfsvT+q4Y5d1QG8gGUzpYjGydM+fARM9MuX90PrXYXe0XbsDVgyxxNzHo6giCj90bsFNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.11.tgz", + "integrity": "sha512-z6cTycz+iJ4PVkuL4HHW4DfTfoeU/2nqYYuSOrTmH7yHK5Y0LCOnA03V4ZNxavyVaU1oOqUgIg2klN/s+USGOA==", + "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.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.11.tgz", + "integrity": "sha512-jShvqNtP6vDC6/A5JOAzbVV+DkgHqhl/ScVCJEbt+TUY6QYz7YnXcrg3sLtFBniro0f/Ld50ZwCWA6f7KYD1nQ==", + "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.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.11.tgz", + "integrity": "sha512-f2i2xiNWq1Z1l2++q2fuhZRdLAT3aqxD6vRNm1RAxpUoBcdqNB3C0s1Bt+K+PbEx2F5F4gQp6hqKkphCY/xF9w==", + "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.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.11.tgz", + "integrity": "sha512-4Ir5FSOKIAMr4r0kExpt1s3bMgzJU3rA45AYOHtQpls0oNeqcYBKrWMlckrYH4KCfGLfkfn1tN1dmZPMVsdXow==", + "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.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.11.tgz", + "integrity": "sha512-/gnRDM+39BROzAN/k1OZjDPnDMcZxB/0EUxKjONO5yVkNEvlsoMDrxGNKgZi/ttFriS2gwlDNzB65pvNbFOXIQ==", + "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.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.11.tgz", + "integrity": "sha512-PFaK8HwvAHbaKbBcDNQihjMKYvFnA5hiENx/l5tphTDz1E0WFp32l0A7aq7lyUwGsRw/xSrNIy/gIK4thrSCrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.11.tgz", + "integrity": "sha512-AskzJUIKRLPxkruR1wLKewGbOw+EYfU/9lOrBFj4AFrEA8hPpKFnODWNu2WLaNs0QNkEb9QIJufmVZZIL/bJlg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.11.tgz", + "integrity": "sha512-qlUGAheh2yh8afH7QBgx0PrRHN85hKnNd78x8MeMhXivuevgd8vgf6/CstOzmNKY/lLTHvNTrPy98cLnAugzJw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.11.tgz", + "integrity": "sha512-secpEad+0vCbSfn8upFySkDskv+bGPk3THSDS9Y89yc4rb4kzqHp8Dmyd9BkQW4SnhNXBZCl/6CrO//hZahNJQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.11.tgz", + "integrity": "sha512-mOVBT3dPpkWm8XBWPmU4bf+U6dYDLeMo/9ojUmis4N0L5uu10qra5vOyngZ7/PSdoE4G9KvRt4bloRxNjLas7A==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.11.tgz", + "integrity": "sha512-Is78i9A8Ui4SqcxUwFJ9uMmjDn58IbVTjFWYdQestFEgeuEmHMLGNriXnVJKkwG2YiZjw8cP0zCTyDMdDGtOOg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.11.tgz", + "integrity": "sha512-dUCXneZ87INUMyQ0D+C0HrEBNUPNXHaPmU5GTjyKTJEiussw9Kaj5Ln8UztPe4epV/ffvgNBEadksdYhmW6xJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.11.tgz", + "integrity": "sha512-jByxb6qfd+bH1xUd0qnfFnb17i9sWBPY2tOavJ0l3tdr3OTu+Kvtm8cd/JV5nFt657b1VqGltxg9olOEfofXWw==", + "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.2.11", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.11.tgz", + "integrity": "sha512-/PzKqzAJ03i19oy2ItPvyvaVjOjBCNnfaJs8yvUdGBKmiESgnrJSQ2awd81QzFbbnAmu7YO9ZnJrDCb9VSJPRA==", + "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.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -1578,6 +1869,17 @@ "tslib": "^2.4.0" } }, + "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/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", @@ -1641,6 +1943,13 @@ "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", "license": "MIT" }, + "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", @@ -2262,72 +2571,185 @@ "win32" ] }, - "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==", + "node_modules/@vitest/expect": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.11.tgz", + "integrity": "sha512-VX2x5vNJXET47KAFzwERI+KRMtTTCSWTfSMKsW7JsUsXV4psq++e3DvZpuTDOpHcxytiDs6p2nhVb2tVDiiUYw==", "dev": true, "license": "MIT", - "bin": { - "acorn": "bin/acorn" + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, - "engines": { - "node": ">=0.4.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "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==", + "node_modules/@vitest/mocker": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.11.tgz", + "integrity": "sha512-2XJVD55d1o5AZous5CCGKS74g/riOj9odEt2bQpCVZeblHyHdnMeFl4jl0XjU21stf4mbjUkew2eXQZt65g5CQ==", "dev": true, "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.11", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "node_modules/@vitest/pretty-format": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.11.tgz", + "integrity": "sha512-yiZzPbGTS9Sr/JpFl8zHrcIkAofNbFV6k21vIgQN/cY/oxZeXhJv5sc/MBJ5jFKWmWs+oJHw0UXLZjmf931+Vw==", "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" + "tinyrainbow": "^3.1.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://opencollective.com/vitest" } }, - "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==", + "node_modules/@vitest/runner": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.11.tgz", + "integrity": "sha512-LztvUgdwMNJMIkj3hQnnxiC2Xy1zNxq928W/xhjCLaNCzqTZOudjwbQf6v9IntZGPw132i2Lq2rgTRZHD3JHNw==", "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" + "@vitest/utils": "4.1.11", + "pathe": "^2.0.3" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://opencollective.com/vitest" } }, - "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==", + "node_modules/@vitest/snapshot": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.11.tgz", + "integrity": "sha512-pN7ikn1ON7h8ee4gIAp4AzyK+zBtJPzVbqOgu5LCEh4VaJVbPQcgYQYJIMGQPXVeJJq1fnfazis7a5pFNPahog==", "dev": true, - "license": "Python-2.0" - }, - "node_modules/aria-query": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.11", + "@vitest/utils": "4.1.11", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.11.tgz", + "integrity": "sha512-apNa/prQy2qCeywhnixOHPRCgGNhvg7T4Dapfl1GahLp/R+uhBm5cPyFoNVyqsNd2h1nJxL6BqqdIjiABL60YA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.11.tgz", + "integrity": "sha512-zTCVGpyFsGWBhllOyKlTw/vnr6D9qxsfSDyfbyZmTyjHw5N/VuvzHpHoQjm2ZJzn4RJgx5w4r7V0er69CmLgPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.11", + "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-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.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", "dev": true, "license": "Apache-2.0", @@ -2495,6 +2917,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/ast-types-flow": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", @@ -2705,6 +3137,16 @@ ], "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", @@ -3226,6 +3668,13 @@ "node": ">= 0.4" } }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, "node_modules/es-object-atoms": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", @@ -3755,6 +4204,16 @@ "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", @@ -3771,6 +4230,16 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "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", @@ -3912,6 +4381,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -5269,9 +5753,9 @@ "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==", + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", "funding": [ { "type": "github", @@ -5539,6 +6023,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/obug": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.2.1.tgz", + "integrity": "sha512-XrsrhT5sybtKI6wakr2SPOlGZWWYbUXZ7a0jT8/QOeAPau+1X/bSegNe5YR75oJmEZQbKningirmGOEJCIk61Q==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -5647,6 +6145,13 @@ "dev": true, "license": "MIT" }, + "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", @@ -5677,9 +6182,9 @@ } }, "node_modules/postcss": { - "version": "8.5.8", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz", - "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==", + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", "dev": true, "funding": [ { @@ -5697,7 +6202,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.18", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -6050,6 +6555,40 @@ "node": ">=0.10.0" } }, + "node_modules/rolldown": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.11.tgz", + "integrity": "sha512-qpSwIyz0jHQq5qXBTNxFmE6664rJ7O+4TvPFOiOaBSrz8IOHc1koKKSqTM2H6u1UG1+TveuC6vaDHKXFOvb1Kw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.151.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.11", + "@rolldown/binding-android-arm64": "1.2.11", + "@rolldown/binding-darwin-arm64": "1.2.11", + "@rolldown/binding-darwin-x64": "1.2.11", + "@rolldown/binding-freebsd-x64": "1.2.11", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.11", + "@rolldown/binding-linux-arm64-gnu": "1.2.11", + "@rolldown/binding-linux-arm64-musl": "1.2.11", + "@rolldown/binding-linux-ppc64-gnu": "1.2.11", + "@rolldown/binding-linux-s390x-gnu": "1.2.11", + "@rolldown/binding-linux-x64-gnu": "1.2.11", + "@rolldown/binding-linux-x64-musl": "1.2.11", + "@rolldown/binding-openharmony-arm64": "1.2.11", + "@rolldown/binding-win32-arm64-msvc": "1.2.11", + "@rolldown/binding-win32-x64-msvc": "1.2.11" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -6351,6 +6890,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -6367,6 +6913,20 @@ "dev": true, "license": "MIT" }, + "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.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "dev": true, + "license": "MIT" + }, "node_modules/stop-iteration-iterator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", @@ -6593,15 +7153,32 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "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.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/tinyglobby": { - "version": "0.2.15", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", - "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { "fdir": "^6.5.0", - "picomatch": "^4.0.3" + "picomatch": "^4.0.4" }, "engines": { "node": ">=12.0.0" @@ -6629,9 +7206,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", "engines": { @@ -6641,6 +7218,16 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/tinyrainbow": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -6961,6 +7548,473 @@ "d3-timer": "^3.0.1" } }, + "node_modules/vite": { + "version": "8.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.1.tgz", + "integrity": "sha512-/bvH9E9tmCXRGp2uXY3WbOldqpTwFkbha/8ANaEQ6VkxhH60KyqLwgZq6lG2y+4uT55x9+9eUHMpQ7uGnOCKjA==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.28", + "rolldown": "~1.2.9", + "tinyglobby": "^0.2.17" + }, + "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.7.1", + "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/vite/node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "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.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/vite/node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "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/vite/node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "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/vite/node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "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/vite/node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "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/vite/node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "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/vite/node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/vite/node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "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/vite/node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "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/vite/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.11.tgz", + "integrity": "sha512-fhACrNXUidIbGSBr5FlbuBkO7VWC1ZyLl0DO4CU2DrQoAPxX84Ysxs+HeGQpii5lZWV1Q4gBZTTu49mF+A6Edw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.11", + "@vitest/mocker": "4.1.11", + "@vitest/pretty-format": "4.1.11", + "@vitest/runner": "4.1.11", + "@vitest/snapshot": "4.1.11", + "@vitest/spy": "4.1.11", + "@vitest/utils": "4.1.11", + "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.11", + "@vitest/browser-preview": "4.1.11", + "@vitest/browser-webdriverio": "4.1.11", + "@vitest/coverage-istanbul": "4.1.11", + "@vitest/coverage-v8": "4.1.11", + "@vitest/ui": "4.1.11", + "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/vitest/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -7066,6 +8120,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "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", diff --git a/package.json b/package.json index 662833b..c1b70d6 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "prestart": "npm run validate:env -- --mode=production", "start": "next start", "lint": "eslint", + "test": "vitest run", + "test:watch": "vitest", "validate:env": "node scripts/validate-env.mjs", "format": "prettier --write .", "format:check": "prettier --check ." @@ -32,6 +34,7 @@ "prettier": "^3.8.1", "prettier-plugin-tailwindcss": "^0.7.2", "tailwindcss": "^4", - "typescript": "^5" + "typescript": "^5", + "vitest": "^4.1.11" } } diff --git a/tests/app/home-page.test.tsx b/tests/app/home-page.test.tsx new file mode 100644 index 0000000..2eb59ac --- /dev/null +++ b/tests/app/home-page.test.tsx @@ -0,0 +1,134 @@ +import { renderToStaticMarkup } from "react-dom/server"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { Providers } from "@/app/components/providers"; +import { USERNAME } from "@/lib/github"; +import { + contributionsHtml, + FROZEN_NOW, + installFetchRouter, + jsonResponse, + makeRepo, + route, +} from "../helpers/github-fixtures"; + +// The home route is an async server component. Awaiting it yields the element +// tree it would hand to React, and rendering that tree to static markup is the +// same path the production server takes, minus streaming. The theme provider +// wraps it because the charts read the theme context. +async function renderHome() { + const { default: Home } = await import("@/app/page"); + const tree = await Home(); + return renderToStaticMarkup({tree}); +} + +beforeEach(() => { + vi.useFakeTimers({ now: FROZEN_NOW, toFake: ["Date"] }); + vi.spyOn(console, "error").mockImplementation(() => undefined); +}); + +afterEach(() => { + vi.useRealTimers(); +}); + +describe("home page", () => { + it("renders the dashboard from stubbed GitHub responses", async () => { + const alpha = makeRepo({ + name: "alpha", + owner: { login: USERNAME }, + description: "Pinned project", + language: "TypeScript", + stargazers_count: 10, + pushed_at: "2026-09-24T10:00:00Z", + }); + const beta = makeRepo({ + name: "beta", + owner: { login: USERNAME }, + language: "TypeScript", + stargazers_count: 2, + pushed_at: "2026-09-10T10:00:00Z", + }); + const gamma = makeRepo({ + name: "gamma", + owner: { login: USERNAME }, + language: "Go", + pushed_at: "2026-08-30T10:00:00Z", + }); + const { calls } = installFetchRouter([ + route.contributions( + USERNAME, + contributionsHtml({ + "2026-09-20": 3, + "2026-09-24": 4, + "2026-09-25": 5, + }), + ), + route.authedRepos([ + alpha, + beta, + gamma, + makeRepo({ name: "old", owner: { login: USERNAME }, archived: true }), + ]), + route.pinned([ + { + databaseId: alpha.id, + name: alpha.name, + nameWithOwner: alpha.full_name, + description: alpha.description, + url: alpha.html_url, + homepageUrl: null, + primaryLanguage: { name: "TypeScript" }, + stargazerCount: alpha.stargazers_count, + forkCount: 0, + isArchived: false, + isPrivate: false, + pushedAt: alpha.pushed_at, + updatedAt: alpha.updated_at, + owner: { login: USERNAME }, + }, + ]), + route.commits({ + [alpha.full_name]: ["2026-09-24T09:00:00Z", "2026-09-20T09:00:00Z"], + }), + ]); + + const html = await renderHome(); + + // Quick snapshot card + expect(html).toContain("12 contributions in 30 days"); + expect(html).toContain("Repos tracked"); + expect(html).toMatch(/Repos tracked<\/div>]*>3]*>2alpha<")).toBeLessThan(html.indexOf(">beta<")); + expect(html.indexOf(">beta<")).toBeLessThan(html.indexOf(">gamma<")); + expect(html).not.toContain(">old<"); + // Recent shipping: 30-day commit totals per repo + expect(html).toContain("2 commits"); + expect(html).toContain("Updated Sep 24, 2026"); + // Commit totals were requested only for the repos on the page + expect( + calls.filter((url) => url.pathname.endsWith("/commits")).length, + ).toBe(3); + }); + + it("still renders when every GitHub request fails", async () => { + installFetchRouter([ + { + match: () => true, + respond: () => jsonResponse({ message: "boom" }, { status: 500 }), + }, + ]); + + const html = await renderHome(); + + expect(html).toContain("0 contributions in 30 days"); + expect(html).toContain("Strongest day: No contributions yet"); + expect(html).toContain("No contribution data available."); + expect(html).toMatch(/Repos tracked<\/div>]*>0 = {}): Repo { + const id = overrides.id ?? nextRepoId++; + const name = overrides.name ?? `repo-${id}`; + const owner = overrides.owner?.login ?? "octo"; + return { + id, + name, + full_name: `${owner}/${name}`, + description: null, + html_url: `https://github.com/${owner}/${name}`, + homepage: null, + language: "TypeScript", + stargazers_count: 0, + forks_count: 0, + archived: false, + private: false, + owner: { login: owner }, + pushed_at: "2026-09-20T10:00:00Z", + updated_at: "2026-09-20T10:00:00Z", + topics: [], + ...overrides, + }; +} + +export function makeDays( + counts: number[], + startDate = "2026-09-01", +): ContributionDay[] { + const start = new Date(`${startDate}T00:00:00.000Z`); + return counts.map((count, index) => { + const date = new Date(start); + date.setUTCDate(start.getUTCDate() + index); + return { + date: date.toISOString().slice(0, 10), + count, + level: + count === 0 ? 0 : count < 2 ? 1 : count < 4 ? 2 : count < 7 ? 3 : 4, + }; + }); +} + +/** + * Builds the fragment of GitHub's contributions HTML that getContributionDays + * scrapes: a `` cell followed by a `` label. + */ +export function contributionsHtml(cells: Record) { + const rows = Object.entries(cells).map(([date, count]) => { + const label = + count === 0 + ? `No contributions on ${date}` + : `${count} contribution${count === 1 ? "" : "s"} on ${date}`; + return `\n${label}`; + }); + return `${rows.join("\n")}
`; +} + +export function jsonResponse( + body: unknown, + init: { status?: number; headers?: Record } = {}, +) { + return new Response(JSON.stringify(body), { + status: init.status ?? 200, + headers: { "content-type": "application/json", ...init.headers }, + }); +} + +export function textResponse(body: string, status = 200) { + return new Response(body, { + status, + headers: { "content-type": "text/html" }, + }); +} + +export type FetchRoute = { + match: (url: URL) => boolean; + respond: (url: URL, init?: RequestInit) => Response | Promise; +}; + +/** + * Replaces global fetch with a URL router. Any request that no route matches + * throws, so a test cannot quietly reach the real GitHub API or fall through to + * a fallback branch it did not mean to exercise. + */ +export function installFetchRouter(routes: FetchRoute[]) { + const calls: URL[] = []; + const fetchMock = vi.fn( + async (input: string | URL | Request, init?: RequestInit) => { + const url = new URL( + typeof input === "string" + ? input + : input instanceof URL + ? input.href + : input.url, + ); + calls.push(url); + const route = routes.find((candidate) => candidate.match(url)); + if (!route) { + throw new Error(`Unrouted fetch in test: ${url.href}`); + } + return route.respond(url, init); + }, + ); + vi.stubGlobal("fetch", fetchMock); + return { calls, fetchMock }; +} + +export const route = { + contributions: (username: string, html: string): FetchRoute => ({ + match: (url) => + url.hostname === "github.com" && + url.pathname === `/users/${username}/contributions`, + respond: () => textResponse(html), + }), + authedRepos: (repos: Repo[]): FetchRoute => ({ + match: (url) => + url.hostname === "api.github.com" && url.pathname === "/user/repos", + respond: () => jsonResponse(repos), + }), + publicRepos: (username: string, repos: Repo[]): FetchRoute => ({ + match: (url) => + url.hostname === "api.github.com" && + url.pathname === `/users/${username}/repos`, + respond: () => jsonResponse(repos), + }), + pinned: (nodes: unknown[]): FetchRoute => ({ + match: (url) => url.pathname === "/graphql", + respond: () => jsonResponse({ data: { user: { pinnedItems: { nodes } } } }), + }), + commits: ( + byFullName: Record, + options: { requireAuthor?: string } = {}, + ): FetchRoute => ({ + match: (url) => /^\/repos\/[^/]+\/[^/]+\/commits$/.test(url.pathname), + respond: (url) => { + if ( + options.requireAuthor && + url.searchParams.get("author") !== options.requireAuthor + ) { + return jsonResponse([]); + } + const fullName = url.pathname + .replace(/^\/repos\//, "") + .replace(/\/commits$/, ""); + // The real endpoint only returns commits at or after `since`. + const since = url.searchParams.get("since"); + const dates = (byFullName[fullName] ?? []).filter( + (date) => !since || date >= since, + ); + return jsonResponse( + dates.map((date) => ({ commit: { author: { date } } })), + ); + }, + }), + searchCount: (countFor: (query: string) => number): FetchRoute => ({ + match: (url) => url.pathname === "/search/issues", + respond: (url) => + jsonResponse({ total_count: countFor(url.searchParams.get("q") ?? "") }), + }), +}; + +export const extraRoutes = { + pulls: ( + byFullName: Record< + string, + Array<{ + created_at: string; + updated_at: string; + merged_at: string | null; + user: { login: string }; + }> + >, + ): FetchRoute => ({ + match: (url) => /^\/repos\/[^/]+\/[^/]+\/pulls$/.test(url.pathname), + respond: (url) => { + const fullName = url.pathname + .replace(/^\/repos\//, "") + .replace(/\/pulls$/, ""); + return jsonResponse(byFullName[fullName] ?? []); + }, + }), + releases: ( + byFullName: Record>, + ): FetchRoute => ({ + match: (url) => /^\/repos\/[^/]+\/[^/]+\/releases$/.test(url.pathname), + respond: (url) => { + const fullName = url.pathname + .replace(/^\/repos\//, "") + .replace(/\/releases$/, ""); + return jsonResponse(byFullName[fullName] ?? []); + }, + }), +}; + +/** + * Drives a promise to completion under fake timers by advancing the clock in + * steps. lib/github.ts spaces GitHub Search calls out with setTimeout, so a + * test that awaited the promise directly would hang. + */ +export async function settle( + promise: Promise, + stepMs = 500, + maxSteps = 400, +): Promise { + type Outcome = { ok: true; value: T } | { ok: false; error: unknown }; + // Held in an object because the assignment happens inside a callback, + // which TypeScript's control-flow narrowing cannot see. + const box: { outcome: Outcome | null } = { outcome: null }; + const tracked = promise.then( + (value) => { + box.outcome = { ok: true, value }; + }, + (error: unknown) => { + box.outcome = { ok: false, error }; + }, + ); + for (let step = 0; step < maxSteps && box.outcome === null; step += 1) { + await vi.advanceTimersByTimeAsync(stepMs); + } + await tracked; + if (box.outcome === null) { + throw new Error(`Promise did not settle within ${maxSteps * stepMs}ms`); + } + if (box.outcome.ok) { + return box.outcome.value; + } + throw box.outcome.error; +} diff --git a/tests/lib/contribution-shaping.test.ts b/tests/lib/contribution-shaping.test.ts new file mode 100644 index 0000000..b0cb1eb --- /dev/null +++ b/tests/lib/contribution-shaping.test.ts @@ -0,0 +1,168 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + buildCalendarCells, + buildLast30Days, + buildRepoPushCadence, + buildSparklinePoints, + buildWeeklyTotals, + chunkWeeks, + currentStreak, + daysSince, + formatRepoDate, + levelForCount, + longestStreak, + prettyDay, + prettyLongDay, +} from "@/lib/github"; +import { FROZEN_NOW, makeDays, makeRepo } from "../helpers/github-fixtures"; + +// Every function here derives its window from "today", so the clock is frozen +// at 2026-09-25 (a Friday) and the 30-day window runs 2026-08-27 .. 2026-09-25. +beforeEach(() => { + vi.useFakeTimers({ now: FROZEN_NOW, toFake: ["Date"] }); +}); + +afterEach(() => { + vi.useRealTimers(); +}); + +describe("levelForCount", () => { + it.each([ + [0, 0], + [1, 1], + [2, 2], + [3, 2], + [4, 3], + [6, 3], + [7, 4], + [40, 4], + ])("maps %i contributions to heatmap level %i", (count, level) => { + expect(levelForCount(count)).toBe(level); + }); +}); + +describe("streaks", () => { + it("longestStreak finds the longest run of active days", () => { + expect(longestStreak(makeDays([1, 2, 0, 3, 4, 5, 0]))).toBe(3); + }); + + it("longestStreak is 0 for no days or no activity", () => { + expect(longestStreak([])).toBe(0); + expect(longestStreak(makeDays([0, 0, 0]))).toBe(0); + }); + + it("currentStreak counts back from the most recent day", () => { + expect(currentStreak(makeDays([1, 1, 0, 2, 3]))).toBe(2); + }); + + it("currentStreak is 0 when the most recent day is quiet", () => { + expect(currentStreak(makeDays([4, 4, 4, 0]))).toBe(0); + }); +}); + +describe("buildLast30Days", () => { + it("returns 30 consecutive ISO dates ending today", () => { + const dates = buildLast30Days(); + expect(dates).toHaveLength(30); + expect(dates[0]).toBe("2026-08-27"); + expect(dates.at(-1)).toBe("2026-09-25"); + for (let index = 1; index < dates.length; index += 1) { + const previous = new Date(`${dates[index - 1]}T00:00:00Z`).getTime(); + const current = new Date(`${dates[index]}T00:00:00Z`).getTime(); + expect(current - previous).toBe(86_400_000); + } + }); +}); + +describe("calendar layout", () => { + it("buildCalendarCells pads to full Sunday-to-Saturday weeks", () => { + // 2026-09-23 is a Wednesday, 2026-09-25 a Friday. + const cells = buildCalendarCells(makeDays([1, 1, 1], "2026-09-23")); + expect(cells).toHaveLength(7); + expect(cells.slice(0, 3)).toEqual([null, null, null]); + expect(cells[3]?.date).toBe("2026-09-23"); + expect(cells[6]).toBeNull(); + }); + + it("buildCalendarCells returns nothing for no days", () => { + expect(buildCalendarCells([])).toEqual([]); + }); + + it("chunkWeeks splits cells into rows of seven", () => { + const cells = buildCalendarCells(makeDays(Array(30).fill(1), "2026-08-27")); + const weeks = chunkWeeks(cells); + expect(weeks).toHaveLength(5); + expect(weeks.every((week) => week.length === 7)).toBe(true); + expect(chunkWeeks(cells.slice(0, 8)).map((week) => week.length)).toEqual([ + 7, 1, + ]); + }); + + it("buildWeeklyTotals sums each calendar week and labels its range", () => { + const counts = Array.from({ length: 30 }, (_, index) => index + 1); + const weeks = buildWeeklyTotals(makeDays(counts, "2026-08-27")); + + expect(weeks.map((week) => week.label)).toEqual([ + "Week 1", + "Week 2", + "Week 3", + "Week 4", + "Week 5", + ]); + // Thu 27 .. Sat 29 Aug -> 1 + 2 + 3 + expect(weeks[0]).toMatchObject({ total: 6, range: "Aug 27 – Aug 29" }); + // Sun 20 .. Fri 25 Sep -> 25 + ... + 30 + expect(weeks[4]).toMatchObject({ total: 165, range: "Sep 20 – Sep 25" }); + expect(weeks.reduce((sum, week) => sum + week.total, 0)).toBe(465); + }); + + it("buildWeeklyTotals returns nothing for no days", () => { + expect(buildWeeklyTotals([])).toEqual([]); + }); +}); + +describe("buildSparklinePoints", () => { + it("scales counts into the 260x72 viewBox with 6px padding", () => { + expect(buildSparklinePoints(makeDays([0, 5, 10]))).toBe( + "6,66 130,36 254,6", + ); + }); +}); + +describe("buildRepoPushCadence", () => { + it("counts repos pushed inside each weekly window of the 30-day range", () => { + const repos = [ + makeRepo({ pushed_at: "2026-08-27T00:00:00Z" }), // first window, first ms + makeRepo({ pushed_at: "2026-09-04T10:00:00Z" }), // second window + makeRepo({ pushed_at: "2026-09-25T23:59:59Z" }), // last window, last second + makeRepo({ pushed_at: "2026-08-01T00:00:00Z" }), // before the window + ]; + + const cadence = buildRepoPushCadence(repos); + + expect(cadence.map((point) => point.value)).toEqual([1, 1, 0, 0, 1]); + expect(cadence.map((point) => point.label)).toEqual([ + "Aug 27", + "Sep 3", + "Sep 10", + "Sep 17", + "Sep 24", + ]); + expect(cadence.map((point) => point.index)).toEqual([1, 2, 3, 4, 5]); + expect(cadence.at(-1)?.range).toBe("Sep 24 – Sep 25"); + }); +}); + +describe("date helpers", () => { + it("daysSince floors to whole days and never goes negative", () => { + expect(daysSince("2026-09-20T12:00:00Z")).toBe(5); + expect(daysSince("2026-09-25T13:00:00Z")).toBe(0); + expect(daysSince("not a date")).toBe(0); + }); + + it("formats dates the way the cards show them", () => { + expect(formatRepoDate("2026-09-20T10:00:00Z")).toBe("Sep 20, 2026"); + expect(prettyDay("2026-09-05")).toBe("Sep 5"); + expect(prettyLongDay("2026-09-05")).toBe("September 5, 2026"); + }); +}); diff --git a/tests/lib/github-api.test.ts b/tests/lib/github-api.test.ts new file mode 100644 index 0000000..68efcbe --- /dev/null +++ b/tests/lib/github-api.test.ts @@ -0,0 +1,552 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { + buildRepoCommitActivitySummary, + getCommitTimingHeatmap, + getContributionDays, + getIssueFlowHealth, + getPinnedRepos, + getPullRequestHealth, + getReleaseCadence, + getRepoRiskSnapshot, + getRepos, +} from "@/lib/github"; +import { + contributionsHtml, + extraRoutes, + FROZEN_NOW, + installFetchRouter, + jsonResponse, + makeRepo, + route, + settle, + textResponse, +} from "../helpers/github-fixtures"; + +// The clock is frozen at 2026-09-25 so the weekly windows are +// Aug 27-Sep 2, Sep 3-9, Sep 10-16, Sep 17-23 and Sep 24-25. +// Only Date and setTimeout are faked: the search rate limiter sleeps with +// setTimeout, and nothing else in the module depends on timers. +beforeEach(() => { + vi.useFakeTimers({ + now: FROZEN_NOW, + toFake: ["Date", "setTimeout", "clearTimeout"], + }); + vi.spyOn(console, "error").mockImplementation(() => undefined); + vi.spyOn(console, "warn").mockImplementation(() => undefined); +}); + +afterEach(() => { + vi.useRealTimers(); +}); + +describe("getContributionDays", () => { + it("scrapes the contribution calendar into a zero-filled 30-day series", async () => { + const { calls } = installFetchRouter([ + route.contributions( + "octo", + contributionsHtml({ + "2026-08-27": 8, + "2026-09-01": 1, + "2026-09-24": 0, + "2026-09-25": 3, + }), + ), + ]); + + const days = await getContributionDays("octo"); + + expect(calls[0].searchParams.get("from")).toBe("2026-08-27"); + expect(calls[0].searchParams.get("to")).toBe("2026-09-25"); + expect(days).toHaveLength(30); + expect(days[0]).toEqual({ date: "2026-08-27", count: 8, level: 4 }); + expect(days.at(-1)).toEqual({ date: "2026-09-25", count: 3, level: 2 }); + expect(days.find((day) => day.date === "2026-09-01")).toEqual({ + date: "2026-09-01", + count: 1, + level: 1, + }); + // Days GitHub did not list, and days labelled "No contributions", are 0. + expect(days.find((day) => day.date === "2026-09-10")?.count).toBe(0); + expect(days.find((day) => day.date === "2026-09-24")?.count).toBe(0); + }); + + it("degrades to an all-zero series when GitHub rate-limits the page", async () => { + installFetchRouter([ + { + match: (url) => url.pathname.endsWith("/contributions"), + respond: () => textResponse("", 403), + }, + ]); + + const days = await getContributionDays("octo"); + + expect(days).toHaveLength(30); + expect(days.every((day) => day.count === 0 && day.level === 0)).toBe(true); + }); + + it("throws on any other upstream failure", async () => { + installFetchRouter([ + { + match: (url) => url.pathname.endsWith("/contributions"), + respond: () => textResponse("", 500), + }, + ]); + + await expect(getContributionDays("octo")).rejects.toThrow(/500/); + }); +}); + +describe("getRepos", () => { + it("uses the authenticated endpoint and keeps only the user's active repos", async () => { + const { calls } = installFetchRouter([ + route.authedRepos([ + makeRepo({ name: "mine" }), + makeRepo({ name: "mine-archived", archived: true }), + makeRepo({ name: "theirs", owner: { login: "someone-else" } }), + { + ...makeRepo({ name: "legacy-shape" }), + owner: undefined, + full_name: "octo/legacy-shape", + }, + ]), + ]); + + const repos = await getRepos("octo"); + + expect(calls[0].pathname).toBe("/user/repos"); + expect(calls[0].searchParams.get("visibility")).toBe("all"); + expect(repos.map((repo) => repo.name)).toEqual(["mine", "legacy-shape"]); + }); + + it("follows Link rel=next pagination", async () => { + installFetchRouter([ + { + match: (url) => url.pathname === "/user/repos", + respond: (url) => + url.searchParams.get("page") === "2" + ? jsonResponse([makeRepo({ name: "page-two" })]) + : jsonResponse([makeRepo({ name: "page-one" })], { + headers: { + link: '; rel="next", ; rel="last"', + }, + }), + }, + ]); + + const repos = await getRepos("octo"); + + expect(repos.map((repo) => repo.name)).toEqual(["page-one", "page-two"]); + }); + + it("falls back to the public listing when the token is rejected", async () => { + const { calls } = installFetchRouter([ + { + match: (url) => url.pathname === "/user/repos", + respond: () => + jsonResponse({ message: "Bad credentials" }, { status: 401 }), + }, + route.publicRepos("octo", [ + makeRepo({ name: "public" }), + makeRepo({ name: "public-archived", archived: true }), + ]), + ]); + + const repos = await getRepos("octo"); + + expect(calls.map((url) => url.pathname)).toEqual([ + "/user/repos", + "/users/octo/repos", + ]); + expect(repos.map((repo) => repo.name)).toEqual(["public"]); + }); + + it("returns an empty list when both endpoints are rate-limited", async () => { + installFetchRouter([ + { + match: (url) => url.pathname.endsWith("/repos"), + respond: () => + jsonResponse({ message: "rate limited" }, { status: 403 }), + }, + ]); + + await expect(getRepos("octo")).resolves.toEqual([]); + }); + + it("goes straight to the public listing without a token", async () => { + vi.stubEnv("GITHUB_TOKEN", ""); + const { calls } = installFetchRouter([ + route.publicRepos("octo", [makeRepo({ name: "public" })]), + ]); + + const repos = await getRepos("octo"); + + expect(calls.map((url) => url.pathname)).toEqual(["/users/octo/repos"]); + expect(repos.map((repo) => repo.name)).toEqual(["public"]); + }); +}); + +describe("getPinnedRepos", () => { + const pinnedNode = { + id: "R_1", + databaseId: 11, + name: "alpha", + nameWithOwner: "octo/alpha", + description: "First project", + url: "https://github.com/octo/alpha", + homepageUrl: "https://alpha.example.com", + primaryLanguage: { name: "TypeScript" }, + stargazerCount: 5, + forkCount: 1, + isArchived: false, + isPrivate: false, + pushedAt: "2026-09-20T10:00:00Z", + updatedAt: "2026-09-21T10:00:00Z", + repositoryTopics: { nodes: [{ topic: { name: "nextjs" } }, { topic: {} }] }, + owner: { login: "octo" }, + }; + + it("maps GraphQL pinned items onto the REST repo shape", async () => { + installFetchRouter([ + route.pinned([ + pinnedNode, + { ...pinnedNode, databaseId: 12, name: "old", isArchived: true }, + { ...pinnedNode, databaseId: null, name: "no-id" }, + ]), + ]); + + const repos = await getPinnedRepos("octo"); + + expect(repos).toHaveLength(1); + expect(repos[0]).toMatchObject({ + id: 11, + name: "alpha", + full_name: "octo/alpha", + html_url: "https://github.com/octo/alpha", + homepage: "https://alpha.example.com", + language: "TypeScript", + stargazers_count: 5, + forks_count: 1, + archived: false, + private: false, + pushed_at: "2026-09-20T10:00:00Z", + updated_at: "2026-09-21T10:00:00Z", + topics: ["nextjs"], + }); + }); + + it("throws when GraphQL reports errors", async () => { + installFetchRouter([ + { + match: (url) => url.pathname === "/graphql", + respond: () => jsonResponse({ errors: [{ message: "nope" }] }), + }, + ]); + + await expect(getPinnedRepos("octo")).rejects.toThrow(/GraphQL/); + }); + + it("refuses to run without a token instead of returning nothing", async () => { + vi.stubEnv("GITHUB_TOKEN", ""); + installFetchRouter([]); + + await expect(getPinnedRepos("octo")).rejects.toThrow(/authentication/i); + }); +}); + +describe("buildRepoCommitActivitySummary", () => { + it("fetches commits only for the top repos, buckets them by week and ranks repos", async () => { + const hot = makeRepo({ + name: "a-very-long-repository-name", + pushed_at: "2026-09-24T10:00:00Z", + }); + const warm = makeRepo({ name: "warm", pushed_at: "2026-09-10T10:00:00Z" }); + const cold = makeRepo({ name: "cold", pushed_at: "2026-08-01T10:00:00Z" }); + const pinnedOld = makeRepo({ + name: "pinned-old", + pushed_at: "2026-07-01T10:00:00Z", + }); + const { calls } = installFetchRouter([ + route.commits({ + [hot.full_name]: [ + "2026-09-24T09:00:00Z", + "2026-09-04T09:00:00Z", + "2026-08-01T00:00:00Z", + ], + [pinnedOld.full_name]: ["2026-09-25T01:00:00Z"], + }), + ]); + + const summary = await buildRepoCommitActivitySummary( + [cold, warm, hot, pinnedOld], + 2, + { forceIncludeFullNames: [pinnedOld.full_name] }, + ); + + expect( + calls.map((url) => url.pathname.replace(/^\/repos\//, "")).sort(), + ).toEqual([`${hot.full_name}/commits`, `${pinnedOld.full_name}/commits`]); + expect(calls[0].searchParams.get("since")).toBe("2026-08-27T00:00:00Z"); + expect(summary.weekly.map((week) => week.value)).toEqual([0, 1, 0, 0, 2]); + expect(summary.weekly.map((week) => week.index)).toEqual([1, 2, 3, 4, 5]); + expect(summary.perRepo).toEqual([ + { + name: "a-very-long-re…", + fullName: hot.full_name, + commits: 2, + pushedAt: hot.pushed_at, + }, + { + name: "pinned-old", + fullName: pinnedOld.full_name, + commits: 1, + pushedAt: pinnedOld.pushed_at, + }, + ]); + }); + + it("returns empty weekly buckets without calling GitHub when there are no repos", async () => { + const { fetchMock } = installFetchRouter([]); + + const summary = await buildRepoCommitActivitySummary([], 8); + + expect(fetchMock).not.toHaveBeenCalled(); + expect(summary.weekly.map((week) => week.value)).toEqual([0, 0, 0, 0, 0]); + expect(summary.perRepo).toEqual([]); + }); +}); + +describe("getCommitTimingHeatmap", () => { + it("buckets commits by weekday and hour in the requested timezone", async () => { + installFetchRouter([ + route.authedRepos([ + makeRepo({ name: "heat", owner: { login: "octo-heat" } }), + ]), + route.commits( + { + "octo-heat/heat": [ + "2026-09-21T03:30:00Z", // Mon 03:30 UTC = Sun 20:30 in Los Angeles + "2026-09-21T03:45:00Z", + "2026-09-22T15:00:00Z", // Tue 15:00 UTC = Tue 08:00 in Los Angeles + "not-a-date", + ], + }, + { requireAuthor: "octo-heat" }, + ), + ]); + + const heatmap = await getCommitTimingHeatmap( + "America/Los_Angeles", + "octo-heat", + ); + + expect(heatmap.timezone).toBe("America/Los_Angeles"); + expect(heatmap.totalCommits).toBe(3); + expect(heatmap.maxCellCount).toBe(2); + expect(heatmap.cells).toHaveLength(7 * 24); + const sundayEvening = heatmap.cells.find( + (cell) => cell.dayIndex === 0 && cell.hour === 20, + ); + expect(sundayEvening).toMatchObject({ + dayLabel: "Sun", + hourLabel: "20:00", + count: 2, + intensity: 4, + }); + expect( + heatmap.cells.find((cell) => cell.dayIndex === 2 && cell.hour === 8), + ).toMatchObject({ dayLabel: "Tue", count: 1, intensity: 2 }); + expect(heatmap.cells.filter((cell) => cell.count > 0)).toHaveLength(2); + }); + + it("falls back to UTC for an unknown timezone", async () => { + installFetchRouter([ + route.authedRepos([ + makeRepo({ name: "heat", owner: { login: "octo-heat-2" } }), + ]), + route.commits( + { "octo-heat-2/heat": ["2026-09-21T03:30:00Z"] }, + { requireAuthor: "octo-heat-2" }, + ), + ]); + + const heatmap = await getCommitTimingHeatmap("Not/AZone", "octo-heat-2"); + + expect(heatmap.timezone).toBe("UTC"); + expect( + heatmap.cells.find((cell) => cell.dayIndex === 1 && cell.hour === 3), + ).toMatchObject({ dayLabel: "Mon", count: 1, intensity: 4 }); + }); +}); + +describe("getRepoRiskSnapshot", () => { + it("assigns active repos to freshness buckets and excludes archived ones", async () => { + const owner = { login: "octo-risk" }; + installFetchRouter([ + route.authedRepos([ + makeRepo({ name: "hot", owner, pushed_at: "2026-09-22T10:00:00Z" }), + makeRepo({ + name: "hot-private", + owner, + private: true, + pushed_at: "2026-09-25T10:00:00Z", + }), + makeRepo({ name: "active", owner, pushed_at: "2026-09-10T10:00:00Z" }), + makeRepo({ name: "stale", owner, pushed_at: "2026-08-01T10:00:00Z" }), + makeRepo({ name: "dormant", owner, pushed_at: "2026-01-01T10:00:00Z" }), + makeRepo({ + name: "archived", + owner, + archived: true, + pushed_at: "2026-09-24T10:00:00Z", + }), + ]), + ]); + + const snapshot = await getRepoRiskSnapshot("octo-risk"); + + expect(snapshot).toEqual({ + totalRepos: 5, + archivedRepos: 1, + privateRepos: 1, + atRiskRepos: 2, + buckets: [ + { label: "Hot (0-7d)", count: 2 }, + { label: "Active (8-30d)", count: 1 }, + { label: "Stale (31-89d)", count: 1 }, + { label: "Dormant (90d+)", count: 1 }, + ], + }); + }); +}); + +describe("getReleaseCadence", () => { + it("counts published releases per calendar month across the window", async () => { + installFetchRouter([ + route.authedRepos([ + makeRepo({ name: "lib", owner: { login: "octo-rel" } }), + ]), + extraRoutes.releases({ + "octo-rel/lib": [ + { published_at: "2026-09-20T00:00:00Z" }, + { published_at: "2026-09-02T00:00:00Z" }, + { published_at: "2026-07-15T00:00:00Z" }, + { published_at: null }, // draft + { published_at: "2025-12-01T00:00:00Z" }, // before the window + ], + }), + ]); + + const cadence = await getReleaseCadence("octo-rel", 6); + + expect(cadence.map((point) => point.label)).toEqual([ + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + ]); + expect(cadence.map((point) => point.monthKey)).toEqual([ + "2026-04", + "2026-05", + "2026-06", + "2026-07", + "2026-08", + "2026-09", + ]); + expect(cadence.map((point) => point.releases)).toEqual([0, 0, 0, 1, 0, 2]); + }); +}); + +describe("getPullRequestHealth", () => { + it("derives weekly merge rate and median cycle time from the user's merged PRs", async () => { + installFetchRouter([ + route.searchCount((query) => { + if (query.includes("merged:")) return 3; + return 4; // created and closed + }), + route.authedRepos([ + makeRepo({ name: "repo-x", owner: { login: "octo-pr" } }), + ]), + extraRoutes.pulls({ + "octo-pr/repo-x": [ + { + created_at: "2026-09-10T00:00:00Z", + updated_at: "2026-09-11T00:00:00Z", + merged_at: "2026-09-11T00:00:00Z", // 24h, third week + user: { login: "Octo-PR" }, // login case must not matter + }, + { + created_at: "2026-09-12T00:00:00Z", + updated_at: "2026-09-12T06:00:00Z", + merged_at: "2026-09-12T06:00:00Z", // 6h, third week + user: { login: "octo-pr" }, + }, + { + created_at: "2026-09-12T00:00:00Z", + updated_at: "2026-09-12T01:00:00Z", + merged_at: "2026-09-12T01:00:00Z", + user: { login: "someone-else" }, // not the author we measure + }, + { + created_at: "2026-09-13T00:00:00Z", + updated_at: "2026-09-13T00:00:00Z", + merged_at: null, // closed without merging + user: { login: "octo-pr" }, + }, + { + created_at: "2026-06-30T00:00:00Z", + updated_at: "2026-07-01T00:00:00Z", + merged_at: "2026-07-01T00:00:00Z", // before the window + user: { login: "octo-pr" }, + }, + ], + }), + ]); + + const weeks = await settle(getPullRequestHealth("octo-pr")); + + expect(weeks).toHaveLength(5); + expect(weeks[2]).toEqual({ + label: "Sep 10", + range: "Sep 10 – Sep 16", + opened: 4, + merged: 3, + closed: 4, + reopened: 0, + mergeRate: 75, + reopenRate: 0, + medianCycleHours: 15, + cycleSampleSize: 2, + }); + for (const index of [0, 1, 3, 4]) { + expect(weeks[index]).toMatchObject({ + medianCycleHours: 0, + cycleSampleSize: 0, + mergeRate: 75, + }); + } + }); +}); + +describe("getIssueFlowHealth", () => { + it("accumulates the net backlog delta week over week", async () => { + installFetchRouter([ + route.searchCount((query) => (query.includes("closed:") ? 1 : 3)), + ]); + + const weeks = await settle(getIssueFlowHealth("octo-issues")); + + expect(weeks.map((week) => week.label)).toEqual([ + "Aug 27", + "Sep 3", + "Sep 10", + "Sep 17", + "Sep 24", + ]); + expect(weeks.map((week) => [week.opened, week.closed])).toEqual( + Array(5).fill([3, 1]), + ); + expect(weeks.map((week) => week.backlogDelta)).toEqual([2, 4, 6, 8, 10]); + }); +}); diff --git a/tests/lib/site-helpers.test.ts b/tests/lib/site-helpers.test.ts new file mode 100644 index 0000000..402a916 --- /dev/null +++ b/tests/lib/site-helpers.test.ts @@ -0,0 +1,83 @@ +import { describe, expect, it, vi } from "vitest"; +import { + getConfiguredActivityTimezone, + sanitizeTimezone, +} from "@/lib/timezone"; +import { normalizeHttpUrl } from "@/lib/url"; + +describe("sanitizeTimezone", () => { + it("accepts IANA names and trims whitespace", () => { + expect(sanitizeTimezone("America/New_York")).toBe("America/New_York"); + expect(sanitizeTimezone(" UTC ")).toBe("UTC"); + }); + + it("rejects unknown or empty values", () => { + expect(sanitizeTimezone("Mars/Olympus")).toBeNull(); + expect(sanitizeTimezone("")).toBeNull(); + expect(sanitizeTimezone(undefined)).toBeNull(); + }); +}); + +describe("getConfiguredActivityTimezone", () => { + it("reads the server-side variable first", () => { + vi.stubEnv("ACTIVITY_HEATMAP_TIMEZONE", "Europe/Berlin"); + vi.stubEnv("NEXT_PUBLIC_ACTIVITY_HEATMAP_TIMEZONE", "Asia/Tokyo"); + expect(getConfiguredActivityTimezone()).toBe("Europe/Berlin"); + }); + + it("falls back to the public variable, then to UTC", () => { + vi.stubEnv("ACTIVITY_HEATMAP_TIMEZONE", undefined); + vi.stubEnv("NEXT_PUBLIC_ACTIVITY_HEATMAP_TIMEZONE", "Asia/Tokyo"); + expect(getConfiguredActivityTimezone()).toBe("Asia/Tokyo"); + + vi.stubEnv("NEXT_PUBLIC_ACTIVITY_HEATMAP_TIMEZONE", "Nowhere/Real"); + expect(getConfiguredActivityTimezone()).toBe("UTC"); + }); +}); + +describe("normalizeHttpUrl", () => { + it("adds https to bare hosts and keeps http(s) URLs", () => { + expect(normalizeHttpUrl("example.com")).toBe("https://example.com/"); + expect(normalizeHttpUrl("https://alpha.example.com/docs")).toBe( + "https://alpha.example.com/docs", + ); + expect(normalizeHttpUrl("http://localhost:3000")).toBe( + "http://localhost:3000/", + ); + }); + + it("refuses anything that is not a web URL, so repo homepages cannot inject other schemes", () => { + expect(normalizeHttpUrl("javascript:alert(1)")).toBeNull(); + expect(normalizeHttpUrl("ftp://files.example.com")).toBeNull(); + expect(normalizeHttpUrl("not a url")).toBeNull(); + expect(normalizeHttpUrl("")).toBeNull(); + expect(normalizeHttpUrl(null)).toBeNull(); + }); +}); + +describe("getPublicSiteUrl", () => { + async function loadFresh() { + vi.resetModules(); + return (await import("@/lib/site-config")).getPublicSiteUrl; + } + + it("strips trailing slashes from the configured URL", async () => { + vi.stubEnv("NEXT_PUBLIC_SITE_URL", "https://github.brandonperfetti.com/"); + const getPublicSiteUrl = await loadFresh(); + expect(getPublicSiteUrl()).toBe("https://github.brandonperfetti.com"); + }); + + it("falls back to localhost only in development", async () => { + vi.stubEnv("NEXT_PUBLIC_SITE_URL", undefined); + vi.stubEnv("NODE_ENV", "development"); + const getPublicSiteUrl = await loadFresh(); + expect(getPublicSiteUrl()).toBe("http://localhost:3000"); + }); + + it("throws outside development when the URL is missing", async () => { + vi.stubEnv("NEXT_PUBLIC_SITE_URL", undefined); + vi.stubEnv("NODE_ENV", "production"); + const getPublicSiteUrl = await loadFresh(); + expect(() => getPublicSiteUrl()).toThrow(/NEXT_PUBLIC_SITE_URL/); + }); +}); diff --git a/tests/setup.ts b/tests/setup.ts new file mode 100644 index 0000000..c3237ee --- /dev/null +++ b/tests/setup.ts @@ -0,0 +1,20 @@ +import { beforeEach, vi } from "vitest"; + +// Date formatting in lib/github.ts uses the process timezone; pin it so the +// expected strings in the tests are the same on every machine and in CI. +process.env.TZ = "UTC"; + +// `cacheLife` / `cacheTag` only work inside the Next.js runtime. The page and +// data modules call them at the top of "use cache" functions, so stub them out. +vi.mock("next/cache", () => ({ + cacheLife: () => undefined, + cacheTag: () => undefined, +})); + +beforeEach(() => { + // Tests never talk to GitHub. A token is stubbed so the authenticated code + // paths run; the fetch router in tests/helpers decides what comes back. + vi.stubEnv("GITHUB_TOKEN", "test-token"); + vi.stubEnv("GH_TOKEN", ""); + vi.stubEnv("NEXT_PUBLIC_SITE_URL", "https://github.brandonperfetti.com"); +}); diff --git a/vitest.config.mts b/vitest.config.mts new file mode 100644 index 0000000..74bb08b --- /dev/null +++ b/vitest.config.mts @@ -0,0 +1,20 @@ +import { fileURLToPath } from "node:url"; +import { defineConfig } from "vitest/config"; + +const projectRoot = fileURLToPath(new URL(".", import.meta.url)); + +export default defineConfig({ + resolve: { + alias: { + "@": projectRoot, + }, + }, + test: { + environment: "node", + include: ["tests/**/*.test.{ts,tsx}"], + setupFiles: ["tests/setup.ts"], + restoreMocks: true, + unstubEnvs: true, + unstubGlobals: true, + }, +}); From 81aa04fe6716607c89aae847ba279cbc6b172134 Mon Sep 17 00:00:00 2001 From: brandonperfetti Date: Fri, 25 Sep 2026 13:45:21 -0700 Subject: [PATCH 2/5] Run lint, format check and tests on pull requests in CI Lint, the Prettier check and the Vitest suite now run on every pull request and on pushes to main and develop. The production build is left to Vercel and to the existing env guard so CI does not depend on GitHub API rate limits. --- .github/workflows/ci.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9d0e4cd --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,34 @@ +name: CI + +on: + pull_request: + push: + branches: + - main + - develop + +jobs: + checks: + name: Lint, format, test + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "24" + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Check formatting + run: npm run format:check + + - name: Test + run: npm test From 643681389f00d90bcb467adca49b54ae04f7e065 Mon Sep 17 00:00:00 2001 From: brandonperfetti Date: Fri, 25 Sep 2026 13:45:21 -0700 Subject: [PATCH 3/5] Point the README at github.brandonperfetti.com and describe the charts The live demo link went to github-commit-dashboard.vercel.app, which serves an unrelated project by another user. It now points at the dashboard's own subdomain. The first screen leads with a screenshot and a per-route table of the charts. Corrects the stack table (Recharts and GSAP were missing, next-themes is not used), removes the unused GITHUB_USERNAME variable, drops the real-time wording (data is cached with five-minute revalidation), and adds Testing and CI sections. --- README.md | 91 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 57 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 6e86132..8117844 100644 --- a/README.md +++ b/README.md @@ -4,46 +4,46 @@ + +CI -**A clean, focused operating dashboard for your GitHub activity.** +**Build turns a GitHub account into an engineering dashboard: 30-day contribution signal, PR and issue flow health, repository momentum and risk, and release cadence.** -[Live Demo](https://github-commit-dashboard.vercel.app) · [Report Bug](https://github.com/brandonperfetti/github-commit-dashboard/issues) +[Live Demo](https://github.brandonperfetti.com) · [Report Bug](https://github.com/brandonperfetti/github-commit-dashboard/issues) ---- +![Build dashboard overview: 30-day contribution trend, language share, and featured repositories](public/og-image.png) -## Overview +## What it shows -GitHub Commit Dashboard transforms raw GitHub API signals into a polished, scannable UI. It surfaces your recent contribution history, top repositories, and language breakdown — without the noise of the standard GitHub profile UI. +| Route | Charts and panels | +| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `/` | 30-day contribution trend · language share · pinned and recently shipped repositories | +| `/activity` | 30-day contribution heatmap with weekly totals and streaks · PR cycle time (median open-to-merge hours) · PR flow health (merge and reopen rates) · issue flow health (opened vs closed, net backlog) · commit timing heatmap (weekday × hour, timezone-aware) · PR throughput · daily breakdown | +| `/repos` | pinned vs non-pinned momentum · weekly commit cadence · repository risk panel (hot / active / stale / dormant) · browsable repository inventory | +| `/featured` | featured relevance score breakdown (pinned boost + stars + recency + 30-day commits) · monthly release cadence · featured project cards | -Built with **Next.js App Router** (server components for real-time data), **Tailwind CSS**, and a dark/light theme system, it's a practical tool for developers who want a cleaner lens into their own productivity. - ---- - -## Features - -- 📊 **Contribution Activity** — Visual heatmap-style contribution feed over the past 30 days -- 🏆 **Featured Repositories** — Top repos surfaced by star count and recent push activity -- 🗂️ **Repository Browser** — Full list of public repos with language, stars, and last-updated indicators -- 🌐 **Language Breakdown** — At-a-glance view of your most-used languages across repositories -- 🌙 **Dark / Light Mode** — System-aware theme toggle via `next-themes` -- ⚡ **Live GitHub Signal** — Server-side data fetching ensures fresh data on every page load +Every chart is a [Recharts](https://recharts.org/) component fed by server-side data shaping in [`lib/github.ts`](lib/github.ts). Data comes from the GitHub REST and GraphQL APIs, fetched in server components and cached with a five-minute revalidation window plus an on-demand revalidation endpoint. It is not real-time: there are no WebSockets or streaming updates. --- ## Tech Stack -| Technology | Purpose | -| --------------------------------------------------------- | -------------------------------------- | -| [Next.js](https://nextjs.org/) (App Router) | React framework with server components | -| [TypeScript](https://www.typescriptlang.org/) | Type safety throughout | -| [Tailwind CSS](https://tailwindcss.com/) | Utility-first styling | -| [next-themes](https://github.com/pacocoursey/next-themes) | Dark/light mode | -| [GitHub REST API](https://docs.github.com/en/rest) | Repository and activity data | -| [Lucide React](https://lucide.dev/) | Icon library | +| Technology | Purpose | +| ------------------------------------------------------------ | ----------------------------------------------------------- | +| [Next.js](https://nextjs.org/) 16 (App Router) | React 19 server components, `"use cache"` route caching | +| [TypeScript](https://www.typescriptlang.org/) | Type safety throughout | +| [Recharts](https://recharts.org/) | Charts, with a theme-aware palette from CSS variables | +| [Tailwind CSS](https://tailwindcss.com/) v4 | Utility-first styling | +| [GSAP](https://gsap.com/) | Headline and scroll-reveal motion (respects reduced motion) | +| [GitHub REST + GraphQL API](https://docs.github.com/en/rest) | Repositories, pinned items, commits, PRs, issues, releases | +| [Lucide React](https://lucide.dev/) | Icon library | +| [Vitest](https://vitest.dev/) | Unit and smoke tests | + +Dark and light mode use a small custom theme provider (cookie plus `localStorage`, system-aware) rather than a library. --- @@ -51,9 +51,9 @@ Built with **Next.js App Router** (server components for real-time data), **Tail ### Prerequisites -- [Node.js](https://nodejs.org/) v18+ +- [Node.js](https://nodejs.org/) v20.9+ (CI runs on 24) - [npm](https://www.npmjs.com/) or compatible package manager -- A [GitHub Personal Access Token](https://github.com/settings/tokens) (classic, with `public_repo` scope) +- A [GitHub token](https://github.com/settings/tokens) (fine-grained with repository read access is preferred; classic with `public_repo` also works) ### Installation @@ -69,7 +69,6 @@ Create a `.env.local` file in the project root: ```env GITHUB_TOKEN=your_github_personal_access_token -GITHUB_USERNAME=your_github_username NEXT_PUBLIC_SITE_URL=http://localhost:3000 REVALIDATE_SECRET=replace_with_a_long_random_secret ``` @@ -77,6 +76,7 @@ REVALIDATE_SECRET=replace_with_a_long_random_secret > **Note:** The `GITHUB_TOKEN` is used server-side only and is never exposed to the client. > **Note:** `NEXT_PUBLIC_SITE_URL` is required for production builds and metadata URLs. > **Note:** `REVALIDATE_SECRET` secures on-demand cache invalidation (`POST /api/revalidate`). +> **Note:** The dashboard's GitHub username is the `USERNAME` constant in `lib/github.ts`; change it there to point the dashboard at another account. ### Development @@ -102,8 +102,16 @@ Notes: - Keep it server-side only (do not prefix with `NEXT_PUBLIC_`). - `ALLOWED_DEV_ORIGINS` accepts a comma-separated list for local device testing (for example, `192.168.1.156,192.168.1.200`). - Fine-grained token with read access to repositories is preferred. -- With a token present, the app uses authenticated `/user/repos` calls and can include private repositories you can access. -- Without a token, it falls back to public `/users/{username}/repos`. +- With a token present, the app uses authenticated `/user/repos` calls and can include private repositories you can access, and it reads your pinned repositories over GraphQL. +- Without a token, it falls back to public `/users/{username}/repos` and skips pinned repositories. + +### Testing + +```bash +npm test +``` + +The suite runs on [Vitest](https://vitest.dev/) with no network access: every GitHub request goes through a fetch router in `tests/helpers`, and any unrouted request fails the test. It covers the data shaping behind the charts (streaks, calendar weeks, weekly and monthly windows, cycle-time medians, merge rates, timezone bucketing, risk buckets), the GitHub client's pagination and fallbacks, the URL and timezone helpers, and a smoke render of the home route from stubbed API responses, including the case where every upstream call fails. `npm run test:watch` keeps it running while you work. ### Production Build @@ -172,6 +180,11 @@ You can also run it manually from Actions and override: npm run lint ``` +### Continuous Integration + +- `CI` (`.github/workflows/ci.yml`) runs lint, the Prettier check, and the test suite on every pull request and on pushes to `main` and `develop`. +- `Env Guard` (`.github/workflows/env-guard.yml`) validates the production environment variables. + --- ## Project Structure @@ -179,14 +192,24 @@ npm run lint ``` github-commit-dashboard/ ├── app/ -│ ├── activity/ # Contribution activity page -│ ├── components/ # Shared UI components +│ ├── activity/ # Contribution, PR/issue flow, and commit timing page +│ ├── api/ +│ │ ├── activity/ # Commit timing heatmap endpoint (timezone query) +│ │ └── revalidate/ # Secret-guarded on-demand cache invalidation +│ ├── components/ +│ │ ├── charts/ # Recharts components + theme-aware color hook +│ │ ├── motion/ # GSAP headline and scroll-reveal wrappers +│ │ └── ui/ # Badge, button, card primitives │ ├── featured/ # Featured repositories page │ ├── repos/ # Repository browser page │ ├── layout.tsx # Root layout with theme provider │ └── page.tsx # Dashboard home ├── lib/ -│ └── github.ts # GitHub API utilities +│ ├── github.ts # GitHub API client and all chart data shaping +│ ├── site-config.ts # Public site URL resolution +│ ├── timezone.ts # IANA timezone validation +│ └── url.ts # Safe http(s) URL normalisation +├── tests/ # Vitest suite (fetch-stubbed, no network) └── public/ # Static assets ``` @@ -194,7 +217,7 @@ github-commit-dashboard/ ## Deployment -This project is optimized for deployment on [Vercel](https://vercel.com/). Add your `GITHUB_TOKEN` and `GITHUB_USERNAME` as environment variables in the Vercel project settings. +This project is optimized for deployment on [Vercel](https://vercel.com/). Add `GITHUB_TOKEN`, `NEXT_PUBLIC_SITE_URL`, and `REVALIDATE_SECRET` as environment variables in the Vercel project settings. [![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/brandonperfetti/github-commit-dashboard) From 9aa4dffd7d76f6164f2ba454d712ceb6bb803a2f Mon Sep 17 00:00:00 2001 From: brandonperfetti Date: Fri, 25 Sep 2026 13:45:22 -0700 Subject: [PATCH 4/5] Add a social preview image and use it for Open Graph metadata The home, repos and featured routes advertised /globe.svg as their Open Graph image, which link previews cannot render. A 1280x640 screenshot of the overview charts now serves as og:image and twitter:image. --- app/featured/page.tsx | 4 ++-- app/page.tsx | 4 ++-- app/repos/page.tsx | 4 ++-- public/og-image.png | Bin 0 -> 117418 bytes 4 files changed, 6 insertions(+), 6 deletions(-) create mode 100644 public/og-image.png diff --git a/app/featured/page.tsx b/app/featured/page.tsx index 4cba7d7..762c859 100644 --- a/app/featured/page.tsx +++ b/app/featured/page.tsx @@ -50,7 +50,7 @@ export const metadata: Metadata = { type: "website", images: [ { - url: `${siteUrl}/globe.svg`, + url: `${siteUrl}/og-image.png`, alt: "Build featured project overview", }, ], @@ -59,7 +59,7 @@ export const metadata: Metadata = { card: "summary_large_image", title: "Featured · Build", description: featuredDescription, - images: [`${siteUrl}/globe.svg`], + images: [`${siteUrl}/og-image.png`], }, }; diff --git a/app/page.tsx b/app/page.tsx index 687086d..9400588 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -45,7 +45,7 @@ export const metadata: Metadata = { type: "website", images: [ { - url: `${siteUrl}/globe.svg`, + url: `${siteUrl}/og-image.png`, alt: "Build GitHub activity dashboard overview", }, ], @@ -54,7 +54,7 @@ export const metadata: Metadata = { card: "summary_large_image", title: "Build · GitHub Activity Dashboard", description: homeDescription, - images: [`${siteUrl}/globe.svg`], + images: [`${siteUrl}/og-image.png`], creator: "@brandonperfetti", }, }; diff --git a/app/repos/page.tsx b/app/repos/page.tsx index 28e485b..2f274b6 100644 --- a/app/repos/page.tsx +++ b/app/repos/page.tsx @@ -37,7 +37,7 @@ export const metadata: Metadata = { type: "website", images: [ { - url: `${siteUrl}/globe.svg`, + url: `${siteUrl}/og-image.png`, alt: "Build repository surface area overview", }, ], @@ -46,7 +46,7 @@ export const metadata: Metadata = { card: "summary_large_image", title: "Repos · Build", description: reposDescription, - images: [`${siteUrl}/globe.svg`], + images: [`${siteUrl}/og-image.png`], }, }; diff --git a/public/og-image.png b/public/og-image.png new file mode 100644 index 0000000000000000000000000000000000000000..d522f03c935046b7e01cf983b540542f303c89d1 GIT binary patch literal 117418 zcmZsDb9`mX)@`z5+v%`l+w9o3ZQHgxb|)P>>DcMmwrzH-x6i%z-1EKnzCZS_*3Mq5 zR@JOovqp_MDv=6u;s~&~upl5H2$B*aN+2K*z)L^?Gyqu0Bnm$O3s7ezabb|^N&FMw z7ZFnpNi$hl5NhBxGze(8B?$Omw*Wu5zz+xrSUxBS7_bEW>u>phzkh`Q$p`!U8W8Z; zjoOSz&LALyAd(_NDjuNcx=@1X;#jGOvnt@{lte}Yo3#d4osZRZqI>o+gQ@-fm~5%( z*v40E51Zas8ptYwl@j(+@7>06ruJ8M_E(&O+q%PCuZI&^8TN;6k69c8L?kcHT|SV{ zN&-|=RJ1+5a9^pOXEB}>-yHZ`Q8HwQ(a?7B&*7XQsRW^s0U;p7e-$sHf;&{Sy}!79 zMO#nZ!9>4wV3ztZ2~wf6Vr(HKp`yjc#M0g!r7372P&3sz4v)+#Ar-Uki_Ca}1g57i z`#?gbKGXG|Np;HM6Gkmy2FCXiBR;sT7Auu~GCh4SDk=&DZ4WHj%sVj5@XFN5P4>4; z7=+Tq7m#8yeBsm2C*>$lhNDo&LS7cah>@k84KY%cm`lXLWayS5{t}LE>?%sk1#IR% zEqGE=Qfv%-L(%x~>NGM3=JT0TQ!;V0S!V1+rZ(|Hxn7P$9Yz#1A=>5 zO~9aCer19r*^GLu%IY&Re$=A>_C*OZW?#V4706ZpiL}4oFT3@5GU#pfL8@pDS%YbB z>_@+sq&f3Ifu+kFCiS~cY2y`evlC`gXq|4)X2~->f<|q(6E>f9cNQ&oe1+LYxnMJ0 zLOwroBh2m~J6|zOI_2Q^oOTCQk&FUpP<}vzpXT^I{y~8DftZgeEE*t0(2PhBoRdXi z!Ypg*T@NXblY#O08+#flTK$&uv5$qteh5lu*u8BY%3buw>c>%LCr;gUV^L!>jY7Ma zDB2$U#L0B)(R}IdrAoo@QkWz?WqhHp5NkzU9ZJPm#MNOQw?9wASy3IHh05?bs#)NB z>G3jIg`1d&NVD}0m+@=@D4!Mi^eO?OfOmaBt+@<>EHe*8Ut9PjQXAF5c zxd*@Vxu&@umLvMRgSYGYl(R|wj=?mAbmW8x!Ra-3R5r<2;9xVu1j2XHn=54u(-P++ z22xDv-(Rj5Xr-sPeA zI%$imT9rLHO26gdanaPgHI{EAdS0$VxmMR$l9)Ii?tIiW73sVb{$SnhHuhu5UO>&o z-EOHvua4y5%pKoz`@-7Vcp*PdJX|eTC&R}FOkTh3zJF+IklTO2iGG8}DgQhT7-agU- zdEFKqyvJ7IR%S4-CVB(o0i(L_{r=$${64#Qk6c{^-WkQYY~ZOYWX0&!n=Z)K)e7wu z%>->}bQ?9^(jvMoUY~;-6zRCS^)26I<8Q077Iu=pc^ni!ZiMq|*zc(=mf0^IhQeX= zNMG3d+jwA5dcW0r&MQ;E_l(aLRk$IZuM}5GOQyb!hpsgf@aDW+fanw~OzL*7RVdG) z9ocn#Vlx}h8bB|wArvUgla|!*`MofAhk9O|VpLYm+f4vWaObO0QE+?@4u98PmyTlS zT-#lo>Hx&9g=nWiYMTjp@5=Qz8sto6{P{fIMt@WI*>Cv=X?13%;LexzC0_~LZg@Yf z|B(#VL=r0hMp4awkR^* zSf~s$bQo*3Aa}tsj{X>1phk5C2m^sRlT)Q`vDtEmW#Q5tMZa2{j0Q`U?gNzG2kxci z@%v+-z2a1D6`+(e*aL3tzNe@E`aCAPGG~}3v8$Go<*T^!5742vXg4;mLAcs7eC&0W z_tC>}tgX;0vuVe?=gOlliR?*ZxG6o)3Fq&s(Obi!o-UWveaJH6e&Wh&^Eex6P}i`y zP%0MLs-`=KU8$!;`StvuiA^SsRH@u)bY|w7vHx;0Brd1hJ1jSFYnpCy_2aIKLBG(v z;O#YDH~sycaX&6MUjH(cZ>VP7$G~3ycx`7RgE`Z0DUgNmTVJ*`#@S3A6`@rk9tz9Y zPOaG;F-^r=XFIF%3WA2%xweRquH;XS)HWqzYwT8+_Z_~gBmS-r#D`ET6{@AyDw_fW z35}59cq~|wW^y&sgDYUTW%Bl%@0pBwcY@tm^`?7u$KqCUjdxPY43jQpCi2r3L#3VT z83nAX2=!2>xSy;y{mAE~o=8&Q23RlJMo0hV&wrMYm1W3Yzc{AsmVCHaX!YuB(%8bo z#JstlnJXv2cxD%h36K3Ch{xzk=nDyO0S^k=WIj-(ImDnjrQ{>FL@;_q%`AMeDAy+v#+_q-Y~O;&V1TUF2q}jv-xc&{J{Y*o%6gm^V%8bu?K0=Et)h z``FM{r9JX_+CNyxLaHwj9c64?B8pXXF`(?ID#BqpC8;ReG zMBDyQsm&t?x4y)6&uigYojpY^|Luz3$H;7j1DojZsjgP7aSv~&&x1jHgg;G6Li6|~iu}i+kZlgj_8wSYzJSh0c=)p`GH>* zS|~t^W^u0O?m(w{MhXn{8ZbwQ}Om2XLz#kln5Y4iKR z8e2v1IiD;RogrmQp}SJ*fb^;2Aqf(aLce?e5`+52!Q=i+pghQ|s6tGp*`!X;2bX3q zx_F$IKkZi&gjoqRZ92IZDa&lLc7<@L4bv0ADFQg9kK()C@ddoUG>ZM!)Y7WdXotFb zA4mMe6BQlE0N*&Nn;MJdi7z0G!<~N~O9Q7}LyS)tx>YtjH|~w1dnDw8>V#^9Mu<)C zHiGj*ml6NDCTO@Y_&zkH3l=Ee92p(^x|f$TdPesntjYTjFW@804eGZGo$_us=~43q z-v9$7)~4$=zE}$*(L#x|G*3I=Nj4CvL4y`5r`yO`!a`)}K@Z_n#rNbB5=^F2Y1hJe zuoXlqNuV1dGv&KZ=S6H&yB~!w>fv*R`%0>GeOI`dtYlq7gX#O?Ocyesk#}A5HX#8T z*?9V-w{vypD7f_i5Zo}09 zvY(e#8@f3UUpi||A0>we{5y7=$^AN?cCbF`m*!3h?0ks(uwAv7>`|8)`f#(v64aiR z-kRXw5!T|LI=wc^CBMM)ZnS>7jOed*_ft@=5l2o!>vZ@87hP<0f(`i*Qz>?oiXYt4 zo}R|?0A9bBO!m5IA}^Nj?C6N`Ptr^Smu0~(0BlQy5A-MvV`JlS9)%JyC*V>&tO7m^ zMvI}B@I3|Y1B!x<*Fb-FFFvOg(r-*h?>a{I{r<{)3T#KekB1K%VpE}2@&|JDYDa@m!m_TFoKX5v1t&`heMO`s%a>HDp;!cHC{W+_i+N)0HGCrI|`q zexZrHWe0@7;xA#hs(uH0v?HWp_ik{N%S}M_YE{0cYFgsy=hE)X%_GiBY`z>;B zzv;XCE^}yUsWAnXDT}KTyv4A-rp^WD5u?ps)@iv^hpE??<-HIDh!_gX5zk^TW%_B) zqr+~m zSqEKfC)-LW_|uZ{8!G&Mygr42BKb>tne+*&sgh>u3pTb&CF8Qxp-Ogq>g4=q?jqnz$jMAq-lJo6iVKFhyvW37O2!S4= zyqu&z$bv3!^dF!*f}8bm*lVQa*hgS+Wg<&q1V#B=Af*gXyN zLVGcMWiVG2t9lvm=^R_m3?RC_@qfQ8xe902w>wc1C706=)15e7DE}%^e>F_z(oxhc z+anL6sF%CY^?4#s+pz!A(Prm-6gYLiwz(JPYEBna&F}oteb)3vkNHSj2=jb)q6q!$ zYfJwQrZ-{^#C(x7#sN*?x1i?7v(0kJ;L`*SYZWjwa5=q}5wF8cx^UQK4f#(y_3S|X#R9ighgfH&T zJ+EA5Uyx2W!X&R>J3p7P9=YX&Oa=F$PA8vu*^QP2Hi4vS93%?c9wId(&+BB^}h@Vw$(wI-4UEo7)=@J zJ6K=4V0Vixc2&bf>qe@_Ge*81=;Wk-iTMKs?rX8}>vBxpUIV=zWGc|tZqfREl{q&C z={)$nQ`%XG<%il9s&&aEdHJAKcw6@;cGP=wkNTE>l6OF2xn9-i`iBfz^vEW~ra$xm z&EZ%;Go?=>^+|3jWFsFVo{p}V4_EMNXC)exVi{_EnTP{P;FIgwcBp_(pY7|REu)V+ zw6Pd{m+PxR5WO_zChNA}GGbJn{&C?#xta@)yXf(^?a}Q+ouoQ_2^pCfCYq1dyz%B9 zevI`ToYw)eHssn}FO;tf70K-m7n{jUZdf%3$X9{R^+3f+=vS896sm*i3!=yMo#nq3Jlj;~3JW_rqJ69$oK%QhX77aF`km47}w>n~0 z`F{<;s6vty6nqo@ttuvoVziuGr%%a~MjtnT&H&ht2csI16ek5-XmO~P-TjX7 zB;+6V6Ms^vBGIPo=l=k$p?JTBgZpf>p~oMvWctHEOoRa%2ZH5D82OWpzQFwL)ltp3RND& z^NcKbxOQ0uO05{RM4Iz<^0{ z$JPidU6~UhyM+cP|gOZf=#AxDxwgno=mB8`E#U%TQ@p)FwloCWT zdmVXD8hCSq#15NI$MQ58-DViCPZXWUlC7m1XCdr{;*E`LaA6(pV(#S(e0a3ZoUFVl z^}&7TY+L8&L9muCA$CFXx*%NG$^k|8F6PHGV34eOJ1Q|Ugf9vl=naGc_*~80G0P1w zMuHWxKN36_cszFaZl8)=j>4pnH8q`d)3P$j#hWNdx1vyKk41>+O8&B?bv4D_toeO9 z0d>f*Wn*Nsf2p=w7-T&c#-?EE`jV~lw%A{G17<3PG4K@^-ltZr=V?2E|01KWsgq)* zDz~PFp4ZWYp?A@R6B4FEYyJdoUlf1N9`T9E=zy!OKro#CJ{lyB|HW_Bo)bGV_C@I2UyX!H%YpqV7U-YxE%xuD8YDuve z#E5}mjp(xmfYHH9Y?ugpg25pC8^h>5coUQ_#hKv?+!~)(L4c~@wm0@3{&2RTO_W-T zLoW=emjfy$2=k~Ih~Ije$M~blN~07H#_Ro%;&p_VfT!!{&E0^%Jy@>IVRugpVwyv; zaU#0_@!L_03Efy4TwmNZ`>F=rt8GERXK5v1HY`lkK|9e96GRNkK+piK&(is05O&${ zo!h_)^6BCd>}Xpx4A^2gD~w8&awkuipc#|Jn!7 z^YRf!v46w*!n=oSJ6kI!#?VvYRx}a&FO}K=*S(i|cHzR$cFjjeG1L5h`?2M&=)C>= zETkYyxHk=0b7atQk$2bL+xslH9r&+i<=RK=-S2 zEIv9}6b%aAG3=zUt&7m5aNhM>@+Ua$U6!%t_QG2HwcHfHgJ;#2ANnFIxp^fK*##9M zFup7bT?O65jm;Vnmx_^UQR#HtfUv@QU-VJHd_Y32R?N2+;>H?4()Qf=W$KmjAmv%g z44`ArFSF1h5QEvg#@N=aXzv~1J)SL=&KK#DKA|#2Kh5l6c@)Y@ve;S}^tkUa+Hc-r z1zO%b+pYVKheCkf6brc#FnGub^#U%lTD<;K1i+Pw97rwP!U1uUuhQ8L*IY{%`_m>?yZ-!)$@o>v%CZ>SA$t zrJ?!ebd^X*;s2zui0^RYh>F>xx0|n5814cwCh9L}-fQJ^C@UI`<@(F}n)6*94C*%I zL^?jNHP=dtZZ(MZDUGe7HQ33xhT%Ja6 zJZi{*y-4M6V<%?m&^(##ETn7*A&%{R9l=6CNo2sEK~yRXOl)J{r^p75X^v?@4VW0d zwjEw^mdMY)yv^{;NoMWYeN4#jNJQ7|T;O*Nt^jSPWs3@~{aE_%zKvg-9p9_=D3z6# z?YXSchoJ#Oa=8_X-509}JlL;vTK$G&ZKl@hbVj;gQWp|_`TY2ODPL~%yq zW)Drlr#)+HI6t`EEgH?&1c=)5+ikVjSAMzSbm1@$$qlD7`a**W6Wt-_XSL+kg<=)S zHh86bMlUhrXZ<1O^jY(#VDv=A)3MA_zM7nf6S;WC5iI z=pZNHaR;MVH_#*7+^FMPOq^41I_D2WP^s-bX;jgxnu)9LOH@{m&+S@lP-Zzq+Cd+e zk>Ljq*KNPY9?N2SMAbeq0?XLmW~?-aj6Ir92ICBeaqg(I)$kDpWIF-*h|b6B*7{5- z7);w;GHiw4R3Tu2jP`C@b#SKHQ4MTG>1c>s%x=c?8q+Uj@;(0IQ@=;xwGf*kah8@P zaCnIKo^bFg-iR(D`t;bAt~_{nFc{Q+PM^TJk59-L87(j)Bo_C!sx@En>Ao#BRbQCM z*P|WTi-?W5uc>LmA-4|r+YX6sQ7*u&Fe&PLRaow3E{qy(p0{0uzxY|gd=g-OGin7g zZ@KHUE|Y%kskxk7mLK&K%VDd%)!Ri`NVVRJ=bs3nfGuc{?$=|khyFe#=fgm(KltiL zyNQZSpLYBav1!>-++DCf@qBfeY~=%{$-RQFYWG+Cw?g;rzXqQ5$gyfRkf6OjH%j$z zCxQSeuJnPrnZPYxO+I4{lgE`Ml{ zSX8`r=6j@51vKuqBjtp;C$`>Gx1c6oB~YDrlY;sB}bhHl{$3_um$R z8{Xid=&ZMh;&b}JY+dIN@cE`_M-xw$0SKZ0~{9PR?C8QXb9|*gXYcVEg5WNturY{-M)ocsLck ztPxz7I5TYcUM!1aZky5-NBWi3X1}E@22VbV7xPuK!;>b4NUR5ZEYqCh#d)rn%e;cRp77hxASws!*CR#+aQLO zX<)iIvn6zkKU}Rgpp%ij$$J}^^3CyVD8A8%vS_vV#yYG13;=$?$g*F}gKWqeI&L}iG%9)AL9JkgxJL%uKvt(fRt1jDS*808jzaOZuApWU0>Hubc+AE z3Mg~|CS8IbPqY?BIV=_Su19E2>oF~HD|hgDe&KDZVO1{2G&{#jnL<6QGvyz~Kzu2}pZTY{Wt8xLFqW8p}n)bHkUElLJ+M zGgDAMVjf5gT^}}UMX3z4UFz_wp`Xz3O!l6v9QmeZyBv=K+wmo++;gF3vAsbjqatV$ zH2e7OJ)IxV{fqveB{fzwpYHURu`NNl98(+toDC=%;hB7c7$RZL2yGMhb(D7=+f84a z$KHn08u5YpZ!f=$Q+oxc1r46Qy*3SX0U5wkz0PN3_8EueVc*pd(5I7~gdbeW;szkr z`vG#{^1599qK6VgKjs=JkRXVKG{9|LyRlMv5X*#jWU2ed&n3|SizIEA@sN&7X%GDG++bMPuk&@x zMm>zBAdu+u1zQ`qKi(c-Mt#1C+R9Y8nZvT%6(qfkajD)%429bj|7AF&_zUHOD4Yfl ziaD>f^kqP$qS&z2qJu7;a1$&Cv)R!_VxQmAg@N7VH%1ZwM|^v*%KNXh;&P2Q*V6qb zE%lY2n!&^VN%VTIhO+tO?>s@!w8BBz0gku9i#02PpK5Y)sKOx7Ev>1BoE_euOe7?z za|qCwe^G2-!Qn{?>hR|SG>^=*9W!9QYDknZO9{#HPpO!p-S&rYLcR9sl=EWr?2EMr zv7fT{B_cv#=I(^XKOg@rQCbWsLHCKjI#pbpq09g#T=jBRa(e)#$j0ME{S{n#OB(3h zAXu1`aP&-kuhPOnlQr%RqrG`$+3GUq=JT1_*JX9Q7R=MU3MfK1^SV*22y*p86*ul! z%IwTLLq*rR#^Rb!MUG9Fq$;CU02s_TBV8uF>E~PHlA0@C2d@djIYBv2uG6^IiBWnZ z${q;^32@$#(R{~|R**#)V?=7L)bGa}AO#T*;nMor4W$r>Xs9Q%M>XreC1=jknj|!f9K2vuKF5^L4;+a^h3=taiiVy z*lHy;l=9u*?+~a@w;PH0tPRn-RidsSB_kun2E|gJ@9yZ%FjFT~%n+3<2Bx;2^(&ZdQrCU|M~@WTU=6-d%#>G_~AZB}CBBe-t5%(V|&@Xkg5` zm^}24F(^w65sRKicjf21P(gr#iY6wB6vcJ1mw*!f&Ksru3M<53>)jyr00)mi6jO8NdM=L-ZAUbU> zqNRgld$R>6Feig>=M5)N2pl{!1gmQnBg4p8GCk^RJ=crU>daw2(q?iO#z8Ytuq$}zhr`_>-#OXkHjz7uVZLW?Qn#k?louO#JON5Dx+qXH!VOIN zYd}rk+9f3UE5l7)I(j4YJFLU?WiueEYgb<>carG=KxA= zDf#X+o4_1qGVre?*V{X2#gT=#VSaa=qbhvujaLhu-!3xiZF^D#mh9IJ!9D&&d`x6c z|5n5yPJ%}1x!k?OstQ3Dw_>wcT&Pfd_i`=whlSbbIs?Ir>lOYJxfcfaeTEIF6*_XX%&SNs4!c92+Tk7bY-6@YeVXJS7-dBXAM_tKxb{P&Bmf^7aH~j5T;^ zv%c;)tfC`#=m1wM)pidrBr!vu-0nZ#Cs(IBxNGq=R~Km|j6X(E0h*B@DBG1HS+^vA zSM;CyZVk~Pk7D-XYk_YFq!kM&7BpwtPX6*QCD1=j|Cc;S@Zm3MhqptC4$QyX@}DaH zZW%Vv%$o3=rsDr-iT@6B-{`)}6+V@M4w3=$I3(;azC z0+9`%^TjPgg1mCXA|!;k0XHx(kf>fcF2O&`P=H;+B_1smR#I{jlU4Bgr|k-%0Y6YW zM@subO}L2zdeN!)q@v>Fo|OxDqXH5W6$z?5*;TU$@G{`u%I=yWW3$k{pry60B)I-v z1Ccz5fsF}1VO|17gFq1hA6jWesZ;QV71kT7;VJjhHJfAyKZ>Yx>%yOc?a--%5cBpb&!o<^T_= zX5-<(8%L!wLN^80l&9&zq~ZB${c*+ryzl%foQdN4UA(LLlF#>gd8x0jGCNwi`;rdD zK7G&E~NJ9^VIHb)-x3LsZe;%Q+@-%Je^9wzYM+3I*S15}v&I~^JVB!iIRBaH6( zyl*DU5ngR7fhl}@)w=Avo$qPt_WZ*I1x7~t5M9!elD%ZhR|VZoqJ#Y|XVX6&JFVOa z__NwwNR{u9JlY)&79R|4uefU&!wcKjOzZ#{xc^|k$aFET3d0ldrr|M z;CvEsB4xX1lIlBUV(NGIm?F^_a@#M-g*8Vpmfn z#c8+f_-CShdsp;g$%ap*CE+65k~G8b;C3>9bjeP@PN4*L3lRFx#1_N>9ecz4VTiI1 zt|k`tRS8*e3YY=cDb24!HO-J*%r)x2h8|vfq^{*t>y@xzl5PQH#d( z5~}D$@aE|LoBZ8iBY&uv&5EzjM$@-RxdA54Hr2V+MYZRx&QEYy-}7;{x0`#g;0Tbr zn|#K<=Sw~&Xl0?aj0|ji1g(K}p;{yfG$6zD5i4BjlI36os|qp5L$o9m zL}Ru~{wV5*ix7~@r!T#k+s-G|d4cMxIo1ms>Gp~Ae`NZp4@1fe5{3~X&pjcj6YsyqhJ6$>eaLC&$>!8;**$%8EvYQ~5HJSkc$mqbO3 z4q1GkqwukK6#rhV9mnNFTo&VKA90*N2-`B{Ir=Pn;U zp!ABCc+x_Xitq1+Caj^<{u;M8GD7`=hKGh1WEv1ToEKZgi<_#`VQ7YH{wuOc9B zu2?fyjd1&uUF@LNmHx0~x^2tG-XDl2EC4b#VJUxYq>~NsH?`|J?uJ3Xq`1#sEwoxn zIgPH+G|OkVBc7fe>g=xV@WHN*%5_z1PWWncBZ|xG>egF9L59={ECRRK%P751jUpmq z6my9ZUKW(hLjNAq1PWx33MvyTyvJvjhg=g@>;-nF0sj|69#263x~gP?o=QYc3dCu& zI_(DLdsMz~ryO{82hM=kmopEbR_53#6c!`ja!UW3Ib%e`M>a#APZbqUb*esX;8(J| zb6)L-9r2sr&q~ci;NQ^4k(Hhv(aBZIO2tG) zg)=X%hYG`(C}k%EayAB!fFQ)Xo!vu|TIX2Y?q9leklOYAhgbeyAi6;&U$%C!S#g+V zee2ZFwtQ-8>M(cXaoMA}UYDhlXXjHIq@_>7z)d01j&AYRou_cCP>LnSu8J=7c|PnQ zIi{s>9re$X;h3m2{o;hUZ&Jlu02Ni1rBQsP=7w1fKJRwGsSD!|EnW1H{}JxA!F$~5 z1FkK!sb{A)pn;CMaUtC}b^<+3uEEs_QTFz~LW?A1A^A}pDG>MlMz&(k*WWFt;<(toS7j%eUEPse{r{|z@C8`qBE{*;?)Mj3<6g@R=t(FS)fLJ4NaYXzZF3sv?HnpHQ3w4g z8rKpppePOq7O;?q@7L3zYuz?D;qsonMx>xfGbZIVGno1CE^t$S{eOlbdIa`Y!wTY)Lkq)K6+FQmsP)1sh~X5k0eN#|LyGrK&X6@?)XNl z`;+ABJgQ!WL_woOGa7labOB!M{NCb`P9nN=Uw;&{XacJ><>?&8za3j}5G9{9jyaB4 z^I!f;#6XlU-j#6gfC8FoIC;Z511Yg2g%nAyfHOkTj8j)Q+ag$+e9HGO<|l^It;_-1Fgqt_Kg;&Ad#?O+8%GM#sj!zL62+_4j{ujc4aO z%7i%t1|V6P7@+3=PCGp-GxL-T%7Enep|p>%u#uCK)1d8cRnPJ=^Z<)E*PXV-Yf^qa zNNX{KGb?At%)1IZ2aGc`;Em%2U&%yuVEhbRLzsoSlv1Y@kj$`0(5zLotNm2o_on5b z`VFS_ku(yC*NlP=!<8*uS7>|Mqw=PT87rzpN*FyyI$_8*<7D0L$c3f+zG7rKRS(Hmnd_OS>^2aWW zqwp#__|Z{M1=*TZ-^K!lOXw|8l#antNV#YF^XF7_A<^FfC?N_2ngBZe$B)3+*p#kP z-3Q@lt=g!7n!b`RZ&dT6qxA|x+Fzs~OuBrY0X!LjG5Oi{TfQR1uG^=rH4rmOTv+%* zBP_G;F2{ArA%9VGxAdTg7FoC^7`qts^JE5Z^0JjdoD=wYHPn>Hd&1&-mZr+|!rX;w zV@jrx>7W6ibdI>=tQfIE#gyAA-)Q4X#DxCs`+*&if-WxboCH4c8A8`xkP^MRI0zrt zH{X2?d9HmTi=^b_U_Q_RL-wHBc#)FK1!2M@T*IiGjK^&*%B@`xprVry;zyShonQ&% z#0hNB*k-cqT+~?Cv|-O>V`&Ty*B~tKu_`nW{08toBk^@3THk>SocH<5XJ_PB^Or^=U z>-bccw1ce9ElX^Rr?d)Obk7weXD5QpBk|2@)#3N5^&`K3}7=)>T zE`6c3ppc#%I4`P%ijEN6Wrn2Em2d4>4 zd9^S5^)8l#s^m(09^95~K!MA@mbCyA@cyL|3HCxECTl0dP-vw1U*p+DRk+j{zB3L- zMnVkiIj9T(bjBLavlRv!3=4u)h$>P&iT=Z0%<-9RiE}^PwH)+%)WFJC3UgU{oPIic z6kxWNijSP{CwP_BX*4+VYDwy2@!;5VeuQJu`cGK!2UR_(MlwzLglH#j6!Mr~xR#7l zMDwaXD$=TyNfG}JzC_=kkB@8z1y8M1zl=uNeiyu}kKPLn72TGvFfwMKp^{UKWmi0i z7^ZY&qYMa&Iy14Nh0RD#4q-$UoPOKChHRO4jEKUK4aE+_FGg!d1055M@<1)D2)ll z3kAi36LSW8ik3rJub35L(AQIjfzd(*mjSqH%qXLk6kdiyv_nzSNN5Bx+N(m36wDu5 znV8alM?9Y#=;L=?YHpw)eQ2%y(_)614cvdq=ZzUKGo~IWtNmE6&`I@y+jx9=_ZBK* ztykq$z4c2v$H~Ox&*uA(VBI{GgHsE#kgG!}0|g2=G6WA1HK29-wrA(NoSmzq6FP-} z|BaU8QRbC^g*aU6eh>(306Qx5bDEM?LQVYjnmgfirhM)*?7o(+(ZbEN;&H?ql!%lA`f@n$yMK5skoBD-G3|?zC3;iagLaw{i5gnH2 z+jmCa%WEd(mU9t6Hu(!!+ph@n=-K6WRxP4X8DYVz$7LC6bN0-MZ{QjTqfWS?{wk={ zj;A!WBQd3HDRQb@?o0xqP8w!kl36$%w%!1x!S(?t5&F-FSgRbrBUfhS;%FbX?D{xH#2to?QH+f zq8LkazTolbIA@~RX)wIsrT?N`rtEWf`QCD6CL&^$%RWxqU+!K_BDFUS67$Pqb$WUE z>8T7(ClT=rmukD(0!bWV_E%C+MzUXt+zwxx48&mb=`Vm;4|?5JM~^Kr{VVCcE*w^` zw_jK_fO)FBgRNLXrn8|)pGcw(v-hSi?TFAJ)C~6!lkeg2Sir>SCWG>O2Se4fH*tBB zt`?|Ds#Kf8&sRwf;mi<0Owt4j=vtDLT~bFE~yb12f7G|BG2^NNZ>b999;30@}l5wzV{A||uDXFGKB9tnf+SkGXgY`!mc zo^qUYcwMMr!eJdeNj6X~*=#nA1P0@x9V6k7XFVW6ZRJCf(W`5@w zj~;we^+-Wk$k*g>BqVjY2ED_&!H5>A+dsz>(_*!T?j~#pp6}yzyY)FjtWrFG(1l{z z`B7Nn{pU3-G#;O4uIWxN`MI4+KzbZt8R=!|6 z_hSt?8N5&bkdDXcjdp%I&2GJI2tLZ^3?dtg0j#H}-PO|CGNW5^?*(lLAwDY3FvI8% zXq;X1Xlwu&hcn*gZd)vmC;LVHZC)&iDOx=0mEW!pQIxcESHm4_l@G7WvD>_oH{h6CNP#zb$K z5KQw6nT}>7w$$5!KD_iwZy#@&3_=MMO_gc@uWU5dJ~4R$Ir-OJ%gluImB;PP^mL zQLFUED7QCTIfvuzc|Z?G^wCtC_vg&8qq01~>gn$Je7$EOhJVe_i?PE%6vq1EX9KpI zUNE z8y+~kHccRVoDdGPl9Cp_fdwJ^U0;u+!^|m8N@0vmy!sx*HykqPF-jZA9*}8`j*qKw zQIpa^TT|YpN6vXa9D+d|2NX>zn)A+uFJwFK1Y((g+zoZvM8_Vgp5aH@%$Q zI-2Zh?VmH7aCfBNsCJ#hoWM0i246T?sq!s^bXB8$4#lv4Kh&jBw@qhs(?}$FAX;Ew zR8uC86*GQsFq5vi{9>h2)!}$%*95_#^)Q=~)iT47$>IBcxWVYUvwwM+vsSs)(%`To zY|&p|ZJJzBNjt*H2~0<@uyC9$dhc%oPh>V;2Yt&LJ2-!)nJbBb;>hK7oy@%FK@P1; zZ^UyS|G44d%`u|te6Icij&rG5{jfcX zkPSC_sKC@eTUuFZk>3kR<+ayCq`C|a@hyvdkooOSdo+#r|dEh7ip+>NiIw#2ns_=Tm(6-ROL;Go!{*|TX3BacK&6WLS?|K-=^7l_}C zL?1IVbt6%nlRfLvq<`zw9(gr?{`HE(BZvegd^~d1f-y%5D2`x=^1wjVSa7 z8Oq%ds$ITEpryATy4H;TEqHc~x3}nA^4sL#Q75JuRP% zrmS~=VL`83Zu2~3h{l}Ai@f1a8O>&F%r4jd%M)K=B4KD!$A#JrI`Bl?CtfaydUDp9 zb($@gDlO3ffZ*Jhr>Ylc{#;(~=BjGdIj}1jwsKW|w_RFjhqF{Y?Ii{%JbXqQBg6Kf ztr%=ZD-D6=_4X@^p$EF5Sb`YFLHcjPeaJl}#_g$mV66i~VRIqf%r$g;)+!=nP)_-3Siu@;OZQqkmdAxSO9%7fqa5R<6alIIz^x_+H~Y z1pFXJfO+GW`_uJCWxNn3R)^zZ%MIvSkH_x{u1dh&5>%j$T);vAbvHys2O8W;O%m|D zBlREW@tQQGYF`)Pyu=?dkC{iZymJQZNqnD5%VV99Gi&|nrty=s?aodb2ZJoQF0>|g zRMD~v2f5!zs|_-OQvD2AHDz3A<$@X6*m|cM`@?;$gl_w>z(Rr{y!}MPL~#bN4O3N$ zF*Y{)|NdV9vjz%V>B}15z`kAE9=LaPfbeZX*Hum~bLLgPq&_pkUO!9wXlS&wwsIA( zS^DUY!yzkvaV7*DFSk$}C3_j}RA*V^$UIi*%<$1aINy;YI4lY8>=2l!mYyokEM28+ z!TDFt^;Viy>usw*q>u8_;<9BXs zYg=|JH{9yEQ{6A4g~C2|kc$HvqZou>l*{2db)JqC6Ne<|y=;+xE1K+vm8nU9P$0VWTqA!PDEjv9ICKIy!k{>Ds{2S< zN$6Zkw@(}r0z$a`L{mU)Y!!m|-7M>gJ~3dsQ={6<%nlYeCc`-6rXC`zHpZD>@D3Ke zR`Gd~f>w>71rh_2DD(n$r|gdEYjTy~7}$L=^`Rd)y4kHowcQJXPD3>~f{d2gozBe@TkK4EmrlI@c2)8oE_b|KJy10k?*D1aJq9pFZnW|+u-JPVxr^yFTC??d1qQ3T z{mqq2-BqSW6l4D$X0wo;k5KDo$6#*t>Wx9NaU+G`69WPB7`WQnQTvwUgNZMMpWMe) zg~6RtY>tM5y+ZQ8gY@FtjF^>e~J}u=>MVV8yG8Lnr36$ zwr$(V#+q!3;@4Y|ZV5VoLd#bBy4z?9coR!C9!J66@t$!lU zPAOZYTy9AREVs7vNoWCs$r?>os>+WzfD0#TVLrq2zc~~cp(q&$$j}Vt_XEK=0<-l5 z0V!ic%A@WZq-+`&4-dxBMTEP$x;9l;H&<7~>llu!>{*B#5rf1)n8-V8dXkw-!WObR z#)7Y51&o?D27%k{D~|;fZ)Ko4*fux;>*#c;8{cYPRgo=6A&rTEnKsg~+?+1;ELU$n zGgo56MX?5_bQ|p>@5uvv0l?M=7j&l_M-IksZX!k%d}&rJ!^-E!TIH)Nex%m=z5Jyf zY20%e{J?oZw^_OAlL;UG@f{ed_HCcbZvjo=>zJAXy6Jv3T5&k4ko)bihjkYe1i(t# z*!I~{S!BcP7{#7|P$QKYpeZ~L=Ya^V4kg?&(wgNj&)SOf1iZ0Nh>i5wegri)?1ilH zVO!7{9oG1Xj?iW)rv^}$+fchXn~|nR$88=9+O!5)7-!UUX2JSg1R&VBGXDyM&)~3K z4^~~fhu$R$#LDf9ab0QXOQJs+$>$E^6Y!4>w=pJXmFI< zD@w(ZA0m6_!bF%ThL31c1cl1N!ebQ2CCwB!ib_73cv0cy*+?iKHJ6jR7jn|g8$;w;i%)GOCFb<+sb=sH zHS6Y&<4r3)o|db{M$|uCZBRv(*4rze;j&m9Wvv$7nMlT$v|JMJ_Q5|oSA*}Gu1CF8 z%You=ducHZQ4O^k9W*LxT(pR%=fMSn|v_$oEK>p__OWJNVDdGI(4kI%g+ z^j@ZLO!|6SL`zu#6KO)$mLW1tThKvD%Bb(b>A4)%^)77(62SzJ#r2piepn$D@1D>$ILC4Diy= zndC~rVznk>3nU{Ncr%(aDNP)%r|#(Z=ZQ zRuaMifq<6g*R!s%=Lb8xvro%ZWX=}A1a(g79sE0n<;i$oDyN|`lkK3$ol52d#`J`T zOkibVm(x$)Z`iwL3JY5XCW`XWMMWnBgnFW!m}F;L1@7EI2G%4V@Qs(0sGigFTi1_P z4Q*lymTaSofT&s6SR^Q+KEcfIHQ9t39Am!!LiZQdc50s?WIj8tyP-`Fz*m46wrxg; zU*^*_OG{^ff07Z--q-P$hyfEicKB_;VueqL)3}rD%v@kM z#KYwXz1`7oG6=( z8tnn8)HcgO`O$;|5U|I3gS&AB8ppRIYrpg9+enP6YxRh=3iJ84U?B$bC*tCsY$vJY zvF-N(aQ@W#KG_6tHhw67ao!exbh$!$d%)tO&ND#PfJKYkvqOcgW zb+x=!v2NFUxva>*lmrkbGcD~0(_%XOwcqeJI&BEL{J8j<<|IR{S|B{p5= zTCLbnI?nO+zLu~{K{H5fGAnvulLx}5>2Jf>XNW?=b1QYGBe9M=R{DgG-8}zyP=yRF zL3z{FHA=$KSL1VmHMBYds(@?gHO7#e0PYl;f+?Rm?j(`NBW#+FVCgO{I>;tD&bTOT zQV+KaOm1Qr@Ik6lks57%Aef^lAjrrI%=Y1-kw^vDg$i)sw17_%*c<*4KV)@#*qV_W ztHzG}58YxU30RtYA*rEt#wp{eR^y+XTEH+HQGX`Ug}Cz#91RK2^NQ;XEPn@0G^;Zd z@I;PWl`;^IHzEM<=%tYQH600E*2`PVODCj{6?B%&y5^37rnZnxXK4B7iz#aAX{`a3&bNN zY9PhIiij7S7cNsFi#m1tjR@;1D8L{A$c7-w>y^li^32&^BH-NKp#z*gW^0<7awNIw zLE0n%z@DU}!Rc7#mBTN`!ntM^ADLOVp=habK6mI#?B$RfP1Y~}l?#G0SAT-Al^MA+Bt`(GV=Gz0HbHHOPuyK}tpsu7 z;8k}Rn7h(9WC(+?Tr=eSOl8$H6Ta&b3IJD2HMrwO&4Z;KGwWb^dS|&f?2yE?I<2*) za!Uk}6@oET0mg)rDAM`EDQB$A!eJ|=_m)*7M9fbT@Qi3nhFW^`LwW0DHr>5!bAyr} zL!dNkAZ#m7(0>HiPQDf@MbLw|&!6n9-aNw?W6;si%TQNq_Td347EQ(R z6`zV!TAgGNia=ZJ?Z$G)svi$N4q;otOOjlUpZFK`v!*y^L9R@uoVhbUaOyLVu;kt% z&DVrskG8d@{1k^kN8;=fRn`Vonz@bXuh}t0h^mk0qEt{9_=lr=%pc9PIWKZ)KaeLA zt^1*4lpsP0BP@I7hCmv-TCU{LQ1D5aEnz5cPG%eUxlcu*eC(k9Bn$h>LXTu>ZVfdg zo|>7N1abxX8Ok&kiW-TE(sOhMuq0w(;TUh5n+FfV0BRE+7)+69%TNVv*bxhN;%1r@ zf)qgahsQ&ZA4IC8fgmv3`Fj;60}h|CR!v?jziKG(4@e+h?Zqj|PTb(B2)Rtl=81Wf zQ4Jy8SP+Hs7>K+f(7Q_^!!HmY(2PcY-)6rGv66TSmOd+Rh*!t#;Py2uW?h(687Bpg zj2MATWJCGA*}Ow~Ix_mdq3@XIlg>xeO01f1D*#I0u>1xNC;qZWiz#CoQ9vH?_kQYd z^8kNlZ=#glRom!&ZlCos3sN9??eG--KIBLsrzQ*Nj}eLy<&r&fqS+}qn3X^{EaD+u zTw9A`r|sGfkbc5wUUySfHcKY+93i>*J;$S=4y+4@c-BByZ?>YAd@Xxx;k|5^{(YG^ zZPU8<^7B-(Svs(V-SS~)q>BLZl1yl)j%{)l{a;(60k01!@zD+0jQa#@Q6gAU{$d93 zcix!zxeESSf`yd@3I|)zmgNPjjex+ z$IG#od7s|d%5VkNuV|A1$Hgo3?aX=&!Suo&sYtNc6UN^}L`e0=2!C<^!k#5L#E6Ln z;G=M;C@6oa%4341F<62E{MwuRs-?>@ormINhzyBh^Lod8A`>F#*SUCzil<0;4Dri1 z!P0|gqq9B9i~|Y5y&?tSs3z-PQQ{f1Gbc6X=;5a7(Mh5#Ozc9rh1-^<5q_jhYBNa& z1G>g(3HriTtU*E${)s_Vk2Z3Kd^QZt`KVedMBC0v;^b=Mrd=F*Gl(v)B@(6Qd+4vu zb1rr#g5S!ehY>LFw6Vn0JG8k3Wi>syWl`q3Zd+JFiC5uqv| z+Ssc4v85zZMV-8s4vZ`#Mo6O;5ET1QrLyg_eR0HrLFO$X8l4O%A4szjZ3c zp$4jO?kepCPzp_eG(cgDFJ1e?sahE1eDqC1s#LaK9V`1zCrxJ01US3xj)Vvz_;z0O zW4jdEdf&KuRQGflIgGj`VRNr)rWXS55FU^mqL``F9j~4rsQ1;6)a(^3&@tUu&j7ty92y8(&DOhnk}bN8YP-1bTz$*IG$MlCb@p%Ra0ky z+Zn_4F=sRCCxi@%{k3$f4bdr)aErg}eEQ~cSNiD1lpB1@#wy_9F`V1s>t>T}w!o7``h zU2bp;lAncA2SrAn76M5}O8@HPq)lY^l5&NUf0~#Z9a_WEF)|wiBc}j!h5-3_fKO|| z2A%@oY5|Bud|^WNnyV9I6)o_k&UdV=18^nvN6DB*giU%tK9pQQ_6q`LHZH|M*G>A! zCw8+AA`2ts${J}EbQNuCfo^h;;Bc89m80=VmCXTl`C4DR{H2K026@ym)faB9CvcLm zuj8$do3VO-)^o(2cDpB5_~z`VN{xQlKY!G#QMb>+)PruH=U(zrd#)Y0LlNN~~frWpd-H|N_-gdFh24m0zt&V~*4g9dS4*_j<)(0s@To`3hQ;PX5(6t+f3cT`$L@aC zl+iGt+v$g^HAolbaWTYQ((CfvUsCO@*W=u%_U?B@pug+;t)i*P6+Mue+dHMFUF%c| z*w8UqT%<3Dloa7d*QH2L3Hn++}~_G^5)8!!p=j&WdXsi*6beXC?WumX8)md!gM}5J`ia@b9cJDdbh1I)q?lK z3He`JY(f4+z+hgo44wJ5^&g#1!pqUSs%Q#g2%zkEA=biGHY~o-S^##NmE0QkZ#$O0fi=_pZPc;9mf^Q7S2%cB zSm;q})jgaouRBt*(g!g2Tz~K*nz}=6VWt{a3dP@J)*#?<5Yy0T|JvO7j*H3R0zEJ3 z!4}v9;LiN-@ql(KArAmc;P<#aGpOWD@nDShrF*{M=QFxf5XZaheRqGkKVkHK#Kp(K z8Q~mmcz6jL__Fqi zFOrdyJL&5xiHDAHRA(CcC+m45vQn225(+q1n7pf-o0q^Pl~H97x;^iZqW0$LHI*F> z-IDtF8a&}JQIN51RadbxS%9LhI_!uVUVo8J&CH`(1P24;7F z&BWp6r*~!|gC)B9KdKS7SYO2DM&)&1LtzLd&|H`1)toMnwbT zN|#kE-8(1ES0e(ip`EYaedz}NTXJ>t%?>nHpy=a)j=_zz-|{B6+AWpt@8Q5=%pHMn zyq?|hWo-suM~KF_{TFr)wh;g3I1}tCA$#=!-|*{dieWRh7tMUm-3ae~5`X@Vn_k+^ z`d=vSSI?R2ZJz6m!UC)|&E14}st&UtRE12OUb1%Qs#g8msL@w>A*i0W-6|iC<8$l# z9yacmPxvz4Ro-6U#OU;mlFGf$u)N5^fwN;;Sv)`HY1*o(dM5xd!AcfKFgdm<|7N$X{lCVt{* zHfIgIAAq{)(sJl_+iwQ#`VAHDMgs|6N3WFI7#FAmbYt)~UjbbiX%_soGw+DGSUj4? zekcB!oaoJh!$NO?SoX@HdJy+>8e$PQ$%;;My_CCEvuNo#Dg)Xu%A!g?lCNlw)nesU7 z%!UPn(=%IJRVMGLkcfGjZ2mkL1^9wL1!#CmJmTYmF8sa^uixBkstmfZ?#%Tc&pA(> z_x2+7`FRY(o`xqmE4)#hN6<8%CWO6bj8tC03Lxm(4mgZDaj7AzMKYWzq_v@WWPGBdxCB=P0ae_+b8OQ8`TRRR zD+WlTo>r+qabD?U7HVljy zV6Lg{y|M23D}mUDcdvLeZ+Ae}T(({UMeHYrQ}=6sZ&meS;`?1jhNic!^Ps99%@Gsc z%li(F%J}5e*xK?+|L}_6M02-pRSPgt{2;R_ zo~99V9Sv)FMFolK(4vr?&(V?YB0WgdOmj0W`J z$KmQ&`}SxZ1mw@_{&#qCH5}m#;Fw$ki&@KQt$C8-zc*!t`Xa$35%9xa@2W}bo}gIS z7|O%;3zqfw>%_$V@idP5bJ^4%%V>b+nC2XcY&aSn_}y$EEY}T#@f|YD&uG0-D3$>_ z5I{B~m7Ff}Vb4@!NDmAo=G7NB(N_K+&;C~g&{ZXuyh#4Xa^jjTon{79=BzaIcXu1*Ts;djXzWmz8Ab2+Df`z{ zOss>CC;A&X#Fsv=3oVx$VwYFlTBGDT2!fnU+vQIu4Z|W~C5iJ6)zf5j#=V zNZ(BNP#I*biKxMESj>PBBGx^$?;dDv;qmj*%YsqYDwVpS55s9W2}-aJ4WUiR)`qZ7 zIP12ir>30r4^Eb(hpmpr!?`=s+3j*VG?-9>nvGLgUBJqoaZ;q<%Hq=}- z+0o{yJpXOM=uCFIG3SJ@={$}q2J|c4j-0&tzDABVlGLxD);s}Urzy^0a86-QU0@;z zqN#-^YWz7#1jFc(*kcCWFXorU&-Wcz|99rS$1In$nu2Sg3|s_5@DU*(kRHEUAPSIC z1pAQ~Gp?w2c!Js*37=QQB#G5B{ZMHFey@r|AlMSxtgH+tjJ2=@=K6YVWJ3<1A?yNx zia-x)k1}$HDz~#O&aakplLES2Xr%HN1l7Bgxd zkVb=jGr+>AU`pX{6wlq9aA`_!_|W+pOT+CG6t9928wo3$kP5cFj_&+{`*3!mUYcej zCyNLnjz-q@Ozl`?BSSeN?br0#y6Njs z&54lfCl47nGq z3y3>5eiFHk9r|yg~EG9$9Q4Wz(5=Bf1*gMm9mv66knmb+UdrNEU;F$q{cY-mK1x(z+ z9@N=T^#fa8m^)y)!|`oq%b*mybVjcq#4zqb1iRS!KoKkOhla!cW_NaFN^uNyAz8D{ z_{Zkl=m)?M0==H-70^Ef2*|@Na5v4|&ID{{y396E3~d8!Y5E;vo;9}J*FZWUEd9Sb ze=e@@V^v>lck@^DeH+@lAWK5>b_T(o$c5vvy$=-Q)-cFJwOY>SR zlcl^wA@}j7knik1^a8!m)-=kCqk4_Iu_W3>lJ(U6_BqWwC0-sjzx{An4MM0;W0xN6 z9m0qAWGittv3-nDYnwP*lQ~;F`AeS5Uq93;S1wb^BZsF}kIb8k3F})Qj^UoilW?X! z{LGnxBR=9k(qtWoUxgY**O0`DD z_DM!x6+mz1jLeaU#q%|w(w2mj^w5=*hJB$BUBu(s+l12`iQeZB(Oy){WVIMjCDUhe z-iMEyRH!-=vfI;0{`u1UVqEz&_OJSu%K=?jYEeV|dkBVaD+f3QE|*{LmUUSD-#*Wp zbwnj(K{PJr)uaLviWF*RN@w)-Ta2ekuS*4GzpFLAnJ3<@Zecenbs%l2KYFkIB^b^* zE-bg5`l@*whL_?xZNRO5D-GMIBMdD zrvyKrAYs1u_LKTn9rklpF6$&s1oo_kkv?oJO~(XjrI2X-)+@ce;RvVRKNaF9&{f|? z>S9=kTkn+Rz)F4Jfx)l0^TpsW>aA<{`oio z4rq!!p9y*axQ-e%J0r(y?+AEm6vf3kG6!{YpW$)4J)b}-qr*RT&b|RJhy;*{FYkC7 z#XCx22oHn7ppZ$9kE4R4DJjBXDT+Nvq9OMO)^NO~uyV)Bf3Bj(5=2 zY&MrO*R9{LA9jLntG#&}-%ruxNDtmTola@xy;EO^WPg8`be|F`e_Vf zUf;RhBat4WS_mHwm~7Ra8@QZc6|!s_cYJ&*Wv z{V*CSY6AAGK15a2*ef*%vA4#2Ny;sq1vpsX>?q>w^C*A3H+*>{t?mTtrqRMJ?a+dm{gGGa%rsg=seGT z_xGCGudI3F(CN21j9N5_=Rxyu*fDZhKh>wJ)*t#&-7mOLDp_Y_02KWw_o(s;W? zwnlC)Sseg=4A@mh&F+mI1=lf9FBdPVo1k&f@oiSCvyLq#F;lK|8|#0+ZXabB&2fat zodevSuLQ6D!u8p;Zom!SujtsqJd&F;#G$6lZ>#5>*m5gQv+sP)uqtpvNc-K=?q)Wh z@3g5r{1p5?m7(p~Q5iH_*yX$V;Yqg8(kSp0{@!qikelh+x@JtOJLt3a96x-$@5^gz zM<~AG94(is?HqaOv2*@{I=f~Z9ecE5sYLlxxMyneg*Xy!FVm2iYSF{emyieL% zF;d;nZ?vp~DnrvQbuRAkdjeonp1U70i{A=FPPRfdF5UayXuH7uO^$z|yBEx11Y%TN z&Gi|62YqF{wMb0F&m-hepco8ZLxy>l%p7lCJWU#8H@qI>c0U|UPSR^M-Nh$47X$~f zIvzshKiy5%?!{P2YOnPl`lM-lNe7Qgm*xiZntYyTK;Eci3;KVZ>$iqj#7-tU72Hjc ze=I^7_}rRhKSx^#{Cq3c%TDI1b9B7Th)%T@F?!0@C_QYML7z#hIiO$apDr`-Iz1X` zCFWwqo@{aOTa1EOV^Wtxp+h?avBmR%0<1GWKU*L{JerifNSSE51&b?b{R#zel09NW zu9>70H`s^m0#n#Cm=p~KV<=UUjY4-FO-#tru<$TX4A-4jQSN_X%jW@UJ&m>IG~ZoMmWvZ*Y@G`(-E+HtU{1*0IXx{y&@+-?mn#hw&*2@C!V zujw?uxplC+q31+^soeTZlys7j1@(r;_kzu=R~Ik7&hK&DOygW}M<{vU=}tb(Kfhq~gcs9-|s6v%+P%355^XN1Iq1?=olLy1xJgQS|ca z3VI(`(4yyKw5k)gy&Pv@!+bB&)t54k$Ueka-sPllp*3^`48M@ z&3Vw4TT;?CIM4nd@Gf+9@u@NifSee4OcdxuP$7e5xzg`GS%+BRd|Ca)zGqgt9weK8 zUKKx+BeYsXMWZbej2C!e`Py0v%+lxjStIvmE|y*o%>K(`(!qA~stkrdx3p-xiXCaf)kiz5L|8ZtBS>^39eZzHEE#<=;kWNAI$iOK$Gq)c zEqBn*`{VtMRhnUUmjgFf!&5AeJel5Dl-d{EpSQ)|4BboTG-qjTSzCk7s1CY61=@}F zE^tMN_UpBM{$#Y>{aXua=@DXOYA1WKJbH5NprFB?U_6+CHm@UPKmLu<5vk8eY)i7l zK_ah%j&?mu9cHH|V|!XXM_J*iY>X~)Ndh^qyqR!v3>GKVU(yCmJ%IG&nwvW2J5H{3 zr(Db2dA&6=c?iSXrkH=X*=Tgmar9TUWfi3ZsK)n--Y3lx1_HHVb=sayp>1oad{$7e zBOQni#`o!Y0dm#?pHq1NZYheh^WcB?eZei+3{@hx1bV?<&mQeP@eX`3YG zz0IAd8Oacxw!M7B6-R~tff4g#A8e%U^d zHHHjpkR?vp_cdnU!>QtDec#0nhfp;YoFp-90N(&83%@`L7!zzOH*r6AP^Cn^`dVEx z32b?!G*THO9<62^u?9Fy%@+}@hI(#DJ(m}8`I_w~)XGpUwFRdDG8E_dIA7Frc$7qv zSrjs!*x<(SOGu~|Dlmx<*a#MI_Q%%l?k=zRZs(Hr*W&`s1`ednaE;y7)2LN~M~ z&?=PwU2%z7`Zs>o*DB|hRl*guE?5on4&Gxyf;f*Ca5)_AFM2!jAH8VH$pu!vi;lqu zEA${b(6S>Gss5!3dHUxjo{qhLdwNdma$%5U!7(0 zm`&KO^j*GpGqVEz*9ID}YVR8G;Bv2#-~6Rr83#9cQU_4nvflb^AHRW@>2Xt!Ny|o| zOTSP_zj`+mkVF8ql$;r?0>fU9&1=KWXpHxtnSH-RLNzy+UN0sWhG+^wr>6{ZH!)QEbsZ9rL6kb`-|Iy7!pQ9~^|6kM!63JdV?S`?XH} zMERfrZfjl?tQe=bSU9=CZiYQLqChsiF6O;a_YS49mv&PskSk1>dZr2MjRu$325b41 z(#CPk27Ex~^}`rBWcNpGkwVNMYn=o4GfvWcHIT;zO~zGRGGGyXMdCn`&`yU!TTR8K ztzT}@($TrH+2D@+f%+3 z)((~bmeZS0NZT5#(R+Z!esd7b7)GnTzYaf~8w}*vRNKW;T3H#rTpJ9_J1vyIN94=8 zHR_87`6$s{)9hG403U;E?B?cS@*MZ0->Hh497N2pfh2%5m`fmn)$Ar_>dLy(gIJk_8tOph*V~>jr{0J5 zXkP^PXP=naAW^`AhanW_oPhP@Hh_S^R|sceFymicv`bfD!0(hwIcjiWES7EvQ z{@)9%zl1{V?1c_qdH&B7h3Q2H&vIc&4S|cfCzavP6!osr_QL~2(qj0Z06kW9OxH?8WK~NhB!9Q@Y|JT1GQTyjl5XmoR#U4hJ2*9 z-9|Gl72IA4NN{l5=uS+}n}b5W@xA!JJ&AP!mH#lj5X5(lS;}9^JENSF?)%3y@zk;i zH~0I3dT=DM=nLNnOlFdd1t%)%(ZKVz7*HOR$ITE^gCv)#5;U>GJ4=oxUm>d^Z__nQA=ZLU9-bMW7VLMc@4oasdg%IS6Z-v>V+>I1 zN=SHhak@|TOYU#WP;_t>iTKug@ugjqUQS-{uyn-gOqtexi3N#3EvOGzTwV_i_i0!F z6$=gyhJC~Q{V*IF*Y@E6ole1L(z`i-jL0RnlJvPKz*7@0aS9?i>}Fg#om zpbP-~!g$w)zR~&4=HCdmOhlf?Y^Yiu#>vA>fV#_K|$=+APB{BH|EHa!Z-rAXTeDDfbp9T@TTc z*By7Y&)@IO++`ui8t8+baxGq-RKnW!C@otL!|r~Y8-#jxgPaUKXkW@^{VnL=bWO`UZe6CKPg_c(>)j!7h`?STlY> zVeM23bb_FIP^~9-43Nc9vRUYj*9m4OHZr#~s42w=;D`nXuO$}*0$N*CVYl`i*Qvx= z|6N|4VglMffm=M+T>5yQvb^e!v#z*{iVM|1*%g6GckqVjj4*~#cqaa$!M#>k_5Pcq zD3uwDr)E9!*mg1wI9ji#_(FszEV2WWiEv&4$JD$FQs~86lchV2qrV~v^=5#%GQ^guO=ELyno|7H523ns=WeXbJia?a-y$kXo z+PPo&d#q~e7!w>RQX22`sT+}P7|mi)ZY{uQxKwS59&r1*x<1Y3mMk1-K{;hyO0MP0 zN!0u%%036tidoKRLBYW<+Bd^w{%ojkwDV(?UWBdXdk4!QUlp59&S7sf6dL)2aO3Vg zw2E>OLa2ZK+6MB2*V@?Cs6qLw{;2x|dNoc8!bB6nPlCh!Z>DY|fS%TQ9tAJjL*H%J zn&*;aIIE>OGc0*B7v{-PZHN7 zoriZxY;-mc8)|grH=?s-*NP)W!+oQ(^k2KyfEB4OAqt@9k`Er~E%7Ww1>}^>IG~n? zpTnm8ib;z=o2HUk=&zpC!VBBW+xt7seT&T0N8^>WSkqk+RuO!n6M(YIQURdfbFQWWKm|9&`VeoyV)dpjaSfc$)+618y zC_|sZBpTqQ^RJG90(K10K;0M$7XwWF`3zWBDc7(ER#M-bN|L5hdnCw734Uf)K~UIb zfzUMEGUjl4qcB*6P&L%jo1}K7|9mYLxdJ^6VrV&8D}@w#qV&tQVehs|nZwoLlC?5s z-y1LJcew$IA6RBaV$Ca0ch8kMMMDkkXP3tj9`i$8H!abdJ5h>g6&wbl1nYCS1KR`+ zl-`R7$%>zFg|!a(aD`gqgymP~<${O3cT1s`){v^Qnjgx)KOWIXMUpS05;929aT_(` z0pd4SqoWk)6Pn#tkO>6BjZ^$HN*i6yNoTh+QaO*i8gL$2(t=fdi(^_!go_x_)JFk% zIPj=hLJ?0!qL&e;*;(@e!x}p3`5I(8q(3d!_LD1MD_xh%zdf4!Ze3bo9obI&kyx(` zG_tgSW&b=BC9l%A($pd63Mgd9^%m8P#Pz5NtJaWzw5hp4S6ii`PthxfN&BpCH?g0E zF<7bEteavU{jb74gaEeHR)HwbbP1I1|8>H@)TD(tQ4n+jj6>~W4E6vC>(5Y7w8r|7 zQd`yZWRlrqnQ4!XS~vv(D5X$wfgRoz%^HR&=I0gDWvZO}s{ICKm6J@#w&7>d_(N@p zEYU47;K1!sgz+hU*5D*401EeDP$689d&@P;AgpNznT-}QwI5tsh~$T7I!l<(+Uk)w zRR`=$W4g=J8}gm%_@6A?`IG|>8YFM!nWFnGO22>I8y%RkTTn!;hZK(Q#uG3VE+lR8 z4#v8gvH6bj6_t;u7**JWwuGUSGdDJ=E`I%J$@bR1_`e7V!6Nq^uQF9ZqoQXjvGIY| zRVYI&l@2_Ev`P3SBCu;UnT)0s+Yl9uE77> z6DgX!AEzZ#WJzaaAhkA2vZNJA=r~ExE9o!bOg&6YxsR1fyZH%!B^CxTuWvKZrm!$r zT5=*{y)LD`Kb+XBsVJFE7l zv}s~iBqbtqeA=!j`cl&`h}crgqp(#)twybjN$#zZV?yBntvtX|;sIHs&_RQat1vo` zrtgw-H?psB;g5s&S0Al#EI=SrY;KUjI~kYLRf^ca$yo?($72`q}HA_D;Dmn1eK)8%pWLd}(y-*c= zR%~`~cpc52awJ6l-ik^4zPv#b^IBtAmV22;VJI%2h>&KhK~$!gq{O@`dGSIrJ?Mu< zdUvRG8llZD6qWvwmwfU~2KBhqIB)lsSRqun)2KY`=*K_lCv1he?$NdJi23Kd2^d@% zWFU__=x>Swq)pGPV-gsQ42p8fWLo}a6=ZEAIvO{+)@laHT$L&6kM&Em z`RvUqedN2)4SmiZ1I)78FHPMrdokms#Nn^0fO&7)*4b)OD>;?s4$A@)Qrr{#1Sb?H z4B4t=G@nUS6yFN(oMn{6mIAx~N?@q>wovG1&<5MJdk#A&g}969Oxv$=JwnLZ#E7~5 z8{0&*FbQa0CX?4=GlGsK@$BPx+^Q?$M+_*2`Jesb-wHigAGy!^}cUm^{H9))I< z4puZ85|Zynxc}}~0Z>a}LKDI4n&NUI1^c@os|9Kk9mYkT=!~vinH)51fu?LtEHL_m zf4!X<%Qz<({v9L$A0Rp5VXM?kNp!k`a=4|`Sy=1xpQKu=J4P5*OEff7T|?S#=|fvF z8MZ!{eJ}Kyg0Un>tkYHKt7%Tp+Xi4Q61Uz<)u5X&W5{}4zbepTHLt)UtqWv! zWrdfLtyd-_Qgma})l86=9_?Q---Y(t2(-Zd1(=>Tq5QX9@7xmdG&x-! z2MbCbWbKLe#HF4;tgdRtzpl#CDw-Py+86`+ov&t#Ae!_xt0=3hYHc{4w+>^Xnza|i zb(Z_2q%g-TDi;^wOW`=2-z_k!!<*xW;;lF;CMI?6yy4k@gSjkVgXW?Z<+$SnP=Dl zaqe7oV4y1s4_lgpa5hzU1FkN%-2G!pCYBrr;IK*0h6>q?HO7m2tX68R8h8JV`kZQx z!GyNKyY?INOy~+`doJT0dJX3p+;R9?%(p*mPH2VOpgu-O>)aZ5`cku-eU=9WL888b z@9buSMn(M?3BHnfjxV!=_#LXEv>K(IKz5-&{auu8g2v;P^i9quEdqQdn0SiKJEP^J zI3v`+@^69cx+}6$RdQr{@x+7-B2KFo*`mzoO@67JUm4%SA3&;Ws!aMmv-wt_s)>4SMX`bu(&R3gALzJvd;4^)&u$6&=pw&-cV z;JBpSQ*#tkohcAQPiNdpRBUJ;toYF2uyg8^^3?v8Y9)><19^L5KY?3ph|_tINFR-t zzA177f|HfAIt4 z+!CmO+C+NRGMXxp!+{PPLs|Ai{ft&5ii0w?0WdLX30O2S`s?lV6Y90QE<9WO7C=3N zB?@N!7G1+QyMUA$w0^E2YS!6TE_N+qk<(GzT__AC%*)OMVFr3 zaBQPu&`3KYvksP+Vd~#j9+?QFc1k14yz-?|@L8Jv(@~k}s)^Hk45xnB z_CElTf)`}%0q3bQPCQVJ$<)b^PAI=NSqQ|f29(yMdwe!Eh*-v1i0(*_x!y<>ne19Z z_zj@OlZ(Y?6pLL^r3649x+IFXh=PhPt*WOOQ^7=a%7+DW8|^cAA=c}f3(n3|bBtIp zKITDhRV?oqO%x))a5XMHt=3G~XYkYFzkBG#W8OsHRh`XJt3hcaNp7mvR$A^3)wZG; z^vA84-guN6WvU>PI5UF&+9WJDJ%~-CEUm|QBW8;)C-yS!-QNHmEeMGj9WCy6Kv-eaz9UTC`b#KI`Z%;B7JkW;&=l$Q44?= zUcCAOpRRxSu7|BuF)uE`KT@FlwQ;j6864A82Nf-YP(#ydT!t^MptX3UhVD}oUzLfv zbK9u#xZAxwH>ImO{v-n<$fzqO?& z#qUhqM;lc9@4EH>B1Q?a28a7(0|OE$I_PJeWx$_GA|@9^QCY?vq8QPMJ|Z=yzJ+xl z$3?rkBIbsRGLt$ix{34evrJCNP_59*Cpc>x?~faDEt}`w@V2O-iuRYf@A3mZHdlWR z5kgDYe`H?O9m5$z&{GB6((~}4!EV}ON6f!IC4Z;OVw!}F+@z$pj7w#iwRk#T*Ja9c z(6X0c@$TEYE(%K_v1fE8R#r{LZzNP21glO>43&pBr?BEW6@SK#WZhGS$nyj>{u@M01LjSQsa$pVyJ`BfKHCpp$(c_{7EstM zB9$FFw8yR0Q<+DbzBab}NM*Ac$u2ASH)vjak?FpzaheJTK8$#r=3`D6r3yw}SeU&x z6st9usbin;@0 zC3knlXwy(7cw7JPPSE$42uMF7H8d<*4rhRXLWTpchqO@biY{?|_X%3u;wa)kiokcE2AV97y#+ClOV1x{Bp9)EW7WE_t=!!(@FZV6>r8Dl13i zbdUZYQP&(F=l68u#g{t+qR9yZLD|G^!t1N&1bWFpL=J{ zoS8XuAK@eN3J5{Xc*KZhV_d#x2iADD>8jld^=X;FWcjNGQwhBSH|3IzS=3Az%A=>5 zm|lHaCp|Wyx7bAt7KaIH!H00I{2_ah7JKZD9$V62e;8N{#@?khuOCX6=@unc1!z-% zYQ~~KgSjT}^4$Jed@HBMk|0|#Xn~wWg!B68R2E&b!KR4^o0WNKQ^^#cMJFY7Jf0#Q z{0Z~i$ELB((9dY?RCXxjhP0V=bcgt?E zC>0s9Jd5gJbOM%`*dV8vx;T}1nVMbjXDK;!wiyFpdBOKG)Tf~*MIhqPs8`wf#%7JA zL=~CY+GMs^mu?JZI_(0|=IjC=P{IlMl+6U3FB>Q7KiBd-D7%I|n#`7nYM%_3bMyZMekEH#xDI;lBPSTs%0u-i zPLfx&q~H3-j>9RzLdOlRjlB@Nz0CL~I8+HseBqu3k=I999bCk4I%#i0hdp1anrQX| zSb%6sNeDI-Aw163h9DD}_#>==$~9NI)C;ag(chIj9=E_wiEo^DKORD@l6Y_Ij0p*%s9W_FnvN4G z2hdFYwy@t@o@A;{@sktO!DWHnvbD>0Gjl99m0NO+BkJ!VA~l7QZ6qn+?o%)Xy}01S^p zmZMr4!^X(%jy(?xTv#0EXK{17t53o)dC+p;e1M$J{!bH!!0xf=Zq_xLiyO=n88~5_efGb6(GUiJ&q|lOMf|Cc{b|bStUW?B#J^kRz;$hYLR&wg>6(U|WP|C<$ z62nkKKb)0+#)RmW`&??!Gd(r$YQ^Uu0jh1!70IT>| ztoK7T>*IS%HP;A0HH~LGI*Otu&l9&H_=8tpC*|w=Y;oa*V^KSIEjb zSq4UzOT#lA@xYmS86+&{E9uEc(3t&3kvT-OT;Sx~r*ANeI-Aw#SGb+DNB!8k(4xX3yd52;54d&X6*x=SkNZE{of`y1;ctl*N2!-guA?F<7s7NmEhGRN-F2{DjTUbRd6GFKKs?pxnl7X>&*t_cj=O*^ywM zGhSASTyqM-+^uig)oWXEN=%l1rw}F_V2ikT_2CbQ=$#rvDm>VB_`4CXIE&$?fG*gz z?RXJgwmfaojYw2$xPyTKY9NaFKvP4U(96=0_6;v(*lrf~PA;i&Cx}$(m)(DGWoNLd zrgu{Hk)5rq2je=t!tTg8G>lnN=AM`2-QR^oV5$U{S zbZI%)Cfcbu*Z5MgKGc`Y8kX?az}p*$$4O8YNQ-nmIjTR5Z;(2ak<_LS_OE6Xlv$5R zwe#4TS7tZL_OORFO>#7s&AU$|6~tAE=ct5c-uj&^uuS z^y|^|W{^03cO2|$N{9(TN@>B2?aoDw`*yQVX}4}ABy=7AAfmWl2uOf`H71&_x`k7a zcv2J}Nl{(R5u`1`qDF^TuUA*p!0j7l!<%;(P*FX)(>7;>5YC{pHA_(&`DVX5rBhwz~*$Y9Rik8DR;u zvwn84Jb)jMk?NE&Ssr6GY|h=BaMq@5o>CX5%_Bl;%XsQ!&6PwJ6Qm7VYeNDYP1F;k zR^AU33K{;@pBRFeHAf7!Xq*|cN8NFRGQ)azXqH_Xq7oB9iD*A5YB4x$t>DYDzpx_U z;+QoLM(o`SijJHH!A?5AOlO~S&w5!jWPf#6n!T5K2Uz;gAOI6#%PEj* z1ZPJ1$an9D5U>S3N%F;R5O>aHWfY|I_(XIO_qk$6^VViS`jf&XwS7%3o-h}0T$4#hvBXQ8pD#&@r zZcYfFi0au_-g7m%NFU*)z2mWIWYVwoTY22^w8Zo^9VT4OK)#`6k-^Bch5XmXRUl5J zrA0MsXz1n{uTN-e%blntXU4L>)BxWpN&kfnaM)xDX_Lnt-i&I(aWEn)mIDi`8-;h- zQ(F$(&lk{Xc|h9rg?5xy|1fOwabE#VAayhTAtqxg!RqUJE^}*ITc>XbDuj?BIxIl# zIcU6%Z|N^CAp261f|y;nusCGog4=zqU+QLr^peDol3VgDRe%Z$apBlELI0^=QYHO% z1eHJm2<455X$yNEV|TM*z<91?v9g0C)!Yj#0!C+N;WG?48{R!kp*1B=2yJ1-1^^Im zKCe*O*x7O*Kyp)xszO_Ye7b9>De7&REn&uEl*vs)v7I*MAUiP3^U;$T`Zo*yHp;dF z9sWN$vq>_^<*#}foeK^&TEEK8quwqjIIy#8v1qo`5<3^BZ%Zuy(O9NN(qX`d;EfO< zgY~ThYfzb47fD){1Ry$`kdlQUABvEwPK6|^=k$M48xKuIfTCseLCNwx_1cA_PL($A z4>0;xq$d&*hs4Q4S-!iz`+G!B&7Fk2{H8c{-UM|P0sCBC#WpbiOPNy1-PhT$vYcG@ zg!V6eb_VP-_+&W_KQjNFbGYEQr*aR~%d950kGh|&mMHne2P8T|BQ5oun9a)qEUEJ8 zr=%P55Ue|L^QsQhpu-g*#D2?Gt0SjGD2N6t?_~06U#ELjld!EodHF4i5zVn$&m$P zZWz^muk05^h$uLEL>?3*kDq#RvWJksx$$wcoqGn>KOLi9_*-oJHfTc1%x;rbV;OaH zH*PUph>CTyC1VUY?_ae4b1=aVG|n9eUz-s{=F4MEpX7X9&0Yu65GAEBwR+z|#6oih zpB24S$9#~~Yj59g5k`e{!K^Cx{u6q#5{$f<}*8;=+bzZ*SI~B2H zx!Ufzo!(cu@NC4?ci-&i)^t*Jv{ECl{^6A*N~hD}w|a#*x$K;{nYA+fq z;pm;of@NB$Lnx5B?K`%{#tWUDg4Wj7BF3|#n~Q@L!g(;Vg?VY&y%;k$`_8mO{C~%f;+r4sB}&C@ zCKSHpq|Mb$w&Hj6gCD&Z;IbZit1E1DJNo8vdfjAltqFzh4@SS0r-Y>hbToU}$Uf8l zTk8*j_y}r3w2k5mvp*PP^a>yg1Rp?;_cNOP=gpvrcLdOsleY4pY7#MBt&{WZP-T<> z7)ssS;%V;ur&cm<^^zkic@q1Ea&oub5LUSzLOWv-ziz@69w61hIR19DMD)T@3LBA# zKO!p>?&})YSl^N6<7ZkBP}+)@*NXDCTSX6oI>`EJ1AR#dLhN9IHGCzj*P^ueafmr& z7J)l8MsALstUs5|%&OQ46dr=$tcZp2yW*&n_E_Os$#(^9aY~%~Hfk(vDKXeNC6XlS z!$7M60O8;Z@k~`{aCD#L^-*}X5y!yOPeXE1I?koe&%~4Ywx;E(h{%8Ygo4~)c}hBG zLgq@hak(@eN7hGypF^EFTH*Ud0@!ifyu@k%^2a7(h980J6N3YFZ&6Yl#qX)@wDH`d zZjo$O)n+xY2ua-=X#7Y|_$wlr#0aaC(n=9eTN`d=AN|k_4eeN($&QEF{T?w3Ds;@I zj$M&y%vHiO1yeCy9MkWE`8x)u#YDom?@H2QVWbQx@XRi=Wue! zD-gspt-+&b^qFJtg!{nrkecq&^CD-|=gl8tY^-HgE%f|dTFs^*kZqp*m@TbzW&)6+{g+531EJ;=$&~i`DPu9J#G_c7{hSAn zK2b-{GIirA&2Q+kYu`0yGJUHg+aUcM1o^)PX&c`B&AUai5Fz-QD|-Bsp$Qh<5h2Tm z;{$~}QJKOOnI%7iVY93Y)#cwIttkk-Rmz34^n|D4o6sL7=qX{#o?6WmZX(va(6pB# z=bB9m%4JKmMSsolW7HHYfvlL$EkhaV)3z0cKl4CED`0+-iBc%LeMR8Gi zTKe?WL+ubKh zmgPx{)A+Yc1XkeTW?e%o-IZ|SHGkp27zG$%@Dww;9a7Pz@LKECT3EPEQlfVb79|_; z@HX#{r*qoxZ%-A~N69tT9%2>PUDv^bC*jO9O+B<5#YrH^Ce?82aFy0fWJhHq6E)6pY>v2jluxOsf&c8Uvf;};;iwUPxx!e8wPt3*x#%t6 zkoK5JC_chfnABPl@zaF?3b>7`$Tn=)3^o2-5~#D2Go#j`ZsCHMf3o*bg)D{nA;(O_ zk7W~$+|*Ft?ub+}r-aFpVk~S0!FT3&8X~y%h*4ef?X)o9_s9>fD8b$kOZCG^mG6MJ zvy{UF+kz^XTwnLwqn$rw{jJ!bzi)u9sm6So?#D~DL#QxT1XX%u%q6UL0Bh6bS-Hu! znGln4M)=We;?TSU@OhwJ$(OH(CO>ZYq=KrdQZz?pO!?TZ)idaKosk@u$&o}2$7yF8 z7!Y=VHf=%*#w=8^(RT6!!W}vRU!|ViL$jM|n+dn29X_|58t9gKU}SQYI7Lk6HC41z zWxSGWL9Q_DYuQ-Pd8DxYHYC-=<<1{IPUwGbFar%=M35J9W;A=aQ~-g~3F0ex5lT<^ zIxyHsdq>$yGO&(kZrBj1_n*i7BGt%bIjP))80+9+ii&s{0Dx;i=S?QdQSzz1v^l*;RQyzM||uUOWP z?O&-)OX5Ro_SE*~e1cVeanY^-S$-!;!z25&x5~ATVB;Q5ilZV2nY8UT#WHNB4s_GA za<%EWz-yCwO27in!jq*dnjmMY7S*_41}~!$AtR~>#Tc=at>T_%sZUWX;E*j|A5Du5 zJ+uGK%z$T%;Y(en%09ke9;(R|qw1Ar1JS|*`6}co`4?wMz#suJxW`2fctP4bV!AeS zg<+lT82tXk1k|YVv_V&tqPHE9U&uouT>8Y7;TXUXYl0zkzc^30XhU$Y#%3l&<=|Ku z>-`WWRrtL?!rSWD42+G85D}Jm0rJ^ZY?$6Uz#@99q;kF22vV?C z`r3x7KMjy=P2vHEOLwEDwPS>zWxUYe%ITEQE_ivA`;(*-^mceM_;ZZIJsImoLvk;q zPPUSLxs>IFd*vW_Z4bB?gjZvO7hfjgX(~oDYI;Ze28y8&Yl^M?y_U#}ly3nn z92e|{qr>qJx7iF%oFGS#4wz4^-mGGI1>J_m*WCCQCJo#+el-)IPE!y-EY}QbYMN2@ z!^-+{vjc-9CwVmEQ$c|->^SQDt>KU2-oU&Afq^jv(^?R*p?q3gsr4W-_MzvG)3OtO z{eO5`nuCMIQ(eRlQ)5TR+6}>-Y`J1W_1$&o!J{Jo(jx>CIZ5PPvEK;Nxl8?dmFOQn z<~$mVXo!ue&gj+G^^Q77|B+HsHJ6I5pi{&}@r8*Lta&LMfX$y8?QrRtE0vOiM?`Sc zGi59B`t|9R`$?gcoNP~30YtdSrFgp`N2ACNNId=S;j&e3<9tiL;~6mTqqdlYd*FS` z&{YR>dNFLq2X?W5TLKPY!BKT*hyPv2JoHA&ybj^oe3=M(%se*vJg-TykS4e}LHf$k zJ&Ocr9&tFn^6NoLFvixQC5Pf2{7M~g``6CbyX%<6loJ>U5gHDVT&c!dYCYn26c!|O@fFu*a&o}qGAL$^3M#y-Dw~oN<^b$%qLY~~T zm3@Bp2ZuWdSO6EF`eRV_ckbdXWX(Y@dMHUAm&xRJcBC8oYS;&Z46609qv6CsNIAU! z)|il<1xvdNQ2+BG5_uHaRaJLMw}!^pk#W*_$eo>Q?nfoQ;=5n^fftI zzRg19(^O1t=|V13qU@b4n|RQN50A#9hdoD=8J5LEvhV0k41AG?;LK3tSyLU7HA`G` zhY0Rl#Rdk&goqF)T?!d;j*$@R=b77FNQ+KUi}W7-s~gR|wr36PZ@{IP;slEotSn3t zHoHFLA}<`Sgwppek(!zkW7spePedw?x~LY!dO%^rJDV**VPeC&vMLb{^N#C03@Aa( z<9SrQoc|Gdn^#Ms5-}QC|904l5UtXRfKh|lYBvPBj$_vjy9wh0eEOj7k;=>8nJDEM z7<$RB4hkEjl^>zP=v&|t&Mbvv3UiC zts}$|_T@@8pguu)g$Higo`0H|kA*EjGTD$Rdh4GKv7Zc#P?M7j5+ID{z;3p`!)y#{ z{A9#$rpo{hj5~VBCTH{OjRb-Hhu<0UtW{NIjh&s1#gkH0aX*#GOf{HowsMU2?CEN% zKLHiR0h+43kW8bi(%yEt2C9W=;^cvll@*z~;B*HBJ?UoLYGqKlBfoPnwmpDfEeMP~ zTq2!@+r?-X5;euz?nlz$>azhyf%Ua%0*PIS8JAEl3sI_XC zwg+yE8tUrhRpeXc9a>s=f`fv^f-Z5>rdL*YML$u&A{eerf%>xD{i{rgWR0O*P`46m zMj^nl4U9|z`#8WC<+%P$AS@t?Sz|1fR}<~9c&ME}U=~kfA=K@z%mt!l1l#o6%Waqi zo8n*-DbpI5091>-BC^pauJZ$r%UmGUXnvS|(L)K*1aXJe!J?>e8HdyV)560WEFQ#r zfz*_RKOaPPc=cDp4)iO%Ct^J`*W^0=Hk%))|1go85LhqI!6KM2xVdCmCsr2*0^)GhvMExQ>cw>Zf28WJF}8q< zqg4;9!|WR3<+HrjghpC_yT)T{;QO!j5r+br%~%QGbiE4u0NnyK>$iaY0gR+$cK2tf ztCqNnbev{QvF?!89WyQ)ZTL9n>>s$zLY#ogkA^1bMP@}Do`IC@KkX3GnrAaplqnNK zFpQ~rDrr)4RL(gBS?A8E9*(3poZ zY&unXu41@>jOw|v+M8dlAo2$K++&=#Goi61WZ*Gi*!`G9R3kO=ob~z1Sy1&A5cL>2 zgdnmTKOLJ^(2Ek$x|49i+!EW}OL#$SY=S0%UTx-g_ zjHH6znrDH6YkYEPgy9dkaJwPqD}&N^ns0K`m6C`_}`p2#3=B6D*@ zH;;8ur^i%fLQmzW;tR=ZwwGkp1&5H(m1c|l!Qzwx|5X%#y}a3G>BNXvYKjFl?3Crk zuZ>j_GpXUkgx8W2HL z{#h=-$viQy$PmSWc^&Xy@YOI!i8}&=lZy2%u8XdSmS}JC5TF5NNh4FQsT^4DinDt2 zN|-iis^o@1eS3F2K{S-Y1UU=IShnzBt)S3^_Dc33Bc4QZ&Dd*N=s}^p33_1{$OHQzR{4!i>;iocF7>EpBE8L*;p8t|2h(|gN}kNZ8jLTH=`1%LtRg< zB6^HDkthSELmiTHd})-SoWZ99@*yTRXLiu?b=B$+So2AA3ljv=kj;~TjYGn14_=>ORzM8J1GA?>7>j|<_ zFS^o>mv{`7XG$$kh|i8OTbL3=uSB^uVvCCSh`LfE(EnCl1A2=TKZFpj6@>rsM^+VE zycB}>2|p3NR$@8U5w$#PF7XwBX#qkBW(2py97{#U&#Z?ri;HNj0|-_#qdkN>XK(>d~vwvLoPTptW7YI%D(pq*L5fhNz9A z6H38I(RYf35Ct+c_m;huqQ7TYMto`MHSYEMD_jJoa{OS`>DKKV&3q~nVu!h*Mz*lZ zd~9?owv-s+LjVll9NfQtfe2A-{jFqDJl02>0%Kokla*jtQg@H0Y;9UT=6>(zC)&%N zy$v~Wwz69YfPyr&!;AHLw*mDUomWkGu8M7TyKGH@?oYKq{n>e1|1IJW!UZXC^r+Cp z3dJUJ8UKranf5ZGufnpqRln<&N2uw?<3kaev8tvxS_E;(w@m@A6$Z?z2_VkwbZ9_e zK3nXyM!k)YnrMegLMlPmK8>%n^0C=nx$;!3P8pTjXKI*?W6(d_XudPUz5=lOOSM3G zWV5lM*EA4GzzPzr_}@;qkZ!UVvewjo;*|VU)v9r-Yfw-6d(na#s1bfM0Qt|vDuF6U z@W)k4RBW|kHQS0secKwtIXSRAvW4Ubz=<`EKczYOC}CE96k5>kN$AWyrBIl-Rft~`Ej*IW`Wwj zISaC{3K{aj*m2sP8Rf0Vcx+@sJGNsPs-rCRn$7)!B5ReFD+i$Z{RDzSI*NIKNtgU9I>_iknj!+Bs0W4x6uyXbW-xNR-mn) ztnI;FoRLcBB@B1vI4*}OxcW$;M_46!UF~r`2>Bdz%TJDV=y7=!H1d8SBSJqa7eeMb@S*?Q+*oYu% zL*-SF`)2vZk+*3z)m_nX3j^<+;GYvRO^($C!Ad4W!V>R{K{jSm+Jxd5rqzJLd(8(O zBRzn1OZV4>0aVbAOzuJ))O(XzTjhr6YTBdaa{gT1(EkN%?%fH_!>FpGM>oyU2Kd%Vht~nvW9t` zPld(Kecotm0TZ(ps6#?Llk-Jlk531)6l9JlG-XP?Ec_I|Q12PU&Qxk4tci2#Kpl2S zQ&_GHQ}9r)Qf?`=#PMD>111Q}V0Ci2Jw;R8VGSA~xm>{yt-vv!J$`GaxmH)P|Ab(7 zf?3f)9%uIP_VeyDrgWO(0tC^r@PWCma}{MN3pew5mh7C>$0<{#DEuE9lV1&TacT_M z^7YJA1t=83-Bolr)%St-(aG)4;8fFYN%dG8raY2aZCsupR*Anq=MExhk00=_ybxkU zt#V5t%nA{c#r&Z(U$aoV=CGDika*wl2$Xb^13CabE>nY{%aBrKO>=XJz;3l!7zbRX z$V(d6X8XZbNN|jR-AST{Fri)p6!D%helf#uD%_A6J)ANh(oPX-Am{t|seG*?wf9Sg z%ygL&{7Qb)XG@BI1W3<-Sg+=rg&6itY!e?Bc7*1V*=K~biw5AkZHvFy{g%;=r~cWaU+O@9USrZgHW6g^j`-AK#N?F* z&thV5-)N>I6oj*t$&R?{1GDX6sLl57EOm_8zOgf!yOF+uHBfygGpCwrg;1(gw#|+r zJQ0XWW$C`PP;=EhWur0RsFvt4YuO4SHmHDDgW<4}t>Zo`C5Ve18kx^txihech=c@i z(jhzMkX7Fekr?~h+o!|*FS*PO7K~rVMIv%6QSZ^$<&~T5RbjH!&(ye%Xj<_BKs(NN zg_N(jRES#bbiGSWkbU<_v+hZUarK>;1Y{l<^V{E1Fv){5dhPD+J?@FYwJq#9Ae&F5l(MlqqDD zioJHvDwP}s|ErUWL!{QaepsW%RS+q~>mf_mPG3t6LhY8Ik!p^q%!wXogld_M;+fad zUP{)C_{h8??tvHu?>JmT7Zgk}6@wCOl4I?v_b>iZ5`dwA(S7?d34xkTI0pDc?G+px z+}!LYSBfKv`aN%0(NX6lK|C-6jd?;bl6yP>SWoK#1GPK-1A-1l5UkZE-(ub%+$j%L zvT#A{0}-$2=8jT{Mbw|Fbur<}$8-HMr-kg>6`qhT0lBYXq>O&p5Bm$iY# zGGdpcCK1l@kD!*uUxH-J6l4gvE#OGfsHcGm|5>Lujy0^WP&UK5RPN<}E9M8|uZbUD zyvS#R!rbogEiXTdEH9Vo1L%!U8utS@o|i0pubXu)!>0thWa|a93qgVa(FK+p*by{b zztIDeQ?JnJtHi~Ef`Xwgk!sgCbCJxX^G@L%+!@|GT0ppmMoH$iCJeAY82^wG<={9% zYnz2>oEYu-d$F?F$Yl_FDEu(TS!AJ&HFW~eIH&KqC?eH7c-xoTWttjB*HpLKPN zkDpCuYp_MO1t5US{E@(f^^M#q|IX%y_Hl_9hMp%kcXK_4`XR;DQQ_?@TYGNyD+jLsjB!kDorMprw2Vu zLDxC?1GS#&PcsipU~UW`h_D+F#eyCb{16QFK~w!f(uSy)jpE-#BM8b5GCeh^;mvX@ z;5;?QzyV7gCZ4-*NJQ|W3n`saSTAmuC-WbYZ;9=Yg?9fkn_*;Vn*7n6tH$`QwNGjY z8Mq~13D((QO?z-eN0l-HZktIXU{5_p;Pgz~Yfl~fw5j1`t>=08+cmh@8Y??0ta+$c zGxO3d?3c@;Ce!$i0{>+#V7}3ta!?b1hOCwX=byQBVzvBJox)OMnc9&W0i1JdB*9Pt zoSuv)fAeJEkrpC=W}2AQFw6uwj{$zW=S!U)cJ_(rH$s06(qQh9q^L^L%WDxNOV0M5 zxxioI!LFXI#@U{x`I>PU4$p{>kF92~zipRoouBSZyauObzYr;BadO^;Zf+m{z966~ zlfyD)X(+U$nW^yuTXFKNp&_z#Kk+u`d3mI5BAeYT{~ag z9PLu2xSqD)&x_+MEkC3VS>E0u)(_~;R+Dmc4#A;9WYfDxe-7!3HinveW2L_1eq_#GM(~5_SABb`3L~4|79TAY8s|riRU4tblvmH8$rmdj)e);MQLn zgofZ-$%bJ3W2PB%^w;@edg!-K>^c}VfH*bTBupERNXE~pxh3XG8#i4P>!3*LfB8{J zq@Yd}cj9k`L6}xNZjS_b{p+VPCbY4 zc0{t7q!e=Cbj`;T#&R#|(0@uu-~@;#1nk|oj2Plh4%AsHi*llSV1a%)o*rN3i2m8} z;>&E-A4`I|n~~#%o{%>Q0MJ;lgQ20VfuBxiL4Zh^&f6Le=M4P7+^0mvuL)q-!7ZGQ3!<@dNS$g5#g5Q+QbFpFGF5;x43i1alQ}nEx1qAcO{RR)4$hkM;>?DwALQ zdM+^#C3xRLVz4M_o!;9B>x#E3BUQ!YMH~ zv;kkbnd{*<@=3bxbBeZJ|hj(No#U8^M#yk->BH4 z@fo_9nAr4(AMFql$owT(z+>_W193r%`XlUWQY?O9ET^5?)vJd76W#Gy`4j`A^Yb08 zOgehRhdR#5C2p?8jRnrOCMsK!jN1yy#D8@IP*P;kP9{}{TD8TQ8P;6uhl@=i%`xCh zb`a(Ldcgum-CSI1IF9O=6@p4k%!4`TH}&<+qs{u(o`5Xg5+l$T{9W2L zWwUE$E_WijU0hsDT#`pR_{0AJqTnMja;)n0s)c49G#_8UwdM*wY`u(IA%{GSm@U35 zHY3C9GWP!-1{E}#KvGn$fRi_gQ~o(IKCN%x2;Di;shpn$91?*MV{CiLz=5flF9IH+ z7ACAj_(NSsF<)DiQ$_7xXhil62?0Sgn$Yzb0J|Oh(;0rWq#%?O6gJtJ#aSE?WO%<| zA|sW>MfYyd!2b766002T%`M}};qlERawr>qG2BEcOAX8{>*%lU1lnBsC`q))D|kuq z+#s?Zvc{(dY1mDZAoTYtUMIPDR`O$L7mQ^bo$rh>-tvBiT`e35Vn7_HtgD%J=n_TzaH#NjF$bHrZMyFAAR~ENC54_34{dR7WarU z8;a&2&TF}F&IoC*9+wfBpY?-W3*!vtGZ!J%oDkOiBsU;-pX~l~l1>+~&RhXj3`e?B z6s;*X3-ace_{_>``1DkCN-~07X)KO^>KDBnG9L`Z_z0oM{6x06`Y&iK!JKO(-(uLp zg;}F9I>q7_ptX#B{j*0u#JfC}z$5;b|34#B>s4)k4@0=r`oKpwIc@7Q6HE4m1tOcp zbnLVOnQSD>#Q^_Z!3lKpW9Tf4kDEo*){>EDauVxapdUx-Da7W2D3G)U0`^YKFlYZW zNv6-pho`5I&j?Y47@MKJX@wEtolbsPou=;?4WtJ_J{Ods;)SZaW3-VyR~&Rc(#di( zi6Sa#c*4BkWWAPrUv@cIEZ*5fXRlxJCrER^$_W-?YIo{d>a)C#L6qu~y=k~%~kIV(W_lV=58#(%nG zV11ZW4~@vmIIkeYQSVyzx7#$P1aZK$2xQRj|2AIE&u1)K?z)-zTLBlwbLB%Ff-#8ssIM1()S%QPbTmYtB(@eK$@#aHlpJ6j)`{b@7x`r&)9kbjj&73dS@i5SM-uAU zbdSqayv1g`fz5=w61x0i|KZdM339BqS7_=nPnQe-L0<1MzM)Dj0seg?&+VFM!kZfA zug^^KLo4Mo$Yl=z^mMkyo|fVx3KDIUYr#%zTQ84?`ZSf2p|+{s7YRh8C_zGuAiFO) z-62sZ#=sfs_x6MngwS*V5*b@yF&D>`Nm@oB8>JF^OC!ULi3J^DKgYxPrENKsabxAb zfI|N{i!^->Pm}1^y!BSsN#O*oSprg)IGNmkU#N$UQkYa*jGQCYL+goBs(_Hv0+!aG z3DSf?>C328-t<7w$LN>+{^P*W{b0dC=YHAs=)q4K0dl}wYw|5+O~jVo0Tk$iMaUN} zX!b}nsD`H0=&BiNPKVdz)&0p72sH+`|o?fZ%B z|E-k(j4+oySvRAB&((j+3-~R`)X=k^Wg#igf+i;`E1Zb31DkA{LNMsYNo}*Rb&fm^7;!8~brEFStP}jV z7d=q|zi$szJqI-BHO%Ysk~F5BE|<;iD_W1;zNl!Po|>25$ha5Ioi5qyctkgJf{01qlU$)!XOH|KSu|IytL7!kBX+INpt&CL!IzyAHI%z&@ z>GSrd;gf@xOW8)xN6n2@&bCvvqNCrI={;RtUE)Nqa8Dgx_lAk;W<~RmImT~b*SY(- zb6;9nFW))lqhKcoja*N(E{Lk}2A}lSUF+1g1ul-~WnR+l({9pv+j&%d+^tpn6u0ry z_bE}MAILn9p4Z5=9S?gGomQ9B0APqgv-T1(5$0mZNZZC0yX!F5g{;oPW_rxABVO`$z;scHp90u@mA4)wZ zR2H(JKEGjoIX_v_w7S*by-v6GbbMv>EPh7)F81c-Z&QS0;p_N(>wm;ftqPAyDOZ5w zXmU27#vdqCvWR!2iX?!SZr?7etaytQ>y}pJbjRo1CTgC0Y<%Fqyz(~wns#<}wwI`l z`--XU5JM@NL2UJqFN^j)UnZU9bAIo8h)N9>G$b6|8&g2agB=4Pe0r}80NW$x5XW@n z^px~Gu_qNEsv_N28ES*dQW>tEMeZ`i4%Tg_<=%X+j3!=ZGwy_5hpGiqDJ*;}c6Yat z&r3tbpRNk17#c6C8NKBR;9$?pfEVuaZd}e=dp&=h0EYo^L(@RctM2k ze6V&algVK@Bh7I)?3JlAsv*%&`v*s$_|VT{iYoSds(1D~Qky(GO|*DTGc^XkuQ&3HuM{&a-I|5QNJ)>e>l(UXyJ zg7-S^9Oe43bdacJux~3)6a?oZ%Xg}E$NdytQU0`_K(6IGK}6(!+~e)0Wg&X+T`{)i z{A!*3oHyZZ@a(vNx|L$A>oIcbe0}pL&1pNv$pmMQ_YE5KTFIEaTx&?L8r}OssM4Q1 zg`-;UroEGNy;9g!ycZVQua*pxB+4ByRF03%_xVor^HPbTC7t zy?1KOOtL?g={?og(f+c(lIiuZ6fv)Rpy+=7GCMQzbUgek%Vs@gV2c3ap|7G4E}p;o z*Q3+2tvNIBw{&0kn*o#jzh-k0{5Sp5XC|62CcIr?Ij>!Q16173jquJMo7w_y`-dRCMkm zi0&s#ky^dob1x!~t0#i|ee zob8{btQ#+HJ_$63+1l?HiBEVL-h}_b-GqF8R@Jo!7hBMDN0{nT?y7NGdR*!dU|jp~ zdPg$Gds=DK6l3(>VC{{QA$v3qvmP zrZ}PJb|_s1?`dx?ZF#9=JZEX8hxcnY97FTyu6_5A@tVz=Xf@U4g!hZ))$0_VYVS6O zgVTK{qKo*SbpDHwf($Q}(#gh?P;MIR(zKGc9!boy?99oGTKx+{o8kZ=HaPm5tSoZ%dH^tGAO3MC%_upok!py3IFXd0*PQd!JXn zvi@gZUmz^jdm(y6H*MxKBnm+!RFyP7S_o&Xn~g~4O9Mh{ZK(>*g5WIp2-8))Lx{i4JxgL?tg^~_^GOo|Ic11h=|h%Nqnu65Mc{ z0MC5h{#@COTdlR^exK06E>qt80JW5NcM)ualWiLRuxhokTX5nv4EPm;6cAvCub8OAP`xr;GK@fZJdVj7c(;^rIlt_js03Uat+mPMN83OG_le}X{Fuzo^Z?DRFJkU`STX*ar-QAY;M0Otya3S1%Bf8&={mOun z4vC17E7?0)ztXBIL*BpCF~ZO=TF>jrR{UD#-~MrAWQ3vRPY~c7K0TPw%i>oVhbXx7 z+_LMuQ-!YA(kt(#{o(f00dRja&ppmfR=t0PP-SSjm(ArO<_d}2ZyyNwlB^EH1vgA^ z{lbcRy&rlca&aNkb_^S`YH2->NAsdSZ8>W{-x4Pj#|wYFpEwJIazfPcx=&D?;3Epo zJQ#I#yzIkT@oJ$jf4a7=XupBNHx-jdAi!_y=SAESx0ZPB^hd&a>{VZ~)-r{}=WX1p zZ@FE32DhPbWW?mOF?l+l_!Ko#X5B_#6wy#K4(d;-``4l*!axdfS0s7>c1$h&FIMb9 zY5}V*J&w8nP9&;5FO!8^l2uI=uO0e^wLr&_ma}GAN&~w^tCicA!{B4uWQ2L$$K{Rn zaPTaoPqArCf!8{Nwm)tfzajR_NtgiPr1+OrEn0as^S(0ftIJDfi>FP&!v1(qin!g% zF(g>{h3#_v z&)_wfrqUyT?(4Qck^m1A6PLS;U4AmP&z%h|*YYBB<7>9KP5hH=)GLM|)3~q3WXFX+ z?>Q3_lg-mq@I>;|hxB$FPtCRb$m|Wrg=Y?wokg}tAAkM6gNccDXf%<>pF2??O}W0` z9=scGy99vjKD6k z^E~dk+aXd%+wbzn^44Q#Rb6MYT66uGRXyU zq|=TYIT>?AS%c)P+=i2*S+x8|C+j+Bpz3-jxW%|tNlj*L$Rb9As z@2~3ezb=2zu&Eigxex~h&PBMIH@ufrwQN4wop&91tDn8+J>UsA7lcopHLrlNvC9es zkRXol{it4|XGUx1U($A&li}EEeHa!9T*Vf)^%~2lnKI@v{cF7q6Em_M7efliSIool zquH<+c%+n*$mqW7F?z@Aspwes?E!H@w^v{hyysVG9!+M%SU9E?rhG{|ZP4k{CLV6C z=s^9neZ(qY*!sxm zZ*b7;n`Q*33i@eFi*fh&Yu|6999tDYj8Z^~lXisifp9e zqsd$)ZX!&zfxV8tcp?Z@c(>D`T&^w&QA_;-_#s1FqAiv4K`GjP3%`)gu3DilMke3c zNPvS9*3Y-1Tlw-I_CMI^e0!M>L0e(wmpBMqwwbD?TW;05Teo*<&%0jUffRF-=Z;XZ zB~p;J=ZdZ7F@jjvySt^-3nTT}H>_e&9N?@%WvpB9y&RgrCa}nl%>ung%chn&xwn?i zb5NyQ7!-aH8k%e|#6=1Eu3rr!XU(>q>yy4b4`#_l%MCy0ibaG>jf+1R_5-{ysAoAp zk2|zw2r<0KUv?uP+ib>NO&y}Yf4lJGqjZnL6BxEY+c`o@YlpZZrC8jWw$No)a9*@x zP5AvtfH8b0Lrrr=)(oZdlZ8}SgZ=lrD*>LHHh^vKif+@p@8JqfW^{jJOYdi^J5gzu zRoUsGQn_D?YXGpjoYy(BE{AOc&f{a?#4`Z*GoEe3x!S|l@Or-D>vpUk>m|SApDPBMRK|Qv?y1I@L2YD^9R7zYcGze9t>MzHBfI_(6sETudfHN=yBG}Pb>9c zz0LwdrNniR4j&K9)|fA_XxIlW3HwujJcbHCKNq^@($ ztbVE@BJij~Mh2<^w+AVDs?oE26;YGSV7r~_u z`F-#3x#?lV%`>mKd^VBE_zS)6`TRB4epC9>b~q0N#sb7!dLrIy-!m4&fXX*Jf3iBx zqeWL1x!W)Y_-tcefRA19EZfqC>-G{qFiSzQ6k~elnX`)eZsRJ?7DLpf*TzE!o=`5IK2&GwrgcL!{2N`%i3K|% z6kZQl@8s0h7L5T!mg2uqV2e51SjW)qso9liMd)7LG}n+toWMr*c$YPq(U*KhQ$qi}`aSCw;$v##JY zcm|_#?Pe0-`ARSDsLp6I5%YbP%4~;8R*$kh-pPnmAFgAuY`jIP(0<`Kob}+ZY?pHn{YQ7wyof%iPklnnhrxtO=7%zZ8Dxy$dwV-c>;!yXh$Q*Voz~565#2|ANjWqb(2wWs`fCbwNZ+u_@GiWE$~c0|OiXdX5yG zfYu#1#m?h}4hswk_M*mJw(Iscp38&?ME@D+-!2=nhpIqr5^i#?$akR>s7zuWXQl*^ z>Yd*{Psa3RQG8{>29w&+0CUs3^?o^jo0VeB@qBhGVZTDMaDAK>P0-(^3 z5P82lFL%d2uJ@I;n_td9$jlJgD6{cAFI}QN*R-|*BaG~K(V}&IcE+G{>=%QU^a>_Z zqPq^KF7zBb&$cfG7(b`T5`66Se~P}|RZTw1xl`h$0IhA>v`4}x8Ddi-mg4+)R(nVa zxs}Y_aLKycAR>hB6304<*?~AT14CB54a3s`)5W*+9bwlZFXrCkttE@$SKz&QJ>m_d z{XWeZ&8r-bDC z-S12?Jtujp zH|j=`jzq+5KWj@M8QXBlFhL4Bqv2n$&RNouvTELNS2C}teJ*oAV3~{?LTDD6N<;<) zntX>o`DwP6s=od1sM$%fu4Z-Ey4%1PVL35P4B~V=xlM=$tIu5)Zhf2#LPxbS-Q+lq-JBhu2oL)*co6iR1d%C(|^)A~~Kb34F;WHn`Pc{HL?d zhi9fqap9ThHN8Cs64M;-(^>)UA9o9?y|&VXKN3y7fu^kx;hSp+ic)V5ZJ#q*20k=s zvTJFozm}y+gFvJU^(x?V>t(GGxyIy-Vw9BfxM3G>mK-DAPSL7&vL4D<`KA$i-NJGX zyyfgmX7C3C_nDU9c^9vuhsOtqcaPfp3}M?51V~p5`eag6XEvrL{q#ZBgUOD z0fE-OPh)$lY6T#MKn1bzg_Uz4`o)PP#E0(&#L6C%l?un${8ia}yW}+|$MxDtiouT( zHASpq7n!^AQiGXw0qDpe2AdG=u~QOlc6;{YP2s5JT;XAB?ZL<}$>2D{=e7qFHss>t zXf8`{MrOX6h%?ykVXEeS=B0vhsy5pjAmWX|f;4Eg7a!(8+LlFi4RqhDru{8m4>tMAUtLJ?vBfOwz)Npg>z2B8U9SdZ_)p(swwgHoybr{DW`j@k}BUUDa^Spk49{~ zL^tbBT}*H64B`Q8T#3mCYUnFBnJm%$Zt^WrXy;LpCw~lR17x47I!`)gD!j$*hv5J5 zx96&kP>wUQm2DYEPfgxb2F!?w6>T&t$YtM1IE$Y8&dyF- z_qp($x!_1mCEWenqMpZ((=2Fx^uhOGv{|^%v4Me`9v7W1Uy;#iyF&8$Aw(fO+@7C6wHixMwYez6rU5J^4Hw)~&3c>5e6Bv|r%FtgKLXKaHpWRuZc3ffb9K~sXyweV3X-VApnncK4$wQd zIpv74o9{t&L1h9%&YdLTjnh?FeVKp603Nw`V6sygvNA!k}V@c%Ys+eM}F z%z7C~+7h7>Sip!>ht%P!Vl2{s-Sp=^A;D~p17Ar}vADnF1&W+VzMbqghw!OJk*4|A zPr6uxe}uTC1WQIM%*b|pK_hJn`b%bC2xGA1nXz|eYHV-{(r9hTkOBTFX=!- z(DmUO*JcogJiA698uvjXBf51?5uM?)kYKb7N6$-&$C`bd@I1mKtIg! zwEintifvssB~egkImoWmd6K8X$g#4Hi0~@oqy5rs!_WV`{lpMoVc@bH^BEie>(E~v z|GX=GI*5l<^ z0-@VM5Khkdri%!_36I_FW7-R$$A&X5*5rGW_X`W-ls!p-YVsZ4he&(HRDQQWM)#>7 z)?-+dKNNRPEkRpQ4v_pD_M;5g#mRto$nKkt=G+mo-cdXTb(pvy|f-T zZ`DaIScDc)A3DDi)x4$8(Td&~X?d1}Z`ZXdJgawCt-~@mIu&bKQHlMo(d;xqo9x(i zykXZB`!X3ZkFd~r-+RvLb=@A%?a8{Iy$2_|5%Y}Ktv@cJri6rrv%Spv08=u@(`_Sw z`lB-e*Y!9_`_8cmr|y{+^8#+8s$)PX2x1X+n*%vxcMtbfv;dOzM(f4Qi%t8>ecYm{ z7oH;^wb#kA&}_u+-EgRXYLbqQZdS6#_3NMPqi5@dPGLOcXTS81k{`6}Y_?b<5q@t~ z`<#p^58+wZ&dp}aoz%j+k&>e)uRJhTfd(Vfv+k+VP__BN@MDzL$5Z=Mr&$LN2gPQ? zj$r8515MRN@~;)AW!7*Juy>K3AUBqIo0l<{VfnVTWgF3hZ+-Itx5wl8^SX%X_qOVKg zQobZtt+%)h6As*-ZF=8cZcgWSKN~!C7jL1UqWVk7KVOs+MRhj$+!WpIYqz>RxpsA# zui5-|u-0PldK)5<@H~-TCDMAgn5!d+&*D#D_t~j_K_V#EDqjuls`TiXd4sK@sfV%DzD$-Fm-VyRS@-CRjjn>(+e) z=y@Pfs)hj;8qa$86#@E$J`V*KVhDgvm`qj5-=~>x|h7eRz^Nj7}hf~SE_Dc{}$?frYU74 z_-=*}oMP&IwIzNb_CRvLXxRid*-{%I@VYyEhv!1_{U|#C+}#;}xf#vf&g`R#cF~Ie z@gb}z>LeO}y}-K@SHf2}G%6aGy{5e6ci54sT&wA67x?gzXDbXdHuhuZDwLGYaElpHn(SmBQ2>;B4vfY#%F!<9i^UfBkH{0<&p`^9$c{=g`J{j5(! zo}R1s8Mx>UV=;QIis*|^?`Q+u@tG!jUk%EGLi7XV3F-q)jhfb0a%gh?<)O^UCZ~m< zq7d(_7E_9?lijGMgoq{*h{&}t*k^sx?fDLkV4Na-`z zX@Z*ofq>g#bVdjgV9RNx+6SNCq0GDUqI3kyQ4uz=gYl9WjaUd+p#}dv686n_$SA1z0FdY z`eMRY)34s37Fbt{hM}$iTh@A`weO+bPUEpQ1X}x8=#!~zAM?la_8jm*Vo`ldHnGYm z>M=+}0ylGvc&klT%GJ&|33Q3cOQur@2(Ya#uc<`{fDU#`hI9ScgM?P8kLA}RUtNNb zTR@%t+Bs79vqIv{L!1(vk1mI^@dxyIE9@%Sq4EG0CPwGYj0>A$JmKq-OcdU{+d&rN zjCiOxxILD?FI)aV;G>opQeSC?IFkP*cCY&0+r+LOPqQ`u`{9oH5#vPWO(nLgW@62y z$6HZW?ByabY`oX)eml{}wuEV(n5T3z3~jxG!^P7uRmt0hR}%;aerN~--(F`mS%3dU zhMjQvA%dyTGx+%ka!iKrRM&gy*NyatXd(hs-0O8qA~N0Sq3oOXp(v}BhTaf8`nuXn z-A1_?#xh=Oc!h87bI{=MZ~htC8P$06l}fZFy#kf5;HC{fg_`rZTorwbM8(AP?*dzM z<9()0s=HXQ`iF#G2Hh%q?RJ$<7RuE`LDN#|2ip!nJ6F{rqk_Lj+YAQ4XNn?qJ*@OS zH(G9^s5m=MW)OLjv7Z!R&9eNoC*>cO%jzflc_;&LJ}2fA7rg)4$&-}1-~Cyi70iMs z{Rdkbx06C0UtAb~*PTkB8ySg1RIN1GwjfjP=y3_?$BgQg z#-r&?@IxHEMCvYkf{bSU!PFhE;V6POn>|_HA7ZdEayV*SU9;mcU1;XYj7gk#3of)@ z>gyG#vlAxY3?BCcPXs zEX5U*ayH8n*-%V1?{OPLe>3^!q17m!eUv(Tz-DUd?~-3I3+I^FgBCRB(9zzPK~K?>S6zZm`Y^DSwo>oQuPLd|wnRL)bT-M- z4-TZASm8U@|6*?!Iou7sVOfic; zWASI6u|uMOt7HhJb4%AAbpbv#7|%O*D9;5}wkJf2{($!AFU?kWG_YyzG79BZXNiYL zPF@^q2sWp=XfGxeC#xl^e${$o*SW3BOAyP2_OU1ih*GNYR=d|4g*}MtzDJcI@!haj-N&%5&MABaKz-(l@PX2oZV;`+5xPg~)!zb9A`Iq}M|_BXGg)Y-?(@ zSN5UWr_Wdzc8ubCi6QRIH?h1r{WzRK^`k(r?7xNuTn*k<%)_O;zfBWhBZ2r|UN2f;R!THYi?1PJ%-R6?8qn!eg4R#RUs^3d!-t<1cxs+VCltbYeK06|lwKtTyG@ zuhif(e`c{Y>cSEL9oSCsFBYnS@AuW$iHPdekHZeZHX>GSRTO2W5cOZ2z|-f0>^sxs z$bOur1+bsu&vr*R=?*B%RIImY1i)dZaybLq#4@5(Weh@%)`)&SXGvpPfT$iSH5>hHW z+v{->>dqp45e5SbQ>WHGZ!SGpnbOjr_k6nTIFw{|zSIe+#^mUo0>R?$cm~tFM$)o( z)~SV@^T&STS(ujPqI?$e4EI425r=*W9MKOd8>rly2ou$erVB#;nh;f&2)b(yn}r%~ z>1B513*`KK(m2=0p7YMO#;+h+RoSoI5BkPcid?{E7h7K6Mem2AmHo^-w#9cQ%KFn% zycEym(7_o(IjAB#-k1+0u@##F6!j7xREm81Abpf-*FvsEhhgO^(n$RNY^mU^iZ3L` z{VE`YVj~=unuc3Z{bXOLO1l-85cAWS%lQLs^Z7dMaQAc0i-B1mI@Q)QhmE`xRnK%` z;ejv~OO5}*uPc^m&s<$nqbb?St@InV(P3PDGm+*kPGtre2u(K27UMzpiss%Zo@5{i z*X@u_HY~?ssjX(#9SR)j4yVfZ6Ta5`3JajQs!c`pRg;RGsjZP;9fjg zwBa0}!6E`3*4C7vdg*=lCaFNNi2h5dOFIH)`CQ2|TT^&Ge|gj%W6*(apxI3GA;R{i zh7dlT>%6(5@MJcROAPCNoQT(R_g5Ipqo4kuE6beZUHPC|?e)`s+Wc_Qx=m;509fOo zehuWNM4od}9{*1S5*>YC%tc1IAjj#eBs#ol_kl!*(kb zQ-lU7Cn45_>i304ud%4L2SLyw!_N7eV}dodg%caA4Mj|aN(=vK@5XcFfxZ_p1dDKO z*I>B0Utvn-z{&d97M5b6>68&)78QO0WH9DBAq=GYcLul$^3OI#nYbVhe(=K#upCqmghsV;--{YZr920K4&DM{x0R;4VwJ(z z`em4H^D0+jn%Ql8iXr*0VJW5Y_9{gnCBv~b(uqHA6nulXVf{mgMZhHMNC_-f&7zd* z##@XYr;J=GpS+IbXnlscwJLrEJ;3e9M>Iykj7(F@kK~h))s#r@tQE$223 zet|G?$Mdq7m>WV?Nyb4@;Qk@n-7lJ%tPGsNiY)1WHo~%5x~E5c_0rbu>~QW!tZ*;E zMvKx}CxdcPQ}wHXw1@z9>*@e`LMaJfQ*!m<;-vU1pb2P`WP4t8ofBs0R z(!nmvk1=3&%v^fjssAS%nKLDpXpo{jrl8YoFP$ngyI2(@PgFue89F-VE;kXHl6%nb zt*Jrz-)?2VLtX?(HBYIL6q z?6NR=(pnT;IS9KEOkA?@)KnAZ5rZ!^64XSCr5b0|Zy_e)A~#sC{*@g}hDM-kaRtRe z^`yKNK~8N=O#(+=&@uh@p$8U^QhYwgVOZZEe$Q*__E}(|1p2wd8c{52BAqX3{0f$7 zU8w9}O$yhc6{RN0ruBK*vt`mV0$Kh|$HfCyJem4Ju(JUjJztU?*fT`mZqIT%bb|zQ zMqpWj9L9xI2U?_V6?{1UydPo-{ZOJ*j+T57|x!r7b=RybkZm;?ra3o zX;3B7Zwf|%x=6pF+$f_+GeNd8otjF+xjJY-{?}DVM!3KOA*Pz^I$kEf2T4g3)ohq; zr4%_Z`V$WlJi#<1KlRP^CtXj#%|SW)hViO=fo9v^HQ7mS4wJGKztRKbv^1E5y5jBK zbehR5k450M$u`DWJUXY5*}gz}K||P52>sUhTOw#?A3J%4fd5u3N`S|1qN)q6BXz%~ z3ry-*EPQt70664hUJrLg`T{DgTGdZ(Ypw+>O!~p!t1jDAnKo3AO2LhRKgtwM{NNKn zU^%5gy657D6H;5qRhC|tXIK1xBy~x8in`LgU#}gcTITH;SRyC#xmNndX036QtF^E4 z5P8Lvb5W!Y3RH`aGB4dLBz2qAyX%8N!(I|cEzsG)P{;4IQoLI3;f2UkdT({WA<;2o z?bdh{CttF6z*@R;H#AV8rK1k`hJgPTs3HA+bl9hs_XVLT-cakf;KK{c);aBYKI1pu zfd-%^8C(ABx`s|#co6lV!AuoNbK3MK#qemnDeFb8YGuANp#gMx2>OH+pPu2x1pGUk z716W(lRMq((3j5@@Hy@J^G0WrkDSTS|_k&5d)c_0D>53RH(|g@_mPdVsTNi=%U<7^Y z5m@?+#Q>B$0A#lzJwC-Y*4C@lQg(No>)kU!&ZJgZg>1Q8l6j&*B}tid0Mq`f@j2=` z=qZogX>k&u-Nhy-r!!d@5;Dk@A)2n|<@hz{@|LoFA0cXaIaoF%ELSe(ULB5ps6#si zwb6lT`w)b%e$i$<=++Lg`C?mv3vRK}pp-|_P8?Nv9dUo_dMl>xG0nMV?(^hf}C2&$b@>sl3 zdqROabhM!89L0T3!ti*crB4O8SJM zor#5M+N~PDBP_t8fT`;}nOwCwn*l>eKi8XM?bL$BX>QOLHzO{$2tV#-n6%IjT)ymP z9H?jMR?9k>z6e!K?l%4OFuj?NSrbj=h?VxPV>DHOQVvok0C_~%orI8rOIpDJYTtG| zpetaf_wk)-#EFH_A5JV)dGZwYC?R*|g4MEQGW-clGMgSpsSg4=kK1#$yV-bzW;-XC za`-Ai0c;nWJVT!EcZ$mo`UIW!^F54Raq}41;U3O50V&nF4f_K$cFT|NM{p_CtMCA` zw>G8-)!qcrZ!ERXymk*!c3`kBz(9bw^OcCheVE_Qab2vBoarxde>xZ78Rb!jH%jF4 z&Z8E%Jg`Rq?MH8EX)Jn~Mbg#qd(8K@9=5%_4?SqMJ>ZzSQDm>|)N|^A_H4tnr#w{{ z{cvRpc~OY|z263%rat%JMLSblZyefk29l{)@4UR?zAk`od&9q88j?gu3?v@!p|=Eu z%<`6>cn{8NcN0ZTcQoE@0nG>}bFeJ=nOLALxWRapa)%#*2kLgjEAB+Wr>s*@Ifu`7q45-jgXDA1}U!K^v2Z^Be#r5ciSkTdD3x%b2md!7E z3tY8HUn@d!8=uj&@)Ai$fQ^fW64{SCo~=NCSD10nrrM+)%FuB!?|=YH<5#)K19X1GtD0VnkFzDJT;%h7>Qqgz`jv-6mF-uQlmQoBR?hNS7Yz)OzslL- zdSToyFR!4pQ$h8_m*9WjPwc8lR5;D_dzPt8&iiaY9DT@jTb2|hIig?6#cxl5 zJ4EToJS?d89?<#v!Y;Gg5StygSHOL&4?Rb}gpk>+PktZpQ(30v?wKSOEo^v$2^&vW zA8U|INIP-Ac{fecVOQ!-G0Y8z=R8Z3PQ4BH;d3g7 z=uZomvExecGT`8)6pMP%=lZykW`-+0kwB>(1B((Tdl#ggHE2D8uyj9Y9hUlhwJSJY z>8egg*@Rf~DU1gP01842c`j{d8a6W7hCc*!n6^X++KH)Vm(ooXYPXz^C24e+wTx4X z)}fjbDy9WNa;YTDzlr@CY@_ux$-!dyNtxm*E{_>e+`U{>UieI-L>ejXo=Q(g${e

BdcddMK!@}=ZUln|3xoa4;M7)zF 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 Date: Fri, 25 Sep 2026 13:53:57 -0700 Subject: [PATCH 5/5] Restrict the CI workflow to a read-only token and drop checkout credentials The job only installs, lints and tests, so it needs no write access and the checkout credential does not need to persist into the Git config where dependency scripts could read it. --- .github/workflows/ci.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9d0e4cd..fc8fe8d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,9 @@ on: - main - develop +permissions: + contents: read + jobs: checks: name: Lint, format, test @@ -14,6 +17,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + persist-credentials: false - name: Setup Node uses: actions/setup-node@v4