Repository: KyleKincer/tipout
Pull request #4: Tip pool groups
Author: login: KyleKincer
Created: 2025-05-03T20:00:20Z | Merged: 2025-05-03T20:14:18Z



<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Added support for assigning roles to tip pool groups, enabling pooled tip calculations and distribution.
  - Introduced a "Tip Pool Summaries" section on the reports page, allowing users to view aggregated tip data by pool group.
  - Added a new API endpoint to fetch available tip pool groups for easier role assignment.
  - Extended role editing to allow selection or creation of tip pool groups.

- **Enhancements**
  - Reports now display gross credit tips and tip pool group information for each employee and pool.
  - Improved accuracy of tip and tipout calculations with daily pooling logic and date-based role configuration.

- **Bug Fixes**
  - Enhanced error handling and logging in report and role configuration APIs.

- **Documentation**
  - Added detailed documentation summarizing tip calculation, pooling, and distribution logic.

- **Tests**
  - Introduced comprehensive tests for tip calculation and pooling logic to ensure correctness.

- **Style**
  - Improved formatting and consistency in schema files.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->


Diff:
diff --git a/.cursor/rules/tip-logic.mdc b/.cursor/rules/tip-logic.mdc
new file mode 100644
index 0000000..44a3c28
--- /dev/null
+++ b/.cursor/rules/tip-logic.mdc
@@ -0,0 +1,72 @@
+---
+description: 
+globs: 
+alwaysApply: false
+---
+# Tip Calculation & Distribution Logic
+
+This rule summarizes the core logic for tip calculation, pooling, and distribution in the system. It is intended to help developers understand and navigate the main files and types involved in tipouts and payroll tip calculations.
+
+## Main Implementation Files
+- [reportCalculations.ts](mdc:src/utils/reportCalculations.ts): Implements the main daily tip pooling, distribution, and summary logic.
+- [tipoutCalculations.ts](mdc:src/utils/tipoutCalculations.ts): Contains the core functions for calculating tipouts per shift and helpers for role config logic.
+- [reports.ts](mdc:src/types/reports.ts): Defines the main types used throughout the tip logic, including `Shift`, `RoleConfig`, `EmployeeRoleSummary`, and `ReportSummary`.
+
+## Core Logic Overview
+
+### 1. Daily Presence Check
+- For each day, determine if Host, SA, or Bar roles worked. This affects whether certain tipouts are paid.
+
+### 2. Tip Pooling (`tipPoolGroup`)
+- Shifts with the same non-null `tipPoolGroup` in their active `RoleConfig` are pooled together for that day.
+- The pool's total original cash/credit tips and hours are summed.
+- The pool's total tipouts for Host and SA are calculated and subtracted from the pool's total credit tips before distribution.
+- **Bar tipout is NOT subtracted at the pool level.**
+
+### 3. Distribution Pools
+- For each tipout type (Bar, Host, SA), calculate the total amount paid into the pool by all shifts for the day.
+
+### 4. Distribution Receiving
+- For each shift, if its role config has `receivesTipout: true`, it receives a share of the corresponding distribution pool proportional to its hours within its `distributionGroup`.
+
+### 5. Payroll Tips Calculation
+- **Non-Pooled Roles:** Payroll tips = Original Credit Tips + Net Received Tipouts - Net Paid Tipouts.
+- **Pooled Roles:** Payroll tips = Net pooled credit share + all received tipouts from distribution pools.
+
+### 6. Final Summary
+- Aggregates all processed shift data into `EmployeeRoleSummary` objects, including total hours, net tips, gross tips, tipouts, and payroll totals.
+
+## Key Types & Fields
+- `RoleConfig.tipPoolGroup`: Groups roles for pooling collected tips.
+- `RoleConfig.distributionGroup`: Groups roles for receiving distributed tipouts.
+- `RoleConfig.paysTipout`: Determines if a role contributes to a distribution pool.
+- `RoleConfig.receivesTipout`: Determines if a role receives from a distribution pool.
+- `EmployeeRoleSummary`: Stores all per-employee/role summary data for payroll and reporting.
+
+---
+
+For detailed implementation, see [reportCalculations.ts](mdc:src/utils/reportCalculations.ts) and [tipoutCalculations.ts](mdc:src/utils/tipoutCalculations.ts). Type definitions are in [reports.ts](mdc:src/types/reports.ts).
+
+# Tipout Calculation Logic
+
+- **Tip Pooling:**
+  - Shifts with the same non-null `tipPoolGroup` in their active `RoleConfig` are pooled together for that day.
+  - The pool's total original cash/credit tips and hours are summed.
+  - The pool's total tipouts for Host and SA are calculated and subtracted from the pool's total credit tips before distribution.
+  - **Bar tipout is NOT subtracted at the pool level.**
+
+- **Distribution:**
+  - After Host and SA tipouts are deducted, the remaining pooled tips are distributed to individuals based on hours worked.
+
+- **Bar Tipout:**
+  - After tips are pooled and distributed, each individual pays their own bar tipout based on their own liquor sales and the bar tipout rate.
+  - This means the bar tipout is calculated and deducted at the individual level, after pooling, not at the pool/group level.
+
+- **Payroll Tips:**
+  - For pooled roles: `payrollTips = net pooled credit share + received distribution tipouts - bar tipout paid by individual`
+  - For non-pooled roles: `payrollTips = original credit tips + net tipouts received/paid`
+
+- **Relevant Implementation:**
+  - See [reportCalculations.ts](mdc:src/utils/reportCalculations.ts) for the main logic.
+  - See [tipoutCalculations.ts](mdc:src/utils/tipoutCalculations.ts) for tipout calculation helpers.
+  - See [reportCalculations.test.ts](mdc:src/utils/reportCalculations.test.ts) and [tipoutCalculations.test.ts](mdc:src/utils/tipoutCalculations.test.ts) for test cases verifying this behavior.
diff --git a/bun.lock b/bun.lock
index a20b10b..a484ebd 100644
--- a/bun.lock
+++ b/bun.lock
@@ -25,14 +25,17 @@
       "devDependencies": {
         "@eslint/eslintrc": "^3",
         "@tailwindcss/postcss": "^4",
+        "@types/jest": "^29.5.14",
         "@types/node": "^20",
         "@types/react": "^19",
         "@types/react-dom": "^19",
         "eslint": "^9",
         "eslint-config-next": "15.2.3",
+        "jest": "^29.7.0",
         "prisma": "^6.5.0",
         "supabase": "^2.19.7",
         "tailwindcss": "^4",
+        "ts-jest": "^29.3.1",
         "typescript": "^5",
       },
     },
@@ -40,6 +43,76 @@
   "packages": {
     "@alloc/quick-lru": ["@alloc/quick-lru@5.2.0", "", {}, "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw=="],
 
+    "@ampproject/remapping": ["@ampproject/remapping@2.3.0", "", { "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw=="],
+
+    "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="],
+
+    "@babel/compat-data": ["@babel/compat-data@7.27.1", "", {}, "sha512-Q+E+rd/yBzNQhXkG+zQnF58e4zoZfBedaxwzPmicKsiK3nt8iJYrSrDbjwFFDGC4f+rPafqRaPH6TsDoSvMf7A=="],
+
+    "@babel/core": ["@babel/core@7.27.1", "", { "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.1", "@babel/helper-compilation-targets": "^7.27.1", "@babel/helper-module-transforms": "^7.27.1", "@babel/helpers": "^7.27.1", "@babel/parser": "^7.27.1", "@babel/template": "^7.27.1", "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", "json5": "^2.2.3", "semver": "^6.3.1" } }, "sha512-IaaGWsQqfsQWVLqMn9OB92MNN7zukfVA4s7KKAI0KfrrDsZ0yhi5uV4baBuLuN7n3vsZpwP8asPPcVwApxvjBQ=="],
+
+    "@babel/generator": ["@babel/generator@7.27.1", "", { "dependencies": { "@babel/parser": "^7.27.1", "@babel/types": "^7.27.1", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^3.0.2" } }, "sha512-UnJfnIpc/+JO0/+KRVQNGU+y5taA5vCbwN8+azkX6beii/ZF+enZJSOKo11ZSzGJjlNfJHfQtmQT8H+9TXPG2w=="],
+
+    "@babel/helper-compilation-targets": ["@babel/helper-compilation-targets@7.27.1", "", { "dependencies": { "@babel/compat-data": "^7.27.1", "@babel/helper-validator-option": "^7.27.1", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "sha512-2YaDd/Rd9E598B5+WIc8wJPmWETiiJXFYVE60oX8FDohv7rAUU3CQj+A1MgeEmcsk2+dQuEjIe/GDvig0SqL4g=="],
+
+    "@babel/helper-module-imports": ["@babel/helper-module-imports@7.27.1", "", { "dependencies": { "@babel/traverse": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w=="],
+
+    "@babel/helper-module-transforms": ["@babel/helper-module-transforms@7.27.1", "", { "dependencies": { "@babel/helper-module-imports": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1", "@babel/traverse": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-9yHn519/8KvTU5BjTVEEeIM3w9/2yXNKoD82JifINImhpKkARMJKPP59kLo+BafpdN5zgNeIcS4jsGDmd3l58g=="],
+
+    "@babel/helper-plugin-utils": ["@babel/helper-plugin-utils@7.27.1", "", {}, "sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw=="],
+
+    "@babel/helper-string-parser": ["@babel/helper-string-parser@7.27.1", "", {}, "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA=="],
+
+    "@babel/helper-validator-identifier": ["@babel/helper-validator-identifier@7.27.1", "", {}, "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow=="],
+
+    "@babel/helper-validator-option": ["@babel/helper-validator-option@7.27.1", "", {}, "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg=="],
+
+    "@babel/helpers": ["@babel/helpers@7.27.1", "", { "dependencies": { "@babel/template": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-FCvFTm0sWV8Fxhpp2McP5/W53GPllQ9QeQ7SiqGWjMf/LVG07lFa5+pgK05IRhVwtvafT22KF+ZSnM9I545CvQ=="],
+
+    "@babel/parser": ["@babel/parser@7.27.1", "", { "dependencies": { "@babel/types": "^7.27.1" }, "bin": "./bin/babel-parser.js" }, "sha512-I0dZ3ZpCrJ1c04OqlNsQcKiZlsrXf/kkE4FXzID9rIOYICsAbA8mMDzhW/luRNAHdCNt7os/u8wenklZDlUVUQ=="],
+
+    "@babel/plugin-syntax-async-generators": ["@babel/plugin-syntax-async-generators@7.8.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw=="],
+
+    "@babel/plugin-syntax-bigint": ["@babel/plugin-syntax-bigint@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg=="],
+
+    "@babel/plugin-syntax-class-properties": ["@babel/plugin-syntax-class-properties@7.12.13", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.12.13" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA=="],
+
+    "@babel/plugin-syntax-class-static-block": ["@babel/plugin-syntax-class-static-block@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw=="],
+
+    "@babel/plugin-syntax-import-attributes": ["@babel/plugin-syntax-import-attributes@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww=="],
+
+    "@babel/plugin-syntax-import-meta": ["@babel/plugin-syntax-import-meta@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g=="],
+
+    "@babel/plugin-syntax-json-strings": ["@babel/plugin-syntax-json-strings@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA=="],
+
+    "@babel/plugin-syntax-jsx": ["@babel/plugin-syntax-jsx@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w=="],
+
+    "@babel/plugin-syntax-logical-assignment-operators": ["@babel/plugin-syntax-logical-assignment-operators@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig=="],
+
+    "@babel/plugin-syntax-nullish-coalescing-operator": ["@babel/plugin-syntax-nullish-coalescing-operator@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ=="],
+
+    "@babel/plugin-syntax-numeric-separator": ["@babel/plugin-syntax-numeric-separator@7.10.4", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.10.4" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug=="],
+
+    "@babel/plugin-syntax-object-rest-spread": ["@babel/plugin-syntax-object-rest-spread@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA=="],
+
+    "@babel/plugin-syntax-optional-catch-binding": ["@babel/plugin-syntax-optional-catch-binding@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q=="],
+
+    "@babel/plugin-syntax-optional-chaining": ["@babel/plugin-syntax-optional-chaining@7.8.3", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.8.0" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg=="],
+
+    "@babel/plugin-syntax-private-property-in-object": ["@babel/plugin-syntax-private-property-in-object@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg=="],
+
+    "@babel/plugin-syntax-top-level-await": ["@babel/plugin-syntax-top-level-await@7.14.5", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw=="],
+
+    "@babel/plugin-syntax-typescript": ["@babel/plugin-syntax-typescript@7.27.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.27.1" }, "peerDependencies": { "@babel/core": "^7.0.0-0" } }, "sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ=="],
+
+    "@babel/template": ["@babel/template@7.27.1", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/parser": "^7.27.1", "@babel/types": "^7.27.1" } }, "sha512-Fyo3ghWMqkHHpHQCoBs2VnYjR4iWFFjguTDEqA5WgZDOrFesVjMhMM2FSqTKSoUSDO1VQtavj8NFpdRBEvJTtg=="],
+
+    "@babel/traverse": ["@babel/traverse@7.27.1", "", { "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.27.1", "@babel/parser": "^7.27.1", "@babel/template": "^7.27.1", "@babel/types": "^7.27.1", "debug": "^4.3.1", "globals": "^11.1.0" } }, "sha512-ZCYtZciz1IWJB4U61UPu4KEaqyfj+r5T1Q5mqPo+IBpcG9kHv30Z0aD8LXPgC1trYa6rK0orRyAhqUgk4MjmEg=="],
+
+    "@babel/types": ["@babel/types@7.27.1", "", { "dependencies": { "@babel/helper-string-parser": "^7.27.1", "@babel/helper-validator-identifier": "^7.27.1" } }, "sha512-+EzkxvLNfiUeKMgy/3luqfsCWFRXLb7U6wNQTk60tovuckwB15B191tJWvpp4HjiQWdJkCxO3Wbvc6jlk3Xb2Q=="],
+
+    "@bcoe/v8-coverage": ["@bcoe/v8-coverage@0.2.3", "", {}, "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw=="],
+
     "@clerk/backend": ["@clerk/backend@1.25.6", "", { "dependencies": { "@clerk/shared": "^3.2.1", "@clerk/types": "^4.49.2", "cookie": "1.0.2", "snakecase-keys": "8.0.1", "tslib": "2.8.1" } }, "sha512-JxHs9OXhx37RWK5rO3IITh+KP3EYQed9rLZiJKvQxr7A7plYAJhK8BzjNXAE5IXZbCZjkOGFyynmuXnjnyUqaw=="],
 
     "@clerk/clerk-react": ["@clerk/clerk-react@5.25.3", "", { "dependencies": { "@clerk/shared": "^3.2.1", "@clerk/types": "^4.49.2", "tslib": "2.8.1" }, "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-0", "react-dom": "^18.0.0 || ^19.0.0 || ^19.0.0-0" } }, "sha512-nSuUzvu/DtiBADx2q0p0syk6NfCT5q4SuVJbQUimNSOPSnGJ7CVfO6QvEXS1oWcVyEKn+8nVlMaZonfDsuaORA=="],
@@ -190,6 +263,48 @@
 
     "@isaacs/fs-minipass": ["@isaacs/fs-minipass@4.0.1", "", { "dependencies": { "minipass": "^7.0.4" } }, "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w=="],
 
+    "@istanbuljs/load-nyc-config": ["@istanbuljs/load-nyc-config@1.1.0", "", { "dependencies": { "camelcase": "^5.3.1", "find-up": "^4.1.0", "get-package-type": "^0.1.0", "js-yaml": "^3.13.1", "resolve-from": "^5.0.0" } }, "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ=="],
+
+    "@istanbuljs/schema": ["@istanbuljs/schema@0.1.3", "", {}, "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA=="],
+
+    "@jest/console": ["@jest/console@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0" } }, "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg=="],
+
+    "@jest/core": ["@jest/core@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "ci-info": "^3.2.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-changed-files": "^29.7.0", "jest-config": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-resolve-dependencies": "^29.7.0", "jest-runner": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "jest-watcher": "^29.7.0", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-ansi": "^6.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg=="],
+
+    "@jest/environment": ["@jest/environment@29.7.0", "", { "dependencies": { "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0" } }, "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw=="],
+
+    "@jest/expect": ["@jest/expect@29.7.0", "", { "dependencies": { "expect": "^29.7.0", "jest-snapshot": "^29.7.0" } }, "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ=="],
+
+    "@jest/expect-utils": ["@jest/expect-utils@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3" } }, "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA=="],
+
+    "@jest/fake-timers": ["@jest/fake-timers@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@sinonjs/fake-timers": "^10.0.2", "@types/node": "*", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ=="],
+
+    "@jest/globals": ["@jest/globals@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/types": "^29.6.3", "jest-mock": "^29.7.0" } }, "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ=="],
+
+    "@jest/reporters": ["@jest/reporters@29.7.0", "", { "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "@types/node": "*", "chalk": "^4.0.0", "collect-v8-coverage": "^1.0.0", "exit": "^0.1.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", "istanbul-lib-source-maps": "^4.0.0", "istanbul-reports": "^3.1.3", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "slash": "^3.0.0", "string-length": "^4.0.1", "strip-ansi": "^6.0.0", "v8-to-istanbul": "^9.0.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"] }, "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg=="],
+
+    "@jest/schemas": ["@jest/schemas@29.6.3", "", { "dependencies": { "@sinclair/typebox": "^0.27.8" } }, "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA=="],
+
+    "@jest/source-map": ["@jest/source-map@29.6.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", "graceful-fs": "^4.2.9" } }, "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw=="],
+
+    "@jest/test-result": ["@jest/test-result@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/types": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "collect-v8-coverage": "^1.0.0" } }, "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA=="],
+
+    "@jest/test-sequencer": ["@jest/test-sequencer@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "slash": "^3.0.0" } }, "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw=="],
+
+    "@jest/transform": ["@jest/transform@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/types": "^29.6.3", "@jridgewell/trace-mapping": "^0.3.18", "babel-plugin-istanbul": "^6.1.1", "chalk": "^4.0.0", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "micromatch": "^4.0.4", "pirates": "^4.0.4", "slash": "^3.0.0", "write-file-atomic": "^4.0.2" } }, "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw=="],
+
+    "@jest/types": ["@jest/types@29.6.3", "", { "dependencies": { "@jest/schemas": "^29.6.3", "@types/istanbul-lib-coverage": "^2.0.0", "@types/istanbul-reports": "^3.0.0", "@types/node": "*", "@types/yargs": "^17.0.8", "chalk": "^4.0.0" } }, "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw=="],
+
+    "@jridgewell/gen-mapping": ["@jridgewell/gen-mapping@0.3.8", "", { "dependencies": { "@jridgewell/set-array": "^1.2.1", "@jridgewell/sourcemap-codec": "^1.4.10", "@jridgewell/trace-mapping": "^0.3.24" } }, "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA=="],
+
+    "@jridgewell/resolve-uri": ["@jridgewell/resolve-uri@3.1.2", "", {}, "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="],
+
+    "@jridgewell/set-array": ["@jridgewell/set-array@1.2.1", "", {}, "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="],
+
+    "@jridgewell/sourcemap-codec": ["@jridgewell/sourcemap-codec@1.5.0", "", {}, "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="],
+
+    "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.25", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ=="],
+
     "@kurkle/color": ["@kurkle/color@0.3.4", "", {}, "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="],
 
     "@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@0.2.7", "", { "dependencies": { "@emnapi/core": "^1.3.1", "@emnapi/runtime": "^1.3.1", "@tybys/wasm-util": "^0.9.0" } }, "sha512-5yximcFK5FNompXfJFoWanu5l8v1hNGqNHh9du1xETp9HWk/B/PzvchX55WYOPaIeNglG8++68AAiauBAtbnzw=="],
@@ -256,6 +371,12 @@
 
     "@rushstack/eslint-patch": ["@rushstack/eslint-patch@1.11.0", "", {}, "sha512-zxnHvoMQVqewTJr/W4pKjF0bMGiKJv1WX7bSrkl46Hg0QjESbzBROWK0Wg4RphzSOS5Jiy7eFimmM3UgMrMZbQ=="],
 
+    "@sinclair/typebox": ["@sinclair/typebox@0.27.8", "", {}, "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA=="],
+
+    "@sinonjs/commons": ["@sinonjs/commons@3.0.1", "", { "dependencies": { "type-detect": "4.0.8" } }, "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ=="],
+
+    "@sinonjs/fake-timers": ["@sinonjs/fake-timers@10.3.0", "", { "dependencies": { "@sinonjs/commons": "^3.0.0" } }, "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA=="],
+
     "@supabase/auth-helpers-nextjs": ["@supabase/auth-helpers-nextjs@0.10.0", "", { "dependencies": { "@supabase/auth-helpers-shared": "0.7.0", "set-cookie-parser": "^2.6.0" }, "peerDependencies": { "@supabase/supabase-js": "^2.39.8" } }, "sha512-2dfOGsM4yZt0oS4TPiE7bD4vf7EVz7NRz/IJrV6vLg0GP7sMUx8wndv2euLGq4BjN9lUCpu6DG/uCC8j+ylwPg=="],
 
     "@supabase/auth-helpers-shared": ["@supabase/auth-helpers-shared@0.7.0", "", { "dependencies": { "jose": "^4.14.4" }, "peerDependencies": { "@supabase/supabase-js": "^2.39.8" } }, "sha512-FBFf2ei2R7QC+B/5wWkthMha8Ca2bWHAndN+syfuEUUfufv4mLcAgBCcgNg5nJR8L0gZfyuaxgubtOc9aW3Cpg=="],
@@ -312,8 +433,26 @@
 
     "@tybys/wasm-util": ["@tybys/wasm-util@0.9.0", "", { "dependencies": { "tslib": "^2.4.0" } }, "sha512-6+7nlbMVX/PVDCwaIQ8nTOPveOcFLSt8GcXdx8hD0bt39uWxYT88uXzqTd4fTvqta7oeUJqudepapKNt2DYJFw=="],
 
+    "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="],
+
+    "@types/babel__generator": ["@types/babel__generator@7.27.0", "", { "dependencies": { "@babel/types": "^7.0.0" } }, "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg=="],
+
+    "@types/babel__template": ["@types/babel__template@7.4.4", "", { "dependencies": { "@babel/parser": "^7.1.0", "@babel/types": "^7.0.0" } }, "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A=="],
+
+    "@types/babel__traverse": ["@types/babel__traverse@7.20.7", "", { "dependencies": { "@babel/types": "^7.20.7" } }, "sha512-dkO5fhS7+/oos4ciWxyEyjWe48zmG6wbCheo/G2ZnHx4fs3EU6YC6UM8rk56gAjNJ9P3MTH2jo5jb92/K6wbng=="],
+
     "@types/estree": ["@types/estree@1.0.6", "", {}, "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="],
 
+    "@types/graceful-fs": ["@types/graceful-fs@4.1.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ=="],
+
+    "@types/istanbul-lib-coverage": ["@types/istanbul-lib-coverage@2.0.6", "", {}, "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w=="],
+
+    "@types/istanbul-lib-report": ["@types/istanbul-lib-report@3.0.3", "", { "dependencies": { "@types/istanbul-lib-coverage": "*" } }, "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA=="],
+
+    "@types/istanbul-reports": ["@types/istanbul-reports@3.0.4", "", { "dependencies": { "@types/istanbul-lib-report": "*" } }, "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ=="],
+
+    "@types/jest": ["@types/jest@29.5.14", "", { "dependencies": { "expect": "^29.0.0", "pretty-format": "^29.0.0" } }, "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ=="],
+
     "@types/json-schema": ["@types/json-schema@7.0.15", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
 
     "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="],
@@ -326,8 +465,14 @@
 
     "@types/react-dom": ["@types/react-dom@19.0.4", "", { "peerDependencies": { "@types/react": "^19.0.0" } }, "sha512-4fSQ8vWFkg+TGhePfUzVmat3eC14TXYSsiiDSLI0dVLsrm9gZFABjPy/Qu6TKgl1tq1Bu1yDsuQgY3A3DOjCcg=="],
 
+    "@types/stack-utils": ["@types/stack-utils@2.0.3", "", {}, "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="],
+
     "@types/ws": ["@types/ws@8.18.0", "", { "dependencies": { "@types/node": "*" } }, "sha512-8svvI3hMyvN0kKCJMvTJP/x6Y/EoQbepff882wL+Sn5QsXb3etnamgrJq4isrBxSJj5L2AuXcI0+bgkoAXGUJw=="],
 
+    "@types/yargs": ["@types/yargs@17.0.33", "", { "dependencies": { "@types/yargs-parser": "*" } }, "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA=="],
+
+    "@types/yargs-parser": ["@types/yargs-parser@21.0.3", "", {}, "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ=="],
+
     "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.27.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.10.0", "@typescript-eslint/scope-manager": "8.27.0", "@typescript-eslint/type-utils": "8.27.0", "@typescript-eslint/utils": "8.27.0", "@typescript-eslint/visitor-keys": "8.27.0", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", "ts-api-utils": "^2.0.1" }, "peerDependencies": { "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-4henw4zkePi5p252c8ncBLzLce52SEUz2Ebj8faDnuUXz2UuHEONYcJ+G0oaCF+bYCWVZtrGzq3FD7YXetmnSA=="],
 
     "@typescript-eslint/parser": ["@typescript-eslint/parser@8.27.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.27.0", "@typescript-eslint/types": "8.27.0", "@typescript-eslint/typescript-estree": "8.27.0", "@typescript-eslint/visitor-keys": "8.27.0", "debug": "^4.3.4" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", "typescript": ">=4.8.4 <5.9.0" } }, "sha512-XGwIabPallYipmcOk45DpsBSgLC64A0yvdAkrwEzwZ2viqGqRUJ8eEYoPz0CWnutgAFbNMPdsGGvzjSmcWVlEA=="],
@@ -378,9 +523,13 @@
 
     "ajv": ["ajv@6.12.6", "", { "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" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="],
 
-    "ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
+    "ansi-escapes": ["ansi-escapes@4.3.2", "", { "dependencies": { "type-fest": "^0.21.3" } }, "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ=="],
 
-    "ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
+    "ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+
+    "ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
+
+    "anymatch": ["anymatch@3.1.3", "", { "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" } }, "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw=="],
 
     "argparse": ["argparse@2.0.1", "", {}, "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="],
 
@@ -404,6 +553,8 @@
 
     "ast-types-flow": ["ast-types-flow@0.0.8", "", {}, "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ=="],
 
+    "async": ["async@3.2.6", "", {}, "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA=="],
+
     "async-function": ["async-function@1.0.0", "", {}, "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA=="],
 
     "available-typed-arrays": ["available-typed-arrays@1.0.7", "", { "dependencies": { "possible-typed-array-names": "^1.0.0" } }, "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ=="],
@@ -412,6 +563,16 @@
 
     "axobject-query": ["axobject-query@4.1.0", "", {}, "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ=="],
 
+    "babel-jest": ["babel-jest@29.7.0", "", { "dependencies": { "@jest/transform": "^29.7.0", "@types/babel__core": "^7.1.14", "babel-plugin-istanbul": "^6.1.1", "babel-preset-jest": "^29.6.3", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "slash": "^3.0.0" }, "peerDependencies": { "@babel/core": "^7.8.0" } }, "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg=="],
+
+    "babel-plugin-istanbul": ["babel-plugin-istanbul@6.1.1", "", { "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-instrument": "^5.0.4", "test-exclude": "^6.0.0" } }, "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA=="],
+
+    "babel-plugin-jest-hoist": ["babel-plugin-jest-hoist@29.6.3", "", { "dependencies": { "@babel/template": "^7.3.3", "@babel/types": "^7.3.3", "@types/babel__core": "^7.1.14", "@types/babel__traverse": "^7.0.6" } }, "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg=="],
+
+    "babel-preset-current-node-syntax": ["babel-preset-current-node-syntax@1.1.0", "", { "dependencies": { "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-bigint": "^7.8.3", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", "@babel/plugin-syntax-numeric-separator": "^7.10.4", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", "@babel/plugin-syntax-optional-chaining": "^7.8.3", "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-ldYss8SbBlWva1bs28q78Ju5Zq1F+8BrqBZZ0VFhLBvhh6lCpC2o3gDJi/5DRLs9FgYZCnmPYIVFU4lRXCkyUw=="],
+
+    "babel-preset-jest": ["babel-preset-jest@29.6.3", "", { "dependencies": { "babel-plugin-jest-hoist": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0" }, "peerDependencies": { "@babel/core": "^7.0.0" } }, "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA=="],
+
     "balanced-match": ["balanced-match@1.0.2", "", {}, "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="],
 
     "bin-links": ["bin-links@5.0.0", "", { "dependencies": { "cmd-shim": "^7.0.0", "npm-normalize-package-bin": "^4.0.0", "proc-log": "^5.0.0", "read-cmd-shim": "^5.0.0", "write-file-atomic": "^6.0.0" } }, "sha512-sdleLVfCjBtgO5cNjA2HVRvWBJAHs4zwenaCPMNJAJU0yNxpzj80IpjOIimkpkr+mhlA+how5poQtt53PygbHA=="],
@@ -420,6 +581,14 @@
 
     "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="],
 
+    "browserslist": ["browserslist@4.24.5", "", { "dependencies": { "caniuse-lite": "^1.0.30001716", "electron-to-chromium": "^1.5.149", "node-releases": "^2.0.19", "update-browserslist-db": "^1.1.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-FDToo4Wo82hIdgc1CQ+NQD0hEhmpPjrZ3hiUgwgOG6IuTdlpr8jdjyG24P6cNP1yJpTLzS5OcGgSw0xmDU1/Tw=="],
+
+    "bs-logger": ["bs-logger@0.2.6", "", { "dependencies": { "fast-json-stable-stringify": "2.x" } }, "sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog=="],
+
+    "bser": ["bser@2.1.1", "", { "dependencies": { "node-int64": "^0.4.0" } }, "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ=="],
+
+    "buffer-from": ["buffer-from@1.1.2", "", {}, "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="],
+
     "busboy": ["busboy@1.6.0", "", { "dependencies": { "streamsearch": "^1.1.0" } }, "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA=="],
 
     "call-bind": ["call-bind@1.0.8", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", "get-intrinsic": "^1.2.4", "set-function-length": "^1.2.2" } }, "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww=="],
@@ -430,20 +599,34 @@
 
     "callsites": ["callsites@3.1.0", "", {}, "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="],
 
+    "camelcase": ["camelcase@6.3.0", "", {}, "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA=="],
+
     "caniuse-lite": ["caniuse-lite@1.0.30001707", "", {}, "sha512-3qtRjw/HQSMlDWf+X79N206fepf4SOOU6SQLMaq/0KkZLmSjPxAkBOQQ+FxbHKfHmYLZFfdWsO3KA90ceHPSnw=="],
 
     "chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="],
 
+    "char-regex": ["char-regex@1.0.2", "", {}, "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw=="],
+
     "chart.js": ["chart.js@4.4.8", "", { "dependencies": { "@kurkle/color": "^0.3.0" } }, "sha512-IkGZlVpXP+83QpMm4uxEiGqSI7jFizwVtF3+n5Pc3k7sMO+tkd0qxh2OzLhenM0K80xtmAONWGBn082EiBQSDA=="],
 
     "chownr": ["chownr@3.0.0", "", {}, "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g=="],
 
+    "ci-info": ["ci-info@3.9.0", "", {}, "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ=="],
+
+    "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="],
+
     "client-only": ["client-only@0.0.1", "", {}, "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="],
 
+    "cliui": ["cliui@8.0.1", "", { "dependencies": { "string-width": "^4.2.0", "strip-ansi": "^6.0.1", "wrap-ansi": "^7.0.0" } }, "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ=="],
+
     "clsx": ["clsx@2.1.1", "", {}, "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="],
 
     "cmd-shim": ["cmd-shim@7.0.0", "", {}, "sha512-rtpaCbr164TPPh+zFdkWpCyZuKkjpAzODfaZCf/SVJZzJN+4bHQb/LP3Jzq5/+84um3XXY8r548XiWKSborwVw=="],
 
+    "co": ["co@4.6.0", "", {}, "sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ=="],
+
+    "collect-v8-coverage": ["collect-v8-coverage@1.0.2", "", {}, "sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q=="],
+
     "color": ["color@4.2.3", "", { "dependencies": { "color-convert": "^2.0.1", "color-string": "^1.9.0" } }, "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A=="],
 
     "color-convert": ["color-convert@2.0.1", "", { "dependencies": { "color-name": "~1.1.4" } }, "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ=="],
@@ -454,8 +637,12 @@
 
     "concat-map": ["concat-map@0.0.1", "", {}, "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="],
 
+    "convert-source-map": ["convert-source-map@2.0.0", "", {}, "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="],
+
     "cookie": ["cookie@1.0.2", "", {}, "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA=="],
 
+    "create-jest": ["create-jest@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "exit": "^0.1.2", "graceful-fs": "^4.2.9", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "prompts": "^2.0.1" }, "bin": { "create-jest": "bin/create-jest.js" } }, "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q=="],
+
     "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
 
     "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="],
@@ -474,8 +661,12 @@
 
     "debug": ["debug@4.4.0", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA=="],
 
+    "dedent": ["dedent@1.6.0", "", { "peerDependencies": { "babel-plugin-macros": "^3.1.0" }, "optionalPeers": ["babel-plugin-macros"] }, "sha512-F1Z+5UCFpmQUzJa11agbyPVMbpgT/qA3/SKyJ1jyBgm7dUcUEa8v9JwDkerSQXfakBwFljIxhOJqGkjUwZ9FSA=="],
+
     "deep-is": ["deep-is@0.1.4", "", {}, "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="],
 
+    "deepmerge": ["deepmerge@4.3.1", "", {}, "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A=="],
+
     "define-data-property": ["define-data-property@1.1.4", "", { "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", "gopd": "^1.0.1" } }, "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A=="],
 
     "define-properties": ["define-properties@1.2.1", "", { "dependencies": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", "object-keys": "^1.1.1" } }, "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg=="],
@@ -484,6 +675,10 @@
 
     "detect-libc": ["detect-libc@2.0.3", "", {}, "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw=="],
 
+    "detect-newline": ["detect-newline@3.1.0", "", {}, "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA=="],
+
+    "diff-sequences": ["diff-sequences@29.6.3", "", {}, "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q=="],
+
     "doctrine": ["doctrine@2.1.0", "", { "dependencies": { "esutils": "^2.0.2" } }, "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw=="],
 
     "dot-case": ["dot-case@3.0.4", "", { "dependencies": { "no-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w=="],
@@ -492,10 +687,18 @@
 
     "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="],
 
+    "ejs": ["ejs@3.1.10", "", { "dependencies": { "jake": "^10.8.5" }, "bin": { "ejs": "bin/cli.js" } }, "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA=="],
+
+    "electron-to-chromium": ["electron-to-chromium@1.5.149", "", {}, "sha512-UyiO82eb9dVOx8YO3ajDf9jz2kKyt98DEITRdeLPstOEuTlLzDA4Gyq5K9he71TQziU5jUVu2OAu5N48HmQiyQ=="],
+
+    "emittery": ["emittery@0.13.1", "", {}, "sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ=="],
+
     "emoji-regex": ["emoji-regex@9.2.2", "", {}, "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="],
 
     "enhanced-resolve": ["enhanced-resolve@5.18.1", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg=="],
 
+    "error-ex": ["error-ex@1.3.2", "", { "dependencies": { "is-arrayish": "^0.2.1" } }, "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g=="],
+
     "es-abstract": ["es-abstract@1.23.9", "", { "dependencies": { "array-buffer-byte-length": "^1.0.2", "arraybuffer.prototype.slice": "^1.0.4", "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", "call-bound": "^1.0.3", "data-view-buffer": "^1.0.2", "data-view-byte-length": "^1.0.2", "data-view-byte-offset": "^1.0.1", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.0.0", "es-set-tostringtag": "^2.1.0", "es-to-primitive": "^1.3.0", "function.prototype.name": "^1.1.8", "get-intrinsic": "^1.2.7", "get-proto": "^1.0.0", "get-symbol-description": "^1.1.0", "globalthis": "^1.0.4", "gopd": "^1.2.0", "has-property-descriptors": "^1.0.2", "has-proto": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "internal-slot": "^1.1.0", "is-array-buffer": "^3.0.5", "is-callable": "^1.2.7", "is-data-view": "^1.0.2", "is-regex": "^1.2.1", "is-shared-array-buffer": "^1.0.4", "is-string": "^1.1.1", "is-typed-array": "^1.1.15", "is-weakref": "^1.1.0", "math-intrinsics": "^1.1.0", "object-inspect": "^1.13.3", "object-keys": "^1.1.1", "object.assign": "^4.1.7", "own-keys": "^1.0.1", "regexp.prototype.flags": "^1.5.3", "safe-array-concat": "^1.1.3", "safe-push-apply": "^1.0.0", "safe-regex-test": "^1.1.0", "set-proto": "^1.0.0", "string.prototype.trim": "^1.2.10", "string.prototype.trimend": "^1.0.9", "string.prototype.trimstart": "^1.0.8", "typed-array-buffer": "^1.0.3", "typed-array-byte-length": "^1.0.3", "typed-array-byte-offset": "^1.0.4", "typed-array-length": "^1.0.7", "unbox-primitive": "^1.1.0", "which-typed-array": "^1.1.18" } }, "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA=="],
 
     "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="],
@@ -516,6 +719,8 @@
 
     "esbuild-register": ["esbuild-register@3.6.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "esbuild": ">=0.12 <1" } }, "sha512-H2/S7Pm8a9CL1uhp9OvjwrBh5Pvx0H8qVOxNu8Wed9Y7qv56MPtq+GGM8RJpq6glYJn9Wspr8uw7l55uyinNeg=="],
 
+    "escalade": ["escalade@3.2.0", "", {}, "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="],
+
     "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="],
 
     "eslint": ["eslint@9.23.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", "@eslint/config-array": "^0.19.2", "@eslint/config-helpers": "^0.2.0", "@eslint/core": "^0.12.0", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "9.23.0", "@eslint/plugin-kit": "^0.2.7", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.4.2", "@types/estree": "^1.0.6", "@types/json-schema": "^7.0.15", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.6", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", "eslint-scope": "^8.3.0", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "json-stable-stringify-without-jsonify": "^1.0.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", "optionator": "^0.9.3" }, "peerDependencies": { "jiti": "*" }, "bin": "bin/eslint.js" }, "sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw=="],
@@ -542,6 +747,8 @@
 
     "espree": ["espree@10.3.0", "", { "dependencies": { "acorn": "^8.14.0", "acorn-jsx": "^5.3.2", "eslint-visitor-keys": "^4.2.0" } }, "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg=="],
 
+    "esprima": ["esprima@4.0.1", "", { "bin": { "esparse": "./bin/esparse.js", "esvalidate": "./bin/esvalidate.js" } }, "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="],
+
     "esquery": ["esquery@1.6.0", "", { "dependencies": { "estraverse": "^5.1.0" } }, "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg=="],
 
     "esrecurse": ["esrecurse@4.3.0", "", { "dependencies": { "estraverse": "^5.2.0" } }, "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag=="],
@@ -550,6 +757,12 @@
 
     "esutils": ["esutils@2.0.3", "", {}, "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="],
 
+    "execa": ["execa@5.1.1", "", { "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", "human-signals": "^2.1.0", "is-stream": "^2.0.0", "merge-stream": "^2.0.0", "npm-run-path": "^4.0.1", "onetime": "^5.1.2", "signal-exit": "^3.0.3", "strip-final-newline": "^2.0.0" } }, "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg=="],
+
+    "exit": ["exit@0.1.2", "", {}, "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ=="],
+
+    "expect": ["expect@29.7.0", "", { "dependencies": { "@jest/expect-utils": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw=="],
+
     "fast-deep-equal": ["fast-deep-equal@3.1.3", "", {}, "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="],
 
     "fast-glob": ["fast-glob@3.3.1", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.4" } }, "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg=="],
@@ -560,12 +773,16 @@
 
     "fastq": ["fastq@1.19.1", "", { "dependencies": { "reusify": "^1.0.4" } }, "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ=="],
 
+    "fb-watchman": ["fb-watchman@2.0.2", "", { "dependencies": { "bser": "2.1.1" } }, "sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA=="],
+
     "fdir": ["fdir@6.4.3", "", { "peerDependencies": { "picomatch": "^3 || ^4" } }, "sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw=="],
 
     "fetch-blob": ["fetch-blob@3.2.0", "", { "dependencies": { "node-domexception": "^1.0.0", "web-streams-polyfill": "^3.0.3" } }, "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ=="],
 
     "file-entry-cache": ["file-entry-cache@8.0.0", "", { "dependencies": { "flat-cache": "^4.0.0" } }, "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ=="],
 
+    "filelist": ["filelist@1.0.4", "", { "dependencies": { "minimatch": "^5.0.1" } }, "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q=="],
+
     "fill-range": ["fill-range@7.1.1", "", { "dependencies": { "to-regex-range": "^5.0.1" } }, "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg=="],
 
     "find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="],
@@ -582,6 +799,8 @@
 
     "framer-motion": ["framer-motion@12.6.0", "", { "dependencies": { "motion-dom": "^12.6.0", "motion-utils": "^12.5.0", "tslib": "^2.4.0" }, "peerDependencies": { "@emotion/is-prop-valid": "*", "react": "^18.0.0 || ^19.0.0", "react-dom": "^18.0.0 || ^19.0.0" }, "optionalPeers": ["@emotion/is-prop-valid"] }, "sha512-91XLZ3VwDlXe9u2ABhTzYBiFQ/qdoiqyTiTCQDDJ4es5/5lzp76hdB+WG7gcNklcQlOmfDZQqVO48tqzY9Z/bQ=="],
 
+    "fs.realpath": ["fs.realpath@1.0.0", "", {}, "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="],
+
     "fsevents": ["fsevents@2.3.3", "", { "os": "darwin" }, "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw=="],
 
     "function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
@@ -590,15 +809,23 @@
 
     "functions-have-names": ["functions-have-names@1.2.3", "", {}, "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ=="],
 
+    "gensync": ["gensync@1.0.0-beta.2", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],
+
+    "get-caller-file": ["get-caller-file@2.0.5", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],
+
     "get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
 
+    "get-package-type": ["get-package-type@0.1.0", "", {}, "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="],
+
     "get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
 
+    "get-stream": ["get-stream@6.0.1", "", {}, "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg=="],
+
     "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="],
 
     "get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="],
 
-    "glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
+    "glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="],
 
     "glob-parent": ["glob-parent@6.0.2", "", { "dependencies": { "is-glob": "^4.0.3" } }, "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A=="],
 
@@ -628,19 +855,29 @@
 
     "hasown": ["hasown@2.0.2", "", { "dependencies": { "function-bind": "^1.1.2" } }, "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ=="],
 
+    "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="],
+
     "https-proxy-agent": ["https-proxy-agent@7.0.6", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],
 
+    "human-signals": ["human-signals@2.1.0", "", {}, "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="],
+
     "ignore": ["ignore@5.3.2", "", {}, "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g=="],
 
     "import-fresh": ["import-fresh@3.3.1", "", { "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" } }, "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ=="],
 
+    "import-local": ["import-local@3.2.0", "", { "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" }, "bin": { "import-local-fixture": "fixtures/cli.js" } }, "sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA=="],
+
     "imurmurhash": ["imurmurhash@0.1.4", "", {}, "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="],
 
+    "inflight": ["inflight@1.0.6", "", { "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA=="],
+
+    "inherits": ["inherits@2.0.4", "", {}, "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="],
+
     "internal-slot": ["internal-slot@1.1.0", "", { "dependencies": { "es-errors": "^1.3.0", "hasown": "^2.0.2", "side-channel": "^1.1.0" } }, "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw=="],
 
     "is-array-buffer": ["is-array-buffer@3.0.5", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "get-intrinsic": "^1.2.6" } }, "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A=="],
 
-    "is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
+    "is-arrayish": ["is-arrayish@0.2.1", "", {}, "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg=="],
 
     "is-async-function": ["is-async-function@2.1.1", "", { "dependencies": { "async-function": "^1.0.0", "call-bound": "^1.0.3", "get-proto": "^1.0.1", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ=="],
 
@@ -664,6 +901,8 @@
 
     "is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="],
 
+    "is-generator-fn": ["is-generator-fn@2.1.0", "", {}, "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ=="],
+
     "is-generator-function": ["is-generator-function@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "get-proto": "^1.0.0", "has-tostringtag": "^1.0.2", "safe-regex-test": "^1.1.0" } }, "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ=="],
 
     "is-glob": ["is-glob@4.0.3", "", { "dependencies": { "is-extglob": "^2.1.1" } }, "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg=="],
@@ -680,6 +919,8 @@
 
     "is-shared-array-buffer": ["is-shared-array-buffer@1.0.4", "", { "dependencies": { "call-bound": "^1.0.3" } }, "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A=="],
 
+    "is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="],
+
     "is-string": ["is-string@1.1.1", "", { "dependencies": { "call-bound": "^1.0.3", "has-tostringtag": "^1.0.2" } }, "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA=="],
 
     "is-symbol": ["is-symbol@1.1.1", "", { "dependencies": { "call-bound": "^1.0.2", "has-symbols": "^1.1.0", "safe-regex-test": "^1.1.0" } }, "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w=="],
@@ -696,10 +937,74 @@
 
     "isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
 
+    "istanbul-lib-coverage": ["istanbul-lib-coverage@3.2.2", "", {}, "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg=="],
+
+    "istanbul-lib-instrument": ["istanbul-lib-instrument@6.0.3", "", { "dependencies": { "@babel/core": "^7.23.9", "@babel/parser": "^7.23.9", "@istanbuljs/schema": "^0.1.3", "istanbul-lib-coverage": "^3.2.0", "semver": "^7.5.4" } }, "sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q=="],
+
+    "istanbul-lib-report": ["istanbul-lib-report@3.0.1", "", { "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", "supports-color": "^7.1.0" } }, "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw=="],
+
+    "istanbul-lib-source-maps": ["istanbul-lib-source-maps@4.0.1", "", { "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", "source-map": "^0.6.1" } }, "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw=="],
+
+    "istanbul-reports": ["istanbul-reports@3.1.7", "", { "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" } }, "sha512-BewmUXImeuRk2YY0PVbxgKAysvhRPUQE0h5QRM++nVWyubKGV0l8qQ5op8+B2DOmwSe63Jivj0BjkPQVf8fP5g=="],
+
     "iterator.prototype": ["iterator.prototype@1.1.5", "", { "dependencies": { "define-data-property": "^1.1.4", "es-object-atoms": "^1.0.0", "get-intrinsic": "^1.2.6", "get-proto": "^1.0.0", "has-symbols": "^1.1.0", "set-function-name": "^2.0.2" } }, "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g=="],
 
     "jackspeak": ["jackspeak@3.4.3", "", { "dependencies": { "@isaacs/cliui": "^8.0.2" }, "optionalDependencies": { "@pkgjs/parseargs": "^0.11.0" } }, "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw=="],
 
+    "jake": ["jake@10.9.2", "", { "dependencies": { "async": "^3.2.3", "chalk": "^4.0.2", "filelist": "^1.0.4", "minimatch": "^3.1.2" }, "bin": { "jake": "bin/cli.js" } }, "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA=="],
+
+    "jest": ["jest@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", "import-local": "^3.0.2", "jest-cli": "^29.7.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw=="],
+
+    "jest-changed-files": ["jest-changed-files@29.7.0", "", { "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0" } }, "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w=="],
+
+    "jest-circus": ["jest-circus@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "co": "^4.6.0", "dedent": "^1.0.0", "is-generator-fn": "^2.0.0", "jest-each": "^29.7.0", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-runtime": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "p-limit": "^3.1.0", "pretty-format": "^29.7.0", "pure-rand": "^6.0.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw=="],
+
+    "jest-cli": ["jest-cli@29.7.0", "", { "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "chalk": "^4.0.0", "create-jest": "^29.7.0", "exit": "^0.1.2", "import-local": "^3.0.2", "jest-config": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "yargs": "^17.3.1" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" }, "optionalPeers": ["node-notifier"], "bin": { "jest": "bin/jest.js" } }, "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg=="],
+
+    "jest-config": ["jest-config@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", "@jest/types": "^29.6.3", "babel-jest": "^29.7.0", "chalk": "^4.0.0", "ci-info": "^3.2.0", "deepmerge": "^4.2.2", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-circus": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-get-type": "^29.6.3", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-runner": "^29.7.0", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "micromatch": "^4.0.4", "parse-json": "^5.2.0", "pretty-format": "^29.7.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "peerDependencies": { "@types/node": "*", "ts-node": ">=9.0.0" }, "optionalPeers": ["@types/node", "ts-node"] }, "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ=="],
+
+    "jest-diff": ["jest-diff@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "diff-sequences": "^29.6.3", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw=="],
+
+    "jest-docblock": ["jest-docblock@29.7.0", "", { "dependencies": { "detect-newline": "^3.0.0" } }, "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g=="],
+
+    "jest-each": ["jest-each@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "jest-util": "^29.7.0", "pretty-format": "^29.7.0" } }, "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ=="],
+
+    "jest-environment-node": ["jest-environment-node@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "jest-mock": "^29.7.0", "jest-util": "^29.7.0" } }, "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw=="],
+
+    "jest-get-type": ["jest-get-type@29.6.3", "", {}, "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw=="],
+
+    "jest-haste-map": ["jest-haste-map@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/graceful-fs": "^4.1.3", "@types/node": "*", "anymatch": "^3.0.3", "fb-watchman": "^2.0.0", "graceful-fs": "^4.2.9", "jest-regex-util": "^29.6.3", "jest-util": "^29.7.0", "jest-worker": "^29.7.0", "micromatch": "^4.0.4", "walker": "^1.0.8" }, "optionalDependencies": { "fsevents": "^2.3.2" } }, "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA=="],
+
+    "jest-leak-detector": ["jest-leak-detector@29.7.0", "", { "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw=="],
+
+    "jest-matcher-utils": ["jest-matcher-utils@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" } }, "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g=="],
+
+    "jest-message-util": ["jest-message-util@29.7.0", "", { "dependencies": { "@babel/code-frame": "^7.12.13", "@jest/types": "^29.6.3", "@types/stack-utils": "^2.0.0", "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "micromatch": "^4.0.4", "pretty-format": "^29.7.0", "slash": "^3.0.0", "stack-utils": "^2.0.3" } }, "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w=="],
+
+    "jest-mock": ["jest-mock@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "jest-util": "^29.7.0" } }, "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw=="],
+
+    "jest-pnp-resolver": ["jest-pnp-resolver@1.2.3", "", { "peerDependencies": { "jest-resolve": "*" }, "optionalPeers": ["jest-resolve"] }, "sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w=="],
+
+    "jest-regex-util": ["jest-regex-util@29.6.3", "", {}, "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg=="],
+
+    "jest-resolve": ["jest-resolve@29.7.0", "", { "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-pnp-resolver": "^1.2.2", "jest-util": "^29.7.0", "jest-validate": "^29.7.0", "resolve": "^1.20.0", "resolve.exports": "^2.0.0", "slash": "^3.0.0" } }, "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA=="],
+
+    "jest-resolve-dependencies": ["jest-resolve-dependencies@29.7.0", "", { "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" } }, "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA=="],
+
+    "jest-runner": ["jest-runner@29.7.0", "", { "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "emittery": "^0.13.1", "graceful-fs": "^4.2.9", "jest-docblock": "^29.7.0", "jest-environment-node": "^29.7.0", "jest-haste-map": "^29.7.0", "jest-leak-detector": "^29.7.0", "jest-message-util": "^29.7.0", "jest-resolve": "^29.7.0", "jest-runtime": "^29.7.0", "jest-util": "^29.7.0", "jest-watcher": "^29.7.0", "jest-worker": "^29.7.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" } }, "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ=="],
+
+    "jest-runtime": ["jest-runtime@29.7.0", "", { "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", "@jest/globals": "^29.7.0", "@jest/source-map": "^29.6.3", "@jest/test-result": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "cjs-module-lexer": "^1.0.0", "collect-v8-coverage": "^1.0.0", "glob": "^7.1.3", "graceful-fs": "^4.2.9", "jest-haste-map": "^29.7.0", "jest-message-util": "^29.7.0", "jest-mock": "^29.7.0", "jest-regex-util": "^29.6.3", "jest-resolve": "^29.7.0", "jest-snapshot": "^29.7.0", "jest-util": "^29.7.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" } }, "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ=="],
+
+    "jest-snapshot": ["jest-snapshot@29.7.0", "", { "dependencies": { "@babel/core": "^7.11.6", "@babel/generator": "^7.7.2", "@babel/plugin-syntax-jsx": "^7.7.2", "@babel/plugin-syntax-typescript": "^7.7.2", "@babel/types": "^7.3.3", "@jest/expect-utils": "^29.7.0", "@jest/transform": "^29.7.0", "@jest/types": "^29.6.3", "babel-preset-current-node-syntax": "^1.0.0", "chalk": "^4.0.0", "expect": "^29.7.0", "graceful-fs": "^4.2.9", "jest-diff": "^29.7.0", "jest-get-type": "^29.6.3", "jest-matcher-utils": "^29.7.0", "jest-message-util": "^29.7.0", "jest-util": "^29.7.0", "natural-compare": "^1.4.0", "pretty-format": "^29.7.0", "semver": "^7.5.3" } }, "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw=="],
+
+    "jest-util": ["jest-util@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "@types/node": "*", "chalk": "^4.0.0", "ci-info": "^3.2.0", "graceful-fs": "^4.2.9", "picomatch": "^2.2.3" } }, "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA=="],
+
+    "jest-validate": ["jest-validate@29.7.0", "", { "dependencies": { "@jest/types": "^29.6.3", "camelcase": "^6.2.0", "chalk": "^4.0.0", "jest-get-type": "^29.6.3", "leven": "^3.1.0", "pretty-format": "^29.7.0" } }, "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw=="],
+
+    "jest-watcher": ["jest-watcher@29.7.0", "", { "dependencies": { "@jest/test-result": "^29.7.0", "@jest/types": "^29.6.3", "@types/node": "*", "ansi-escapes": "^4.2.1", "chalk": "^4.0.0", "emittery": "^0.13.1", "jest-util": "^29.7.0", "string-length": "^4.0.1" } }, "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g=="],
+
+    "jest-worker": ["jest-worker@29.7.0", "", { "dependencies": { "@types/node": "*", "jest-util": "^29.7.0", "merge-stream": "^2.0.0", "supports-color": "^8.0.0" } }, "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw=="],
+
     "jiti": ["jiti@2.4.2", "", { "bin": "lib/jiti-cli.mjs" }, "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A=="],
 
     "jose": ["jose@4.15.9", "", {}, "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA=="],
@@ -710,22 +1015,30 @@
 
     "js-yaml": ["js-yaml@4.1.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA=="],
 
+    "jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
+
     "json-buffer": ["json-buffer@3.0.1", "", {}, "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="],
 
+    "json-parse-even-better-errors": ["json-parse-even-better-errors@2.3.1", "", {}, "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w=="],
+
     "json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
 
     "json-stable-stringify-without-jsonify": ["json-stable-stringify-without-jsonify@1.0.1", "", {}, "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="],
 
-    "json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": "lib/cli.js" }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
+    "json5": ["json5@2.2.3", "", { "bin": { "json5": "lib/cli.js" } }, "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="],
 
     "jsx-ast-utils": ["jsx-ast-utils@3.3.5", "", { "dependencies": { "array-includes": "^3.1.6", "array.prototype.flat": "^1.3.1", "object.assign": "^4.1.4", "object.values": "^1.1.6" } }, "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ=="],
 
     "keyv": ["keyv@4.5.4", "", { "dependencies": { "json-buffer": "3.0.1" } }, "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw=="],
 
+    "kleur": ["kleur@3.0.3", "", {}, "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w=="],
+
     "language-subtag-registry": ["language-subtag-registry@0.3.23", "", {}, "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ=="],
 
     "language-tags": ["language-tags@1.0.9", "", { "dependencies": { "language-subtag-registry": "^0.3.20" } }, "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA=="],
 
+    "leven": ["leven@3.1.0", "", {}, "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A=="],
+
     "levn": ["levn@0.4.1", "", { "dependencies": { "prelude-ls": "^1.2.1", "type-check": "~0.4.0" } }, "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ=="],
 
     "lightningcss": ["lightningcss@1.29.2", "", { "dependencies": { "detect-libc": "^2.0.3" }, "optionalDependencies": { "lightningcss-darwin-arm64": "1.29.2", "lightningcss-darwin-x64": "1.29.2", "lightningcss-freebsd-x64": "1.29.2", "lightningcss-linux-arm-gnueabihf": "1.29.2", "lightningcss-linux-arm64-gnu": "1.29.2", "lightningcss-linux-arm64-musl": "1.29.2", "lightningcss-linux-x64-gnu": "1.29.2", "lightningcss-linux-x64-musl": "1.29.2", "lightningcss-win32-arm64-msvc": "1.29.2", "lightningcss-win32-x64-msvc": "1.29.2" } }, "sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA=="],
@@ -750,24 +1063,38 @@
 
     "lightningcss-win32-x64-msvc": ["lightningcss-win32-x64-msvc@1.29.2", "", { "os": "win32", "cpu": "x64" }, "sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA=="],
 
+    "lines-and-columns": ["lines-and-columns@1.2.4", "", {}, "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg=="],
+
     "locate-path": ["locate-path@6.0.0", "", { "dependencies": { "p-locate": "^5.0.0" } }, "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw=="],
 
+    "lodash.memoize": ["lodash.memoize@4.1.2", "", {}, "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag=="],
+
     "lodash.merge": ["lodash.merge@4.6.2", "", {}, "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="],
 
     "loose-envify": ["loose-envify@1.4.0", "", { "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" }, "bin": "cli.js" }, "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q=="],
 
     "lower-case": ["lower-case@2.0.2", "", { "dependencies": { "tslib": "^2.0.3" } }, "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg=="],
 
-    "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+    "lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
+
+    "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="],
+
+    "make-error": ["make-error@1.3.6", "", {}, "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw=="],
+
+    "makeerror": ["makeerror@1.0.12", "", { "dependencies": { "tmpl": "1.0.5" } }, "sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg=="],
 
     "map-obj": ["map-obj@4.3.0", "", {}, "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ=="],
 
     "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="],
 
+    "merge-stream": ["merge-stream@2.0.0", "", {}, "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="],
+
     "merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
 
     "micromatch": ["micromatch@4.0.8", "", { "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" } }, "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA=="],
 
+    "mimic-fn": ["mimic-fn@2.1.0", "", {}, "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg=="],
+
     "minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="],
 
     "minimist": ["minimist@1.2.8", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],
@@ -796,8 +1123,16 @@
 
     "node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="],
 
+    "node-int64": ["node-int64@0.4.0", "", {}, "sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw=="],
+
+    "node-releases": ["node-releases@2.0.19", "", {}, "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="],
+
+    "normalize-path": ["normalize-path@3.0.0", "", {}, "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="],
+
     "npm-normalize-package-bin": ["npm-normalize-package-bin@4.0.0", "", {}, "sha512-TZKxPvItzai9kN9H/TkmCtx/ZN/hvr3vUycjlfmH0ootY9yFBzNOpiXAdIn1Iteqsvk4lQn6B5PTrt+n6h8k/w=="],
 
+    "npm-run-path": ["npm-run-path@4.0.1", "", { "dependencies": { "path-key": "^3.0.0" } }, "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw=="],
+
     "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
 
     "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
@@ -814,6 +1149,10 @@
 
     "object.values": ["object.values@1.2.1", "", { "dependencies": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA=="],
 
+    "once": ["once@1.4.0", "", { "dependencies": { "wrappy": "1" } }, "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w=="],
+
+    "onetime": ["onetime@5.1.2", "", { "dependencies": { "mimic-fn": "^2.1.0" } }, "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg=="],
+
     "optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="],
 
     "own-keys": ["own-keys@1.0.1", "", { "dependencies": { "get-intrinsic": "^1.2.6", "object-keys": "^1.1.1", "safe-push-apply": "^1.0.0" } }, "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg=="],
@@ -822,12 +1161,18 @@
 
     "p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="],
 
+    "p-try": ["p-try@2.2.0", "", {}, "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="],
+
     "package-json-from-dist": ["package-json-from-dist@1.0.1", "", {}, "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw=="],
 
     "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
 
+    "parse-json": ["parse-json@5.2.0", "", { "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", "json-parse-even-better-errors": "^2.3.0", "lines-and-columns": "^1.1.6" } }, "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg=="],
+
     "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
 
+    "path-is-absolute": ["path-is-absolute@1.0.1", "", {}, "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="],
+
     "path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
 
     "path-parse": ["path-parse@1.0.7", "", {}, "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw=="],
@@ -836,7 +1181,11 @@
 
     "picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
 
-    "picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
+    "picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+
+    "pirates": ["pirates@4.0.7", "", {}, "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA=="],
+
+    "pkg-dir": ["pkg-dir@4.2.0", "", { "dependencies": { "find-up": "^4.0.0" } }, "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ=="],
 
     "possible-typed-array-names": ["possible-typed-array-names@1.1.0", "", {}, "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg=="],
 
@@ -844,14 +1193,20 @@
 
     "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="],
 
+    "pretty-format": ["pretty-format@29.7.0", "", { "dependencies": { "@jest/schemas": "^29.6.3", "ansi-styles": "^5.0.0", "react-is": "^18.0.0" } }, "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ=="],
+
     "prisma": ["prisma@6.5.0", "", { "dependencies": { "@prisma/config": "6.5.0", "@prisma/engines": "6.5.0" }, "optionalDependencies": { "fsevents": "2.3.3" }, "peerDependencies": { "typescript": ">=5.1.0" }, "bin": "build/index.js" }, "sha512-yUGXmWqv5F4PByMSNbYFxke/WbnyTLjnJ5bKr8fLkcnY7U5rU9rUTh/+Fja+gOrRxEgtCbCtca94IeITj4j/pg=="],
 
     "proc-log": ["proc-log@5.0.0", "", {}, "sha512-Azwzvl90HaF0aCz1JrDdXQykFakSSNPaPoiZ9fm5qJIMHioDZEi7OAdRwSm6rSoPtY3Qutnm3L7ogmg3dc+wbQ=="],
 
+    "prompts": ["prompts@2.4.2", "", { "dependencies": { "kleur": "^3.0.3", "sisteransi": "^1.0.5" } }, "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q=="],
+
     "prop-types": ["prop-types@15.8.1", "", { "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", "react-is": "^16.13.1" } }, "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg=="],
 
     "punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="],
 
+    "pure-rand": ["pure-rand@6.1.0", "", {}, "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA=="],
+
     "queue-microtask": ["queue-microtask@1.2.3", "", {}, "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="],
 
     "react": ["react@19.0.0", "", {}, "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="],
@@ -862,7 +1217,7 @@
 
     "react-hook-form": ["react-hook-form@7.54.2", "", { "peerDependencies": { "react": "^16.8.0 || ^17 || ^18 || ^19" } }, "sha512-eHpAUgUjWbZocoQYUHposymRb4ZP6d0uwUnooL2uOybA9/3tPUvoAKqEWK1WaSiTxxOfTpffNZP7QwlnM3/gEg=="],
 
-    "react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
+    "react-is": ["react-is@18.3.1", "", {}, "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg=="],
 
     "read-cmd-shim": ["read-cmd-shim@5.0.0", "", {}, "sha512-SEbJV7tohp3DAAILbEMPXavBjAnMN0tVnh4+9G8ihV4Pq3HYF9h8QNez9zkJ1ILkv9G2BjdzwctznGZXgu/HGw=="],
 
@@ -870,12 +1225,18 @@
 
     "regexp.prototype.flags": ["regexp.prototype.flags@1.5.4", "", { "dependencies": { "call-bind": "^1.0.8", "define-properties": "^1.2.1", "es-errors": "^1.3.0", "get-proto": "^1.0.1", "gopd": "^1.2.0", "set-function-name": "^2.0.2" } }, "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA=="],
 
+    "require-directory": ["require-directory@2.1.1", "", {}, "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q=="],
+
     "resolve": ["resolve@1.22.10", "", { "dependencies": { "is-core-module": "^2.16.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w=="],
 
+    "resolve-cwd": ["resolve-cwd@3.0.0", "", { "dependencies": { "resolve-from": "^5.0.0" } }, "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg=="],
+
     "resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
 
     "resolve-pkg-maps": ["resolve-pkg-maps@1.0.0", "", {}, "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw=="],
 
+    "resolve.exports": ["resolve.exports@2.0.3", "", {}, "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A=="],
+
     "reusify": ["reusify@1.1.0", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],
 
     "rimraf": ["rimraf@5.0.10", "", { "dependencies": { "glob": "^10.3.7" }, "bin": "dist/esm/bin.mjs" }, "sha512-l0OE8wL34P4nJH/H2ffoaniAokM2qSmrtXHmlpvYr5AVVX8msAyW0l8NVJFDxlSK4u3Uh/f41cQheDVdnYijwQ=="],
@@ -892,7 +1253,7 @@
 
     "scheduler": ["scheduler@0.25.0", "", {}, "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA=="],
 
-    "semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+    "semver": ["semver@7.7.1", "", { "bin": "bin/semver.js" }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
 
     "server-only": ["server-only@0.0.1", "", {}, "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA=="],
 
@@ -922,19 +1283,33 @@
 
     "simple-swizzle": ["simple-swizzle@0.2.2", "", { "dependencies": { "is-arrayish": "^0.3.1" } }, "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg=="],
 
+    "sisteransi": ["sisteransi@1.0.5", "", {}, "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg=="],
+
+    "slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
+
     "snake-case": ["snake-case@3.0.4", "", { "dependencies": { "dot-case": "^3.0.4", "tslib": "^2.0.3" } }, "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg=="],
 
     "snakecase-keys": ["snakecase-keys@8.0.1", "", { "dependencies": { "map-obj": "^4.1.0", "snake-case": "^3.0.4", "type-fest": "^4.15.0" } }, "sha512-Sj51kE1zC7zh6TDlNNz0/Jn1n5HiHdoQErxO8jLtnyrkJW/M5PrI7x05uDgY3BO7OUQYKCvmeMurW6BPUdwEOw=="],
 
+    "source-map": ["source-map@0.6.1", "", {}, "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g=="],
+
     "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
 
+    "source-map-support": ["source-map-support@0.5.13", "", { "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" } }, "sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w=="],
+
+    "sprintf-js": ["sprintf-js@1.0.3", "", {}, "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="],
+
     "stable-hash": ["stable-hash@0.0.5", "", {}, "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA=="],
 
+    "stack-utils": ["stack-utils@2.0.6", "", { "dependencies": { "escape-string-regexp": "^2.0.0" } }, "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ=="],
+
     "std-env": ["std-env@3.8.1", "", {}, "sha512-vj5lIj3Mwf9D79hBkltk5qmkFI+biIKWS2IBxEyEU3AX1tUf7AoL8nSazCOiiqQsGKIq01SClsKEzweu34uwvA=="],
 
     "streamsearch": ["streamsearch@1.1.0", "", {}, "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg=="],
 
-    "string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
+    "string-length": ["string-length@4.0.2", "", { "dependencies": { "char-regex": "^1.0.2", "strip-ansi": "^6.0.0" } }, "sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ=="],
+
+    "string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
 
     "string-width-cjs": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
 
@@ -950,12 +1325,14 @@
 
     "string.prototype.trimstart": ["string.prototype.trimstart@1.0.8", "", { "dependencies": { "call-bind": "^1.0.7", "define-properties": "^1.2.1", "es-object-atoms": "^1.0.0" } }, "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg=="],
 
-    "strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
+    "strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
 
     "strip-ansi-cjs": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
 
     "strip-bom": ["strip-bom@3.0.0", "", {}, "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA=="],
 
+    "strip-final-newline": ["strip-final-newline@2.0.0", "", {}, "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA=="],
+
     "strip-json-comments": ["strip-json-comments@3.1.1", "", {}, "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="],
 
     "styled-jsx": ["styled-jsx@5.1.6", "", { "dependencies": { "client-only": "0.0.1" }, "peerDependencies": { "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" } }, "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA=="],
@@ -976,21 +1353,29 @@
 
     "tar": ["tar@7.4.3", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.0.1", "mkdirp": "^3.0.1", "yallist": "^5.0.0" } }, "sha512-5S7Va8hKfV7W5U6g3aYxXmlPoZVAwUMy9AOKyF2fVuZa2UD3qZjg578OrLRt8PcNN1PleVaL/5/yYATNL0ICUw=="],
 
+    "test-exclude": ["test-exclude@6.0.0", "", { "dependencies": { "@istanbuljs/schema": "^0.1.2", "glob": "^7.1.4", "minimatch": "^3.0.4" } }, "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w=="],
+
     "tinyglobby": ["tinyglobby@0.2.12", "", { "dependencies": { "fdir": "^6.4.3", "picomatch": "^4.0.2" } }, "sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww=="],
 
+    "tmpl": ["tmpl@1.0.5", "", {}, "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw=="],
+
     "to-regex-range": ["to-regex-range@5.0.1", "", { "dependencies": { "is-number": "^7.0.0" } }, "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ=="],
 
     "tr46": ["tr46@0.0.3", "", {}, "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="],
 
     "ts-api-utils": ["ts-api-utils@2.1.0", "", { "peerDependencies": { "typescript": ">=4.8.4" } }, "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ=="],
 
+    "ts-jest": ["ts-jest@29.3.2", "", { "dependencies": { "bs-logger": "^0.2.6", "ejs": "^3.1.10", "fast-json-stable-stringify": "^2.1.0", "jest-util": "^29.0.0", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", "semver": "^7.7.1", "type-fest": "^4.39.1", "yargs-parser": "^21.1.1" }, "peerDependencies": { "@babel/core": ">=7.0.0-beta.0 <8", "@jest/transform": "^29.0.0", "@jest/types": "^29.0.0", "babel-jest": "^29.0.0", "jest": "^29.0.0", "typescript": ">=4.3 <6" }, "optionalPeers": ["@babel/core", "@jest/transform", "@jest/types", "babel-jest"], "bin": { "ts-jest": "cli.js" } }, "sha512-bJJkrWc6PjFVz5g2DGCNUo8z7oFEYaz1xP1NpeDU7KNLMWPpEyV8Chbpkn8xjzgRDpQhnGMyvyldoL7h8JXyug=="],
+
     "tsconfig-paths": ["tsconfig-paths@3.15.0", "", { "dependencies": { "@types/json5": "^0.0.29", "json5": "^1.0.2", "minimist": "^1.2.6", "strip-bom": "^3.0.0" } }, "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg=="],
 
     "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],
 
     "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="],
 
-    "type-fest": ["type-fest@4.38.0", "", {}, "sha512-2dBz5D5ycHIoliLYLi0Q2V7KRaDlH0uWIvmk7TYlAg5slqwiPv1ezJdZm1QEM0xgk29oYWMCbIG7E6gHpvChlg=="],
+    "type-detect": ["type-detect@4.0.8", "", {}, "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="],
+
+    "type-fest": ["type-fest@4.40.1", "", {}, "sha512-9YvLNnORDpI+vghLU/Nf+zSv0kL47KbVJ1o3sKgoTefl6i+zebxbiDQWoe/oWWqPhIgQdRZRT1KA9sCPL810SA=="],
 
     "typed-array-buffer": ["typed-array-buffer@1.0.3", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" } }, "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw=="],
 
@@ -1006,10 +1391,16 @@
 
     "undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="],
 
+    "update-browserslist-db": ["update-browserslist-db@1.1.3", "", { "dependencies": { "escalade": "^3.2.0", "picocolors": "^1.1.1" }, "peerDependencies": { "browserslist": ">= 4.21.0" }, "bin": { "update-browserslist-db": "cli.js" } }, "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw=="],
+
     "uri-js": ["uri-js@4.4.1", "", { "dependencies": { "punycode": "^2.1.0" } }, "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg=="],
 
     "use-sync-external-store": ["use-sync-external-store@1.4.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw=="],
 
+    "v8-to-istanbul": ["v8-to-istanbul@9.3.0", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^2.0.0" } }, "sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA=="],
+
+    "walker": ["walker@1.0.8", "", { "dependencies": { "makeerror": "1.0.12" } }, "sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ=="],
+
     "web-streams-polyfill": ["web-streams-polyfill@3.3.3", "", {}, "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw=="],
 
     "webidl-conversions": ["webidl-conversions@3.0.1", "", {}, "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="],
@@ -1028,29 +1419,65 @@
 
     "word-wrap": ["word-wrap@1.2.5", "", {}, "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA=="],
 
-    "wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
+    "wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
 
     "wrap-ansi-cjs": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
 
+    "wrappy": ["wrappy@1.0.2", "", {}, "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="],
+
     "write-file-atomic": ["write-file-atomic@6.0.0", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^4.0.1" } }, "sha512-GmqrO8WJ1NuzJ2DrziEI2o57jKAVIQNf8a18W3nCYU3H7PNWqCCVTeH6/NQE93CIllIgQS98rrmVkYgTX9fFJQ=="],
 
     "ws": ["ws@8.18.1", "", { "peerDependencies": { "bufferutil": "^4.0.1", "utf-8-validate": ">=5.0.2" }, "optionalPeers": ["bufferutil", "utf-8-validate"] }, "sha512-RKW2aJZMXeMxVpnZ6bck+RswznaxmzdULiBr6KY7XkTnW8uvt0iT9H5DkHUChXrc+uurzwa0rVI16n/Xzjdz1w=="],
 
+    "y18n": ["y18n@5.0.8", "", {}, "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA=="],
+
     "yallist": ["yallist@5.0.0", "", {}, "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw=="],
 
+    "yargs": ["yargs@17.7.2", "", { "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", "get-caller-file": "^2.0.5", "require-directory": "^2.1.1", "string-width": "^4.2.3", "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } }, "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w=="],
+
+    "yargs-parser": ["yargs-parser@21.1.1", "", {}, "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw=="],
+
     "yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],
 
+    "@babel/core/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+    "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+    "@babel/traverse/globals": ["globals@11.12.0", "", {}, "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="],
+
     "@clerk/themes/@clerk/types": ["@clerk/types@4.50.1", "", { "dependencies": { "csstype": "3.1.3" } }, "sha512-GwsW/6LPHavHghh2QpmDbhyIuDP61OYV0T6x5hnjgAxjfexpRymbewR7Qez7H4kOo4gtnCNUrgTZ6nyresLEEg=="],
 
     "@eslint-community/eslint-utils/eslint-visitor-keys": ["eslint-visitor-keys@3.4.3", "", {}, "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag=="],
 
     "@humanfs/node/@humanwhocodes/retry": ["@humanwhocodes/retry@0.3.1", "", {}, "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA=="],
 
+    "@isaacs/cliui/string-width": ["string-width@5.1.2", "", { "dependencies": { "eastasianwidth": "^0.2.0", "emoji-regex": "^9.2.2", "strip-ansi": "^7.0.1" } }, "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA=="],
+
+    "@isaacs/cliui/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="],
+
+    "@isaacs/cliui/wrap-ansi": ["wrap-ansi@8.1.0", "", { "dependencies": { "ansi-styles": "^6.1.0", "string-width": "^5.0.1", "strip-ansi": "^7.0.1" } }, "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ=="],
+
+    "@istanbuljs/load-nyc-config/camelcase": ["camelcase@5.3.1", "", {}, "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="],
+
+    "@istanbuljs/load-nyc-config/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
+
+    "@istanbuljs/load-nyc-config/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="],
+
+    "@istanbuljs/load-nyc-config/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
+
+    "@jest/transform/write-file-atomic": ["write-file-atomic@4.0.2", "", { "dependencies": { "imurmurhash": "^0.1.4", "signal-exit": "^3.0.7" } }, "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg=="],
+
     "@typescript-eslint/typescript-estree/fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
 
     "@typescript-eslint/typescript-estree/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
 
-    "@typescript-eslint/typescript-estree/semver": ["semver@7.7.1", "", { "bin": "bin/semver.js" }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
+    "ansi-escapes/type-fest": ["type-fest@0.21.3", "", {}, "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w=="],
+
+    "babel-plugin-istanbul/istanbul-lib-instrument": ["istanbul-lib-instrument@5.2.1", "", { "dependencies": { "@babel/core": "^7.12.3", "@babel/parser": "^7.14.7", "@istanbuljs/schema": "^0.1.2", "istanbul-lib-coverage": "^3.2.0", "semver": "^6.3.0" } }, "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg=="],
+
+    "browserslist/caniuse-lite": ["caniuse-lite@1.0.30001716", "", {}, "sha512-49/c1+x3Kwz7ZIWt+4DvK3aMJy9oYXXG6/97JKsnjdCk/6n9vVyWL8NAwVt95Lwt9eigI10Hl782kDfZUUlRXw=="],
+
+    "chalk/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
 
     "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
 
@@ -1058,44 +1485,86 @@
 
     "eslint-plugin-import/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="],
 
+    "eslint-plugin-import/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
     "eslint-plugin-react/resolve": ["resolve@2.0.0-next.5", "", { "dependencies": { "is-core-module": "^2.13.0", "path-parse": "^1.0.7", "supports-preserve-symlinks-flag": "^1.0.0" }, "bin": "bin/resolve" }, "sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA=="],
 
     "eslint-plugin-react/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
 
+    "execa/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
+
     "fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
 
-    "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+    "fdir/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
+
+    "filelist/minimatch": ["minimatch@5.1.6", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g=="],
+
+    "jest-runtime/strip-bom": ["strip-bom@4.0.0", "", {}, "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w=="],
 
-    "is-bun-module/semver": ["semver@7.7.1", "", { "bin": "bin/semver.js" }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
+    "jest-worker/supports-color": ["supports-color@8.1.1", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q=="],
 
-    "micromatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
+    "lru-cache/yallist": ["yallist@3.1.1", "", {}, "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="],
 
     "next/postcss": ["postcss@8.4.31", "", { "dependencies": { "nanoid": "^3.3.6", "picocolors": "^1.0.0", "source-map-js": "^1.0.2" } }, "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ=="],
 
-    "sharp/semver": ["semver@7.7.1", "", { "bin": "bin/semver.js" }, "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA=="],
+    "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="],
+
+    "pkg-dir/find-up": ["find-up@4.1.0", "", { "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" } }, "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw=="],
+
+    "prop-types/react-is": ["react-is@16.13.1", "", {}, "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="],
+
+    "resolve-cwd/resolve-from": ["resolve-from@5.0.0", "", {}, "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw=="],
+
+    "rimraf/glob": ["glob@10.4.5", "", { "dependencies": { "foreground-child": "^3.1.0", "jackspeak": "^3.1.2", "minimatch": "^9.0.4", "minipass": "^7.1.2", "package-json-from-dist": "^1.0.0", "path-scurry": "^1.11.1" }, "bin": "dist/esm/bin.mjs" }, "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg=="],
+
+    "simple-swizzle/is-arrayish": ["is-arrayish@0.3.2", "", {}, "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="],
+
+    "snakecase-keys/type-fest": ["type-fest@4.38.0", "", {}, "sha512-2dBz5D5ycHIoliLYLi0Q2V7KRaDlH0uWIvmk7TYlAg5slqwiPv1ezJdZm1QEM0xgk29oYWMCbIG7E6gHpvChlg=="],
+
+    "stack-utils/escape-string-regexp": ["escape-string-regexp@2.0.0", "", {}, "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w=="],
+
+    "string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
 
     "string-width-cjs/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
 
-    "string-width-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+    "tinyglobby/picomatch": ["picomatch@4.0.2", "", {}, "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="],
+
+    "tsconfig-paths/json5": ["json5@1.0.2", "", { "dependencies": { "minimist": "^1.2.0" }, "bin": "lib/cli.js" }, "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA=="],
+
+    "wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
 
-    "strip-ansi-cjs/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+    "wrap-ansi-cjs/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
 
-    "wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
+    "@isaacs/cliui/strip-ansi/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="],
 
-    "wrap-ansi-cjs/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="],
+    "@isaacs/cliui/wrap-ansi/ansi-styles": ["ansi-styles@6.2.1", "", {}, "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="],
 
-    "wrap-ansi-cjs/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
+    "@istanbuljs/load-nyc-config/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
+
+    "@istanbuljs/load-nyc-config/js-yaml/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="],
+
+    "@jest/transform/write-file-atomic/signal-exit": ["signal-exit@3.0.7", "", {}, "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="],
 
     "@typescript-eslint/typescript-estree/fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
 
     "@typescript-eslint/typescript-estree/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
 
-    "glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
+    "babel-plugin-istanbul/istanbul-lib-instrument/semver": ["semver@6.3.1", "", { "bin": "bin/semver.js" }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="],
+
+    "filelist/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
+
+    "pkg-dir/find-up/locate-path": ["locate-path@5.0.0", "", { "dependencies": { "p-locate": "^4.1.0" } }, "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g=="],
+
+    "rimraf/glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
+
+    "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
+
+    "pkg-dir/find-up/locate-path/p-locate": ["p-locate@4.1.0", "", { "dependencies": { "p-limit": "^2.2.0" } }, "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A=="],
 
-    "string-width-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+    "rimraf/glob/minimatch/brace-expansion": ["brace-expansion@2.0.1", "", { "dependencies": { "balanced-match": "^1.0.0" } }, "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA=="],
 
-    "wrap-ansi-cjs/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="],
+    "@istanbuljs/load-nyc-config/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
 
-    "wrap-ansi-cjs/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
+    "pkg-dir/find-up/locate-path/p-locate/p-limit": ["p-limit@2.3.0", "", { "dependencies": { "p-try": "^2.0.0" } }, "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w=="],
   }
 }
diff --git a/prisma/migrations/20250430012212_add_tip_pool_group/migration.sql b/prisma/migrations/20250430012212_add_tip_pool_group/migration.sql
new file mode 100644
index 0000000..68cb196
--- /dev/null
+++ b/prisma/migrations/20250430012212_add_tip_pool_group/migration.sql
@@ -0,0 +1,2 @@
+-- AlterTable
+ALTER TABLE "RoleConfig" ADD COLUMN     "tipPoolGroup" TEXT;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index 2f799e3..42a6a02 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -15,42 +15,42 @@ datasource db {
 }
 
 model Employee {
-  id            String    @id @default(cuid())
+  id            String   @id @default(cuid())
   name          String
-  active        Boolean   @default(true)
+  active        Boolean  @default(true)
   defaultRoleId String?
-  defaultRole   Role?     @relation("DefaultRole", fields: [defaultRoleId], references: [id])
+  defaultRole   Role?    @relation("DefaultRole", fields: [defaultRoleId], references: [id])
   shifts        Shift[]
-  createdAt     DateTime  @default(now())
-  updatedAt     DateTime  @updatedAt
+  createdAt     DateTime @default(now())
+  updatedAt     DateTime @updatedAt
 }
 
 model Role {
-  id            String     @id @default(cuid())
-  name          String
-  basePayRate   Decimal    @default(0)
-  defaultFor    Employee[] @relation("DefaultRole")
-  shifts        Shift[]
-  createdAt     DateTime   @default(now())
-  updatedAt     DateTime   @updatedAt
-  configs       RoleConfig[]
+  id          String       @id @default(cuid())
+  name        String
+  basePayRate Decimal      @default(0)
+  defaultFor  Employee[]   @relation("DefaultRole")
+  shifts      Shift[]
+  createdAt   DateTime     @default(now())
+  updatedAt   DateTime     @updatedAt
+  configs     RoleConfig[]
 
   @@index([name])
 }
 
 model Shift {
-  id           String   @id @default(cuid())
-  employeeId   String
-  employee     Employee @relation(fields: [employeeId], references: [id])
-  roleId       String
-  role         Role     @relation(fields: [roleId], references: [id])
-  date         DateTime
-  hours        Decimal
-  cashTips     Decimal  @default(0)
-  creditTips   Decimal  @default(0)
-  liquorSales  Decimal  @default(0)
-  createdAt    DateTime @default(now())
-  updatedAt    DateTime @updatedAt
+  id          String   @id @default(cuid())
+  employeeId  String
+  employee    Employee @relation(fields: [employeeId], references: [id])
+  roleId      String
+  role        Role     @relation(fields: [roleId], references: [id])
+  date        DateTime
+  hours       Decimal
+  cashTips    Decimal  @default(0)
+  creditTips  Decimal  @default(0)
+  liquorSales Decimal  @default(0)
+  createdAt   DateTime @default(now())
+  updatedAt   DateTime @updatedAt
 
   @@index([date])
   @@index([employeeId])
@@ -58,19 +58,20 @@ model Shift {
 }
 
 model RoleConfig {
-  id             String    @id @default(uuid())
-  roleId         String
-  tipoutType     String
-  percentageRate Decimal   @default(0)
-  effectiveFrom  DateTime  @default(now())
-  effectiveTo    DateTime?
-  receivesTipout Boolean   @default(false)
-  paysTipout     Boolean   @default(true)  
+  id                String    @id @default(uuid())
+  roleId            String
+  tipoutType        String
+  percentageRate    Decimal   @default(0)
+  effectiveFrom     DateTime  @default(now())
+  effectiveTo       DateTime?
+  receivesTipout    Boolean   @default(false)
+  paysTipout        Boolean   @default(true)
   distributionGroup String?
-  createdAt      DateTime  @default(now())
-  updatedAt      DateTime  @updatedAt
-  role           Role      @relation(fields: [roleId], references: [id], onDelete: Cascade)
+  tipPoolGroup      String?
+  createdAt         DateTime  @default(now())
+  updatedAt         DateTime  @updatedAt
+  role              Role      @relation(fields: [roleId], references: [id], onDelete: Cascade)
 
-  @@index([roleId])
   @@unique([roleId, tipoutType, effectiveFrom])
+  @@index([roleId])
 }
diff --git a/src/app/(dashboard)/reports/page.tsx b/src/app/(dashboard)/reports/page.tsx
index 81df7b2..7b27297 100644
--- a/src/app/(dashboard)/reports/page.tsx
+++ b/src/app/(dashboard)/reports/page.tsx
@@ -74,6 +74,7 @@ function ReportsContent() {
     }
   });
   const [groupByEmployee, setGroupByEmployee] = useState(true)
+  const [showPoolSummaries, setShowPoolSummaries] = useState(false)
 
   // Update filters when search params change (e.g., back/forward navigation)
   useEffect(() => {
@@ -179,6 +180,7 @@ function ReportsContent() {
           totalHours: 0,
           totalCashTips: 0,
           totalCreditTips: 0,
+          totalGrossCreditTips: 0,
           totalBarTipout: 0,
           totalHostTipout: 0,
           totalSaTipout: 0,
@@ -194,15 +196,15 @@ function ReportsContent() {
           agg.totalHours += summary.totalHours
           agg.totalCashTips += summary.totalCashTips
           agg.totalCreditTips += summary.totalCreditTips
-          agg.totalBarTipout += summary.totalBarTipout // Summing net amounts
-          agg.totalHostTipout += summary.totalHostTipout // Summing net amounts
-          agg.totalSaTipout += summary.totalSaTipout   // Summing net amounts
+          agg.totalGrossCreditTips += summary.totalGrossCreditTips
+          agg.totalBarTipout += summary.totalBarTipout
+          agg.totalHostTipout += summary.totalHostTipout
+          agg.totalSaTipout += summary.totalSaTipout
           agg.totalLiquorSales += summary.totalLiquorSales
-          agg.basePayRate += summary.basePayRate * summary.totalHours // for weighted avg
+          agg.basePayRate += summary.basePayRate * summary.totalHours
           agg.totalPayrollTips = (agg.totalPayrollTips || 0) + (summary.totalPayrollTips || 0)
           agg.payrollTotal = (agg.payrollTotal || 0) + (summary.payrollTotal || 0)
         }
-        // Re-calculate per-hour rates based on aggregated values
         agg.cashTipsPerHour = agg.totalHours > 0 ? agg.totalCashTips / agg.totalHours : 0;
         agg.creditTipsPerHour = agg.totalHours > 0 ? (agg.totalPayrollTips ?? 0) / agg.totalHours : 0;
         agg.totalTipsPerHour = agg.totalHours > 0
@@ -833,6 +835,19 @@ function ReportsContent() {
   // Sorting logic remains the same
   displayedEmployeeSummaries = displayedEmployeeSummaries.slice().sort((a, b) => a.employeeName.localeCompare(b.employeeName))
 
+  // Option B: compute per-pool aggregates
+  const poolSummaries: [string, { totalHours: number; totalGrossCreditTips: number; totalNetCreditShare: number }][] = Object.entries(
+    displayedEmployeeSummaries.reduce((acc, s) => {
+      if (s.tipPoolGroup) {
+        if (!acc[s.tipPoolGroup]) acc[s.tipPoolGroup] = { totalHours: 0, totalGrossCreditTips: 0, totalNetCreditShare: 0 };
+        acc[s.tipPoolGroup].totalHours += s.totalHours;
+        acc[s.tipPoolGroup].totalGrossCreditTips += s.totalGrossCreditTips;
+        acc[s.tipPoolGroup].totalNetCreditShare += s.totalCreditTips;
+      }
+      return acc;
+    }, {} as Record<string, { totalHours: number; totalGrossCreditTips: number; totalNetCreditShare: number }>)
+  );
+
   return (
     <div className="px-4 sm:px-6 lg:px-8 max-w-7xl mx-auto">
       {/* ... Page Title ... */}
@@ -1083,6 +1098,36 @@ function ReportsContent() {
                 {groupByEmployee ? 'All roles and payroll are summed for each employee.' : 'Each row is a unique employee/role combination.'}
               </span>
             </div>
+            {/* Option B: Tip Pool Summaries */}
+            <div className="mb-6">
+              <div className="flex justify-between items-center">
+                <h3 className="text-lg font-medium text-[var(--foreground)]">Tip Pool Summaries</h3>
+                <button
+                  type="button"
+                  onClick={() => setShowPoolSummaries(v => !v)}
+                  className="text-sm text-indigo-600 hover:underline"
+                >
+                  {showPoolSummaries ? 'Hide' : 'Show'}
+                </button>
+              </div>
+              {showPoolSummaries && poolSummaries.length > 0 && (
+                <div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-4">
+                  {poolSummaries.map(([group, { totalHours, totalGrossCreditTips, totalNetCreditShare }]) => (
+                    <div key={group} className="p-4 bg-white/50 dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-gray-700">
+                      <div className="flex justify-between font-medium">
+                        <span>{group}</span><span>{totalHours.toFixed(2)} hrs</span>
+                      </div>
+                      <div className="mt-2 flex justify-between text-sm">
+                        <span>Gross Credit Tips</span><span>${totalGrossCreditTips.toFixed(2)}</span>
+                      </div>
+                      <div className="mt-1 flex justify-between text-sm">
+                        <span>Net Credit Share</span><span>${totalNetCreditShare.toFixed(2)}</span>
+                      </div>
+                    </div>
+                  ))}
+                </div>
+              )}
+            </div>
             {/* Mobile card view */}
             <div className="block md:hidden space-y-4">
               {displayedEmployeeSummaries.map((summary) => (
@@ -1104,7 +1149,7 @@ function ReportsContent() {
                     <div className="flex-grow">
                       <h3 className="text-base font-medium text-[var(--foreground)]">{summary.employeeName}</h3>
                       <p className="text-sm text-gray-500 dark:text-gray-400">{groupByEmployee ? 'All Roles' : summary.roleName}</p>
-                    </div>
+                      </div>
                     <div className="text-right">
                       <p className="text-sm font-medium text-green-600 dark:text-green-400">
                         ${(summary.totalTipsPerHour + summary.basePayRate).toFixed(2)}/hr
@@ -1112,16 +1157,22 @@ function ReportsContent() {
                       <p className="text-xs text-gray-500 dark:text-gray-400">
                         {summary.totalHours.toFixed(2)} hours
                       </p>
-                    </div>
+                      </div>
                   </div>
                   {/* Tips Section */}
-                  <div className="grid grid-cols-3 gap-4 mb-4">
+                  <div className="grid grid-cols-4 gap-4 mb-4">
                     <div>
                       <p className="text-xs text-gray-500 dark:text-gray-400">cash tips</p>
                       <p className="text-sm font-medium text-[var(--foreground)]">
                         ${summary.totalCashTips.toFixed(2)}
                       </p>
                     </div>
+                    <div>
+                      <p className="text-xs text-gray-500 dark:text-gray-400">gross credit tips</p>
+                      <p className="text-sm font-medium text-[var(--foreground)]">
+                        ${summary.totalGrossCreditTips.toFixed(2)}
+                      </p>
+                    </div>
                     <div>
                       <p className="text-xs text-gray-500 dark:text-gray-400">credit tips</p>
                       <p className="text-sm font-medium text-[var(--foreground)]">
@@ -1205,10 +1256,10 @@ function ReportsContent() {
                         </p>
                       </div>
                     </div>
-                  </div>
+                      </div>
+                    </div>
+                  ))}
                 </div>
-              ))}
-            </div>
             {/* Desktop table view */}
             <div className="hidden md:block overflow-hidden bg-white/50 dark:bg-gray-800/50 shadow sm:rounded-lg border border-gray-200 dark:border-gray-700">
               <div className="overflow-x-auto">
@@ -1219,7 +1270,6 @@ function ReportsContent() {
                         <th scope="col" className="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 dark:text-white sm:pl-6">
                           employee
                         </th>
-                        {/* Only show role column if not grouping by employee */}
                         {!groupByEmployee && (
                           <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white">
                             role
@@ -1231,28 +1281,37 @@ function ReportsContent() {
                         <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white">
                           cash tips
                         </th>
+                        <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white">
+                          gross credit tips
+                        </th>
                         <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white">
                           credit tips
                         </th>
                         <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white">
                           liquor sales
                         </th>
-                        <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white">
+                        <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white"
+                          title="Net Bar Tipout: Positive if received, negative if paid. $0 for pooled roles as tipouts are handled pre-distribution."
+                        >
                           bar tipout
                         </th>
-                        <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white">
+                        <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white"
+                          title="Net Host Tipout: Positive if received, negative if paid. $0 for pooled roles as tipouts are handled pre-distribution."
+                        >
                           host tipout
                         </th>
-                        <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white">
+                        <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white"
+                          title="Net SA Tipout: Positive if received, negative if paid. $0 for pooled roles as tipouts are handled pre-distribution."
+                        >
                           sa tipout
                         </th>
                         <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white"
-                          title="Total credit tips received by this employee/role after any applicable tipouts paid into pools (Host, SA, Bar). Note: This might differ from raw Credit Tips collected."
+                          title="Total tips allocated for payroll: (Adjusted/Pooled Credit Tips) + (Net Received/Paid Distribution Tipouts)."
                         >
                           payroll tips
                         </th>
                         <th scope="col" className="px-3 py-3.5 text-left text-sm font-semibold text-gray-900 dark:text-white"
-                          title="For pooled roles (Bartender, Host, SA), this reflects the average earnings per hour from the pool(s). For non-pooled roles (Server), reflects individual cash + calculated payroll tips per hour."
+                          title="Total net tips per hour: (Adjusted/Pooled Cash Tips + Payroll Tips) / Hours. Should be equal for roles in the same pool."
                         >
                           total tips/hour
                         </th>
@@ -1287,7 +1346,6 @@ function ReportsContent() {
                           <td className="whitespace-nowrap py-4 pl-4 pr-3 text-sm font-medium text-[var(--foreground)] sm:pl-6">
                             {summary.employeeName}
                           </td>
-                          {/* Only show role cell if not grouping by employee */}
                           {!groupByEmployee && (
                             <td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
                               {summary.roleName}
@@ -1299,6 +1357,9 @@ function ReportsContent() {
                           <td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
                             ${summary.totalCashTips.toFixed(2)}
                           </td>
+                          <td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
+                            ${summary.totalGrossCreditTips.toFixed(2)}
+                          </td>
                           <td className="whitespace-nowrap px-3 py-4 text-sm text-gray-500 dark:text-gray-400">
                             ${summary.totalCreditTips.toFixed(2)}
                           </td>
@@ -1359,7 +1420,7 @@ function ReportsContent() {
                       ))}
                     </tbody>
                   </table>
-                </div>
+            </div>
               </div>
             </div>
           </div>
diff --git a/src/app/(dashboard)/roles/[id]/edit/page.tsx b/src/app/(dashboard)/roles/[id]/edit/page.tsx
index 134745f..791c676 100644
--- a/src/app/(dashboard)/roles/[id]/edit/page.tsx
+++ b/src/app/(dashboard)/roles/[id]/edit/page.tsx
@@ -13,6 +13,7 @@ type RoleConfig = {
   receivesTipout: boolean
   paysTipout: boolean
   distributionGroup: string | null
+  tipPoolGroup?: string | null
 }
 
 export default function EditRolePage() {
@@ -26,6 +27,7 @@ export default function EditRolePage() {
   const [configs, setConfigs] = useState<RoleConfig[]>([])
   const [loading, setLoading] = useState(true)
   const [error, setError] = useState<string | null>(null)
+  const [existingPoolGroups, setExistingPoolGroups] = useState<string[]>([])
   
   const tipoutTypes = ['bar', 'host', 'sa']
   const distributionGroups = ['bartenders', 'hosts', 'servers', 'support']
@@ -65,7 +67,8 @@ export default function EditRolePage() {
         ...config,
         percentageRate: Number(config.percentageRate),
         effectiveFrom: new Date(config.effectiveFrom).toISOString().split('T')[0],
-        effectiveTo: config.effectiveTo ? new Date(config.effectiveTo).toISOString().split('T')[0] : null
+        effectiveTo: config.effectiveTo ? new Date(config.effectiveTo).toISOString().split('T')[0] : null,
+        tipPoolGroup: config.tipPoolGroup || null
       })))
     } catch (err) {
       console.error('Error fetching configs:', err)
@@ -75,6 +78,20 @@ export default function EditRolePage() {
     }
   }, [params.id])
 
+  const fetchExistingPoolGroups = useCallback(async () => {
+    try {
+      const response = await fetch('/api/tip-pool-groups');
+      if (!response.ok) {
+        throw new Error('Failed to fetch existing pool groups');
+      }
+      const data = await response.json();
+      setExistingPoolGroups(data);
+    } catch (err) {
+      console.error('Error fetching pool groups:', err);
+      // Non-critical error, maybe just log it
+    }
+  }, [])
+
   useEffect(() => {
     if (params.id === 'new') {
       // Creating a new role
@@ -85,7 +102,8 @@ export default function EditRolePage() {
     // Fetch existing role
     fetchRole()
     fetchConfigs()
-  }, [params.id, fetchRole, fetchConfigs])
+    fetchExistingPoolGroups()
+  }, [params.id, fetchRole, fetchConfigs, fetchExistingPoolGroups])
 
   const handleSubmit = async (e: React.FormEvent) => {
     e.preventDefault()
@@ -427,6 +445,55 @@ export default function EditRolePage() {
                 </div>
               </div>
 
+              {/* Tip Pool Group Configuration */}
+              <div className="mt-6 p-4 bg-white/50 dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-gray-700">
+                <h4 className="text-md font-medium text-gray-900 dark:text-white">Tip Pooling</h4>
+                <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
+                  Optionally assign this role to a tip pool group (e.g., "servers", "bartenders"). Roles within the same group on the same day will have their tips pooled and distributed based on hours worked.
+                  Leave blank if this role's tips should not be pooled.
+                </p>
+
+                <div className="mt-4">
+                  <label htmlFor="tipPoolGroupInput" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
+                    Tip Pool Group Name
+                  </label>
+                  <div className="mt-1">
+                    <input
+                      type="text"
+                      id="tipPoolGroupInput"
+                      list="pool-groups-list"
+                      placeholder="Select existing or type new..." 
+                      value={configs.find(c => c.tipPoolGroup)?.tipPoolGroup || ''}
+                      onChange={(e) => {
+                        const poolName = e.target.value.trim() ? e.target.value.trim() : null;
+                        const updatedConfigs = configs.map(c => ({ ...c, tipPoolGroup: poolName }));
+                        if (updatedConfigs.length === 0 && poolName !== null) {
+                           setConfigs([{ 
+                             tipoutType: '', percentageRate: 0,
+                             effectiveFrom: new Date().toISOString().split('T')[0], effectiveTo: null,
+                             receivesTipout: false, paysTipout: false, distributionGroup: null,
+                             tipPoolGroup: poolName
+                           }]);
+                         } else if (configs.length > 0) {
+                           setConfigs(updatedConfigs);
+                         } else {
+                             setConfigs([]);
+                         }
+                      }}
+                      className="block w-full sm:w-1/2 rounded-md border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm dark:bg-gray-800 dark:border-gray-700 dark:text-white px-3 py-2"
+                    />
+                    <datalist id="pool-groups-list">
+                      {existingPoolGroups.map(group => (
+                        <option key={group} value={group} />
+                      ))}
+                    </datalist>
+                  </div>
+                   <p className="mt-2 text-xs text-gray-500 dark:text-gray-400">
+                     Start typing to see existing groups or enter a new name. Leave blank for no pooling.
+                   </p>
+                </div>
+              </div>
+
               <div className="mt-6 rounded-md bg-yellow-50 dark:bg-yellow-900/50 border border-yellow-200 dark:border-yellow-800 p-4">
                 <div className="flex">
                   <div className="flex-shrink-0">
diff --git a/src/app/api/reports/route.ts b/src/app/api/reports/route.ts
index 3c57dcd..3a0b96e 100644
--- a/src/app/api/reports/route.ts
+++ b/src/app/api/reports/route.ts
@@ -48,14 +48,9 @@ async function fetchShiftsFromDB(startDate: string, endDate: string): Promise<Re
                 select: { id: true, name: true },
             },
             role: {
-                select: {
-                    id: true,
-                    name: true,
-                    basePayRate: true,
-                    createdAt: true,
-                    updatedAt: true,
-                    configs: true,
-                },
+                include: { 
+                    configs: true // This includes all fields from RoleConfig, including tipPoolGroup
+                }, 
             },
         },
         orderBy: {
@@ -63,8 +58,9 @@ async function fetchShiftsFromDB(startDate: string, endDate: string): Promise<Re
         },
     });
 
-    // Map Prisma result to the ReportShift type
+    // Map Prisma result to the ReportShift type, including tipPoolGroup
     const reportShifts: ReportShift[] = shifts.filter(
+        // Ensure employee and role are non-null
         (shift): shift is ShiftWithIncludes & { employee: NonNullable<ShiftWithIncludes['employee']>, role: NonNullable<ShiftWithIncludes['role']> } =>
             !!shift.employee && !!shift.role
     ).map((shift) => ({
@@ -79,21 +75,25 @@ async function fetchShiftsFromDB(startDate: string, endDate: string): Promise<Re
             name: shift.employee.name,
         },
         role: {
+            id: shift.role.id,
             name: shift.role.name,
             basePayRate: Number(shift.role.basePayRate),
+            // Map configs, ensuring the target type ReportShift['role']['configs'] is compatible
             configs: (shift.role.configs || []).map((config) => ({
                 id: config.id,
-                tipoutType: config.tipoutType,
+                tipoutType: config.tipoutType, // Assuming ReportShift uses string here
                 percentageRate: Number(config.percentageRate),
                 effectiveFrom: config.effectiveFrom.toISOString(),
                 effectiveTo: config.effectiveTo ? config.effectiveTo.toISOString() : null,
-                receivesTipout: config.receivesTipout ?? true,
+                receivesTipout: config.receivesTipout ?? false,
                 paysTipout: config.paysTipout ?? true,
                 distributionGroup: config.distributionGroup ?? undefined,
+                tipPoolGroup: config.tipPoolGroup ?? undefined // Map the new field
             })),
         },
     }));
 
+    console.log(`Fetched ${reportShifts.length} shifts.`);
     return reportShifts;
 }
 
@@ -111,26 +111,29 @@ export async function GET(request: NextRequest) {
         const allShiftsData = await fetchShiftsFromDB(startDate, endDate);
 
         if (!allShiftsData || allShiftsData.length === 0) {
-            return NextResponse.json({ summary: null, employeeSummaries: [] });
+            return NextResponse.json({ summary: null, employeeSummaries: [], roleConfigs: {} });
         }
 
         // Get unique roles and their current configs
-        const roleConfigMap = new Map();
+        const roleConfigMap = new Map<string, Record<string, number>>();
         allShiftsData.forEach(shift => {
             if (!roleConfigMap.has(shift.role.name)) {
-                const roleConfigs = {
-                    bar: shift.role.configs.find(c => c.tipoutType === 'bar')?.percentageRate || 0,
-                    host: shift.role.configs.find(c => c.tipoutType === 'host')?.percentageRate || 0,
+                // Storing percentage rates directly for frontend display
+                const roleConfigRates = {
+                    barTipout: shift.role.configs.find(c => c.tipoutType === 'bar')?.percentageRate || 0,
+                    hostTipout: shift.role.configs.find(c => c.tipoutType === 'host')?.percentageRate || 0,
                     sa: shift.role.configs.find(c => c.tipoutType === 'sa')?.percentageRate || 0,
+                    // We might want other config details here later if needed
                 };
-                roleConfigMap.set(shift.role.name, roleConfigs);
+                roleConfigMap.set(shift.role.name, roleConfigRates);
             }
         });
 
         // Convert the map to an object for the response
-        const roleConfigs = Object.fromEntries(roleConfigMap);
+        const roleConfigsForResponse = Object.fromEntries(roleConfigMap);
 
-        // Calculate summaries
+        // Calculate summaries using the utility function
+        // Pass the fetched shifts directly
         const summary = calculateOverallSummary(allShiftsData);
         const employeeSummaries = calculateEmployeeRoleSummariesDaily(allShiftsData);
 
@@ -138,11 +141,19 @@ export async function GET(request: NextRequest) {
         return NextResponse.json({ 
             summary, 
             employeeSummaries,
-            roleConfigs 
+            roleConfigs: roleConfigsForResponse // Use the prepared object
         });
 
     } catch (error) {
         console.error("Error generating report:", error);
-        return NextResponse.json({ message: 'Error generating report data' }, { status: 500 });
+        // Consider logging the specific error or providing more context if safe
+        const errorMessage = 'Error generating report data'; // Use const
+        if (error instanceof Error) {
+            // Avoid exposing sensitive details, but log them
+            console.error("Detailed error:", error.message, error.stack);
+            // Optionally, provide a generic error or a more specific one if applicable
+            // errorMessage = `Failed to process report: ${error.message}`; // Use cautiously
+        }
+        return NextResponse.json({ message: errorMessage }, { status: 500 });
     }
 } 
\ No newline at end of file
diff --git a/src/app/api/roles/[id]/config/route.ts b/src/app/api/roles/[id]/config/route.ts
index c6123e0..6748807 100644
--- a/src/app/api/roles/[id]/config/route.ts
+++ b/src/app/api/roles/[id]/config/route.ts
@@ -1,5 +1,6 @@
 import { NextResponse } from 'next/server'
 import { prisma } from '@/lib/prisma'
+import { RoleConfig as FrontendRoleConfig } from '@/types/reports'
 
 export async function GET(
   request: Request,
@@ -38,7 +39,7 @@ export async function PUT(
     
     // Create new configs
     const configs = await Promise.all(
-      data.map((config: any) => 
+      data.map((config: FrontendRoleConfig) => 
         prisma.roleConfig.create({
           data: {
             roleId: id,
@@ -48,7 +49,8 @@ export async function PUT(
             effectiveTo: config.effectiveTo ? new Date(config.effectiveTo) : null,
             receivesTipout: config.receivesTipout,
             paysTipout: config.paysTipout,
-            distributionGroup: config.distributionGroup
+            distributionGroup: config.distributionGroup,
+            tipPoolGroup: config.tipPoolGroup
           }
         })
       )
diff --git a/src/app/api/tip-pool-groups/route.ts b/src/app/api/tip-pool-groups/route.ts
new file mode 100644
index 0000000..f1bc30f
--- /dev/null
+++ b/src/app/api/tip-pool-groups/route.ts
@@ -0,0 +1,29 @@
+import { NextResponse } from 'next/server';
+import { prisma as db } from '@/lib/prisma';
+
+export async function GET() {
+  try {
+    const configsWithGroups = await db.roleConfig.findMany({
+      where: {
+        tipPoolGroup: {
+          not: null, // Exclude null values
+          notIn: [''], // Exclude empty strings if necessary
+        },
+      },
+      select: {
+        tipPoolGroup: true,
+      },
+      distinct: ['tipPoolGroup'], // Get distinct values
+    });
+
+    // Extract the names and filter out any potential nulls again just in case
+    const groupNames = configsWithGroups
+      .map(config => config.tipPoolGroup)
+      .filter((group): group is string => group !== null && group !== ''); 
+
+    return NextResponse.json(groupNames);
+  } catch (error) {
+    console.error("Error fetching tip pool groups:", error);
+    return NextResponse.json({ message: 'Error fetching tip pool groups' }, { status: 500 });
+  }
+} 
\ No newline at end of file
diff --git a/src/types/reports.ts b/src/types/reports.ts
index 835650b..bd7ef63 100644
--- a/src/types/reports.ts
+++ b/src/types/reports.ts
@@ -17,7 +17,8 @@ export type RoleConfig = {
   effectiveTo: string | null;
   receivesTipout?: boolean;  // Whether this role receives tipout of this type
   paysTipout?: boolean;      // Whether this role pays tipout of this type
-  distributionGroup?: string; // For pooling tipouts (e.g., 'bartenders', 'hosts')
+  distributionGroup?: string; // For pooling tipouts RECEIVED FROM others (e.g., 'bartenders', 'hosts')
+  tipPoolGroup?: string;     // For pooling tips COLLECTED BY this role WITH others (e.g. "server_pool", "bartender_pool")
 };
 
 /**
@@ -36,6 +37,7 @@ export type Shift = {
   cashTips: number;
   creditTips: number;
   liquorSales: number;
+  configs?: RoleConfig[]; // Add optional configs property
 };
 
 /**
@@ -69,7 +71,12 @@ export type EmployeeRoleSummary = {
   totalHours: number;
   totalCashTips: number;
   totalCreditTips: number;
-  totalBarTipout: number;   // Net amount for this employee/role: +received / -paid
+  totalGrossCreditTips: number; // Gross credit tips before pooling
+  /**
+   * Net amount for this employee/role: Positive=Received, Negative=Paid.
+   * For pooled roles, this is now (received from pool - paid to pool), so can be negative or positive.
+   */
+  totalBarTipout: number;
   totalHostTipout: number;  // Net amount for this employee/role: +received / -paid
   totalSaTipout: number;    // Net amount for this employee/role: +received / -paid
   cashTipsPerHour: number;
@@ -79,6 +86,7 @@ export type EmployeeRoleSummary = {
   totalPayrollTips: number;  // Represents the value used for payroll (CreditTips + Net Tipouts)
   totalLiquorSales: number;
   payrollTotal: number;      // Calculated total payroll amount (Base Pay + Payroll Tips)
+  tipPoolGroup?: string | null; // Name of the tip pool group, if any
 };
 
 /**
diff --git a/src/utils/reportCalculations.test.ts b/src/utils/reportCalculations.test.ts
new file mode 100644
index 0000000..704b4aa
--- /dev/null
+++ b/src/utils/reportCalculations.test.ts
@@ -0,0 +1,290 @@
+import { calculateEmployeeRoleSummariesDaily, calculateOverallSummary } from './reportCalculations';
+import { Shift, EmployeeRoleSummary, RoleConfig, ReportSummary, TipoutType } from '@/types/reports'; // Assuming types are exported from here
+
+// --- Mock Data Setup ---
+
+const mockEmployee = (id: string, name: string): { id: string; name: string } => ({ id, name });
+
+const mockRoleConfig = (
+    id: string,
+    tipoutType: string,
+    percentageRate: number,
+    { receivesTipout = false, paysTipout = true, distributionGroup = undefined, tipPoolGroup = undefined, effectiveFrom = '2024-01-01', effectiveTo = null }: Partial<RoleConfig> & { tipPoolGroup?: string | null } = {}
+): RoleConfig => ({
+    id,
+    tipoutType: tipoutType as TipoutType,
+    percentageRate,
+    effectiveFrom,
+    effectiveTo,
+    receivesTipout,
+    paysTipout,
+    distributionGroup: distributionGroup ?? undefined, // Handle null vs undefined
+    tipPoolGroup: tipPoolGroup ?? undefined,      // Handle null vs undefined
+});
+
+const mockRole = (
+    id: string,
+    name: string,
+    basePayRate: number,
+    configs: RoleConfig[],
+): Shift['role'] => ({
+    name,
+    basePayRate,
+    configs,
+});
+
+const mockShift = (
+    id: string,
+    employee: { id: string; name: string },
+    role: Shift['role'],
+    date: string, // YYYY-MM-DD
+    hours: number,
+    cashTips: number,
+    creditTips: number,
+    liquorSales: number,
+    configs: RoleConfig[] = []
+): Shift => ({
+    id,
+    employee,
+    role,
+    date,
+    hours,
+    cashTips,
+    creditTips,
+    liquorSales,
+    configs,
+});
+
+// --- Common Roles ---
+const serverConfigs = [
+    mockRoleConfig('cfgSrvBar', 'bar', 25, { paysTipout: true, tipPoolGroup: 'server_pool' }),
+    mockRoleConfig('cfgSrvHost', 'host', 7, { paysTipout: true, tipPoolGroup: 'server_pool' }),
+    mockRoleConfig('cfgSrvSa', 'sa', 4, { paysTipout: true, tipPoolGroup: 'server_pool' }),
+];
+const roleServer = mockRole('roleSrv', 'Server', 3, serverConfigs);
+
+const barConfigs = [
+    mockRoleConfig('cfgBarBar', 'bar', 0, { receivesTipout: true, paysTipout: false, distributionGroup: 'bartenders', tipPoolGroup: 'bar_pool' }),
+    mockRoleConfig('cfgBarHost', 'host', 7, { paysTipout: true, tipPoolGroup: 'bar_pool' }),
+    mockRoleConfig('cfgBarSa', 'sa', 4, { paysTipout: true, tipPoolGroup: 'bar_pool' }),
+];
+const roleBar = mockRole('roleBar', 'Bar', 9, barConfigs);
+
+const hostConfigs = [
+     mockRoleConfig('cfgHostHost', 'host', 0, { receivesTipout: true, paysTipout: false, distributionGroup: 'hosts' }),
+];
+const roleHost = mockRole('roleHost', 'Host', 10, hostConfigs);
+
+const saConfigs = [
+     mockRoleConfig('cfgSaSa', 'sa', 0, { receivesTipout: true, paysTipout: false, distributionGroup: 'support' }),
+];
+const roleSA = mockRole('roleSA', 'SA', 8, saConfigs);
+
+
+// --- Employees ---
+const empDylan = mockEmployee('emp1', 'Dylan');
+const empRegan = mockEmployee('emp2', 'Regan');
+const empBrigid = mockEmployee('emp3', 'Brigid');
+const empChristina = mockEmployee('emp4', 'Christina');
+const empAlex = mockEmployee('emp5', 'Alex'); // SA
+
+
+// --- Tests ---
+
+describe('reportCalculations', () => {
+
+    describe('calculateOverallSummary', () => {
+        it('should calculate basic totals correctly', () => {
+             const shifts: Shift[] = [
+                mockShift('s1', empDylan, roleServer, '2024-03-15', 8, 50, 150, 400),
+                mockShift('s2', empBrigid, roleBar, '2024-03-15', 7, 20, 80, 600),
+            ];
+            const summary = calculateOverallSummary(shifts);
+            expect(summary.totalShifts).toBe(2);
+            expect(summary.totalHours).toBeCloseTo(15);
+            expect(summary.totalCashTips).toBeCloseTo(70);
+            expect(summary.totalCreditTips).toBeCloseTo(230);
+            expect(summary.totalLiquorSales).toBeCloseTo(1000);
+        });
+
+        it('should calculate total tipouts paid correctly (ignoring SA)', () => {
+             // Server pays 25% Liq to Bar, 7% Tips to Host
+             // Bar pays 7% Tips to Host
+             const shifts: Shift[] = [
+                mockShift('s1', empDylan, roleServer, '2024-03-15', 8, 50, 150, 400), // Tips=200, Liq=400 -> Pays Bar=100, Host=14
+                mockShift('s2', empBrigid, roleBar, '2024-03-15', 7, 20, 80, 600),    // Tips=100, Liq=600 -> Pays Host=7
+            ];
+            const summary = calculateOverallSummary(shifts);
+            // Total Paid *Into* Pools
+            expect(summary.totalBarTipoutPaid).toBeCloseTo(100); // Only server pays bar
+            expect(summary.totalHostTipoutPaid).toBeCloseTo(14 + 7); // Server + Bar pay host
+            expect(summary.totalSaTipoutPaid).toBeCloseTo(0); // No SA shifts present
+        });
+        
+        it('should calculate total tipouts paid correctly (with SA)', () => {
+             // Server pays 25% Liq to Bar, 7% Tips to Host, 4% Tips to SA
+             // Bar pays 7% Tips to Host, 4% Tips to SA
+             const shifts: Shift[] = [
+                mockShift('s1', empDylan, roleServer, '2024-03-15', 8, 50, 150, 400), // Tips=200, Liq=400 -> Pays Bar=100, Host=14, SA=8
+                mockShift('s2', empBrigid, roleBar, '2024-03-15', 7, 20, 80, 600),    // Tips=100, Liq=600 -> Pays Host=7, SA=4
+                mockShift('s3', empAlex, roleSA, '2024-03-15', 6, 0, 0, 0),        // SA present
+            ];
+            const summary = calculateOverallSummary(shifts);
+            // Total Paid *Into* Pools
+            expect(summary.totalBarTipoutPaid).toBeCloseTo(100); // Only server pays bar
+            expect(summary.totalHostTipoutPaid).toBeCloseTo(14 + 7); // Server + Bar pay host
+            expect(summary.totalSaTipoutPaid).toBeCloseTo(8 + 4); // Server + Bar pay SA
+        });
+        
+        // Add more tests for average calculations if they become critical, 
+        // but note they are less meaningful with daily pooling.
+    });
+
+    describe('calculateEmployeeRoleSummariesDaily', () => {
+        
+        it('Scenario: Simple Non-Pooled (Server pays, Host receives)', () => {
+            const simpleServerConfig = [ mockRoleConfig('cfgSimpleBar', 'bar', 10, { paysTipout: true }) ]; // Pays 10% Liq to Bar
+            const simpleHostConfig = [ mockRoleConfig('cfgSimpleHost', 'bar', 0, { receivesTipout: true, paysTipout: false, distributionGroup: 'hosts'}) ]; // Receives Bar Tipout
+            const roleSimpleServer = mockRole('roleSimpleSrv', 'Server', 3, simpleServerConfig);
+            const roleSimpleReceiver = mockRole('roleSimpleRec', 'Host', 10, simpleHostConfig);
+            
+            const shifts: Shift[] = [
+                mockShift('s1', empDylan, roleSimpleServer, '2024-03-15', 8, 50, 150, 400), // Pays Bar = 40
+                mockShift('s2', empChristina, roleSimpleReceiver, '2024-03-15', 8, 0, 0, 0), // Receives Bar = 40
+            ];
+            
+            const summaries = calculateEmployeeRoleSummariesDaily(shifts);
+            
+            const dylanSummary = summaries.find(s => s.employeeName === 'Dylan');
+            const christinaSummary = summaries.find(s => s.employeeName === 'Christina');
+
+            expect(dylanSummary).toBeDefined();
+            expect(christinaSummary).toBeDefined();
+
+            // Dylan (Server) - Pays 40 Bar Tipout
+            expect(dylanSummary!.totalGrossCreditTips).toBeCloseTo(150);
+            expect(dylanSummary!.totalCreditTips).toBeCloseTo(150); // Original credit shown for non-pooled
+            expect(dylanSummary!.totalBarTipout).toBeCloseTo(-40); // Net paid out
+            expect(dylanSummary!.totalHostTipout).toBeCloseTo(0);
+            expect(dylanSummary!.totalPayrollTips).toBeCloseTo(150 - 40); // 110
+            expect(dylanSummary!.payrollTotal).toBeCloseTo((3 * 8) + 110); // 24 + 110 = 134
+
+            // Christina (Host) - Receives 40 Bar Tipout
+            expect(christinaSummary!.totalGrossCreditTips).toBeCloseTo(0);
+            expect(christinaSummary!.totalCreditTips).toBeCloseTo(0); // Original credit shown
+            expect(christinaSummary!.totalBarTipout).toBeCloseTo(40); // Net received
+            expect(christinaSummary!.totalHostTipout).toBeCloseTo(0);
+            expect(christinaSummary!.totalPayrollTips).toBeCloseTo(0 + 40); // 40
+            expect(christinaSummary!.payrollTotal).toBeCloseTo((10 * 8) + 40); // 80 + 40 = 120
+        });
+
+        it('Scenario: Server Pool with Bar Tipout', () => {
+            // Server Pool: Dylan (8h) + Regan (7h) = 15h total
+            // Server Pool Tips: Cash=100, Credit=300
+            // Server Pool Liq Sales: Dylan=400, Regan=300 = 700 total
+            // Bar Tipout: 10% of Liq Sales (paid after pooling)
+            // Host Tipout: 7% of Tips (paid before pooling)
+            // SA Tipout: 4% of Tips (paid before pooling)
+            const serverPoolConfig = [
+                mockRoleConfig('cfgBar', 'bar', 10, { paysTipout: true }), // 10% of Liq to Bar
+                mockRoleConfig('cfgHost', 'host', 7, { paysTipout: true }), // 7% of Tips to Host
+                mockRoleConfig('cfgSA', 'sa', 4, { paysTipout: true }), // 4% of Tips to SA
+            ];
+            const shifts: Shift[] = [
+                mockShift('s1', empDylan, roleServer, '2024-03-15', 8, 50, 150, 400, serverPoolConfig),
+                mockShift('s2', empRegan, roleServer, '2024-03-15', 7, 50, 150, 300, serverPoolConfig),
+                mockShift('s3', empBrigid, roleBar, '2024-03-15', 6, 0, 0, 0), // Bar present
+                mockShift('s4', empChristina, roleHost, '2024-03-15', 5, 0, 0, 0), // Host present
+                mockShift('s5', empAlex, roleSA, '2024-03-15', 4, 0, 0, 0), // SA present
+            ];
+
+            const summaries = calculateEmployeeRoleSummariesDaily(shifts);
+            const dylanSummary = summaries.find(s => s.employeeId === empDylan.id);
+            const reganSummary = summaries.find(s => s.employeeId === empRegan.id);
+            const brigidSummary = summaries.find(s => s.employeeId === empBrigid.id);
+            const christinaSummary = summaries.find(s => s.employeeId === empChristina.id);
+            const alexSummary = summaries.find(s => s.employeeId === empAlex.id);
+
+            // Dylan (Server Pool)
+            expect(dylanSummary!.tipPoolGroup).toBe('server_pool');
+            expect(dylanSummary!.totalCashTips).toBeCloseTo(8 * 6.6667); // 53.33
+            expect(dylanSummary!.totalCreditTips).toBeCloseTo(8 * 8.1333); // 65.07
+            expect(dylanSummary!.totalGrossCreditTips).toBeCloseTo(150);
+            expect(dylanSummary!.totalBarTipout).toBeCloseTo(-40); // Pays 10% of 400 liquor sales
+            expect(dylanSummary!.totalHostTipout).toBeCloseTo(0); // Pooled display
+            expect(dylanSummary!.totalSaTipout).toBeCloseTo(0); // Pooled display
+            expect(dylanSummary!.totalPayrollTips).toBeCloseTo(8 * 8.1333 - 40); // 65.07 - 40 = 25.07
+            expect(dylanSummary!.payrollTotal).toBeCloseTo((3 * 8) + 25.07); // 24 + 25.07 = 49.07
+
+            // Regan (Server Pool)
+            expect(reganSummary!.tipPoolGroup).toBe('server_pool');
+            expect(reganSummary!.totalCashTips).toBeCloseTo(7 * 6.6667); // 46.67
+            expect(reganSummary!.totalCreditTips).toBeCloseTo(7 * 8.1333); // 56.93
+            expect(reganSummary!.totalGrossCreditTips).toBeCloseTo(150);
+            expect(reganSummary!.totalBarTipout).toBeCloseTo(-30); // Pays 10% of 300 liquor sales
+            expect(reganSummary!.totalHostTipout).toBeCloseTo(0);
+            expect(reganSummary!.totalSaTipout).toBeCloseTo(0);
+            expect(reganSummary!.totalPayrollTips).toBeCloseTo(7 * 8.1333 - 30); // 56.93 - 30 = 26.93
+            expect(reganSummary!.payrollTotal).toBeCloseTo((3 * 7) + 26.93); // 21 + 26.93 = 47.93
+
+            // Brigid (Bar)
+            expect(brigidSummary!.tipPoolGroup).toBeUndefined(); // Not pooled
+            expect(brigidSummary!.totalCashTips).toBeCloseTo(0);
+            expect(brigidSummary!.totalCreditTips).toBeCloseTo(0);
+            expect(brigidSummary!.totalGrossCreditTips).toBeCloseTo(0);
+            expect(brigidSummary!.totalBarTipout).toBeCloseTo(70); // Receives 10% of 700 total liquor sales
+            expect(brigidSummary!.totalHostTipout).toBeCloseTo(0);
+            expect(brigidSummary!.totalSaTipout).toBeCloseTo(0);
+            expect(brigidSummary!.totalPayrollTips).toBeCloseTo(0 + 70); // 0 + 70 = 70
+            expect(brigidSummary!.payrollTotal).toBeCloseTo((9 * 6) + 70); // 54 + 70 = 124
+
+            // Christina (Host)
+            expect(christinaSummary!.tipPoolGroup).toBeUndefined(); // Not pooled
+            expect(christinaSummary!.totalCashTips).toBeCloseTo(0);
+            expect(christinaSummary!.totalCreditTips).toBeCloseTo(0);
+            expect(christinaSummary!.totalGrossCreditTips).toBeCloseTo(0);
+            expect(christinaSummary!.totalBarTipout).toBeCloseTo(0);
+            expect(christinaSummary!.totalHostTipout).toBeCloseTo(28); // Receives 7% of 400 total tips
+            expect(christinaSummary!.totalSaTipout).toBeCloseTo(0);
+            expect(christinaSummary!.totalPayrollTips).toBeCloseTo(0 + 28); // 0 + 28 = 28
+            expect(christinaSummary!.payrollTotal).toBeCloseTo((5 * 5) + 28); // 25 + 28 = 53
+
+            // Alex (SA)
+            expect(alexSummary!.tipPoolGroup).toBeUndefined(); // Not pooled
+            expect(alexSummary!.totalCashTips).toBeCloseTo(0);
+            expect(alexSummary!.totalCreditTips).toBeCloseTo(0);
+            expect(alexSummary!.totalGrossCreditTips).toBeCloseTo(0);
+            expect(alexSummary!.totalBarTipout).toBeCloseTo(0);
+            expect(alexSummary!.totalHostTipout).toBeCloseTo(0);
+            expect(alexSummary!.totalSaTipout).toBeCloseTo(16); // Receives 4% of 400 total tips
+            expect(alexSummary!.totalPayrollTips).toBeCloseTo(0 + 16); // 0 + 16 = 16
+            expect(alexSummary!.payrollTotal).toBeCloseTo((4 * 4) + 16); // 16 + 16 = 32
+        });
+        
+        it('should handle zero hour shifts correctly', () => {
+             const shifts: Shift[] = [
+                 mockShift('s1', empDylan, roleServer, '2024-03-15', 0, 50, 150, 400), // Zero hours
+                 mockShift('s2', empChristina, roleHost, '2024-03-15', 8, 0, 0, 0), // Normal hours
+            ];
+            const summaries = calculateEmployeeRoleSummariesDaily(shifts);
+            const dylanSummary = summaries.find(s => s.employeeName === 'Dylan');
+            
+            expect(dylanSummary).toBeDefined();
+            expect(dylanSummary!.totalHours).toBe(0);
+            expect(dylanSummary!.totalCashTips).toBeCloseTo(0); // Tips should be zeroed out due to pooling rate calc
+            expect(dylanSummary!.totalCreditTips).toBeCloseTo(0);
+            expect(dylanSummary!.totalGrossCreditTips).toBeCloseTo(150);
+            expect(dylanSummary!.totalPayrollTips).toBeCloseTo(0);
+            expect(dylanSummary!.cashTipsPerHour).toBe(0);
+            expect(dylanSummary!.creditTipsPerHour).toBe(0);
+            expect(dylanSummary!.totalTipsPerHour).toBe(0);
+            expect(dylanSummary!.payrollTotal).toBeCloseTo(0);
+        });
+
+        it('should handle empty shifts array', () => {
+            const summaries = calculateEmployeeRoleSummariesDaily([]);
+            expect(summaries).toEqual([]);
+        });
+
+    });
+}); 
\ No newline at end of file
diff --git a/src/utils/reportCalculations.ts b/src/utils/reportCalculations.ts
index 61b5b20..597946a 100644
--- a/src/utils/reportCalculations.ts
+++ b/src/utils/reportCalculations.ts
@@ -3,8 +3,75 @@ import {
   Shift,
   ReportSummary,
   EmployeeRoleSummary,
+  RoleConfig,
 } from '@/types/reports';
 
+import { isWithinInterval, parseISO, isBefore, isEqual } from 'date-fns';
+
+// Helper function to find the active configuration for a specific type and date
+const findActiveConfig = (shift: Shift, tipoutType: string): RoleConfig | null => {
+  if (!shift.role?.configs) return null;
+  
+  const shiftDate = parseISO(shift.date); // Parse the shift date string once
+  
+  const activeConfig = shift.role.configs.find(config => {
+    if (config.tipoutType !== tipoutType) return false;
+    
+    const effectiveFrom = parseISO(config.effectiveFrom);
+    const isAfterOrOnFrom = isEqual(shiftDate, effectiveFrom) || isBefore(effectiveFrom, shiftDate);
+    if (!isAfterOrOnFrom) return false;
+    
+    if (config.effectiveTo) {
+      const effectiveTo = parseISO(config.effectiveTo);
+      const isOnOrBeforeTo = isEqual(shiftDate, effectiveTo) || isBefore(shiftDate, effectiveTo);
+      return isOnOrBeforeTo;
+    } else {
+      return true;
+    }
+  });
+  
+  return activeConfig || null;
+};
+
+/**
+ * Finds the tipPoolGroup for a given shift based on its active config.
+ */
+const getShiftTipPoolGroup = (shift: Shift): string | null => {
+  // Find any active config first, then check its tipPoolGroup
+  // This assumes a role belongs entirely to one pool based on any active config.
+  // A more precise approach might find the active config for a specific *pooling type* if that were a concept.
+  if (!shift.role?.configs) return null;
+  const shiftDate = parseISO(shift.date);
+  
+  const relevantConfig = shift.role.configs.find(config => {
+     // Check date effectivity first
+     const effectiveFrom = parseISO(config.effectiveFrom);
+     const isAfterOrOnFrom = isEqual(shiftDate, effectiveFrom) || isBefore(effectiveFrom, shiftDate);
+     if (!isAfterOrOnFrom) return false;
+     if (config.effectiveTo) {
+       const effectiveTo = parseISO(config.effectiveTo);
+       const isOnOrBeforeTo = isEqual(shiftDate, effectiveTo) || isBefore(shiftDate, effectiveTo);
+       if (!isOnOrBeforeTo) return false;
+     } 
+     // If effective, check if it defines a tip pool group
+     return config.tipPoolGroup; // Return true if tipPoolGroup is defined and truthy
+  });
+
+  return relevantConfig?.tipPoolGroup || null;
+};
+
+// Helper type for intermediate calculations
+type ProcessedShift = Shift & {
+  originalCashTips: number;
+  originalCreditTips: number;
+  tipPoolGroup: string | null;
+  // Add net tipout amounts calculated daily
+  netBarTipout: number;
+  netHostTipout: number;
+  netSaTipout: number;
+  payrollTips: number; // Calculated based on pooled/adjusted tips + net tipouts
+};
+
 /**
  * Calculates the overall summary report for a given set of shifts.
  * Note: Averages calculated here might be less meaningful if pooling is done daily.
@@ -125,39 +192,137 @@ export const calculateOverallSummary = (shiftsToProcess: Shift[]): ReportSummary
  * Calculates employee/role summaries, performing tip pooling and distribution daily.
  */
 export const calculateEmployeeRoleSummariesDaily = (shiftsToProcess: Shift[]): EmployeeRoleSummary[] => {
-  const summaries = new Map<string, EmployeeRoleSummary>();
+  const dailyProcessedShifts: ProcessedShift[] = [];
 
   // Get unique dates from the shifts to process
   const uniqueDates = Array.from(new Set(shiftsToProcess.map(shift => shift.date.slice(0, 10))));
 
-  // Iterate through each day to calculate pools and distribute tips daily
+  // --- Process Day by Day ---
   uniqueDates.forEach(date => {
-    const dailyShifts = shiftsToProcess.filter(shift => shift.date.slice(0, 10) === date);
-    if (dailyShifts.length === 0) return; // Skip if no shifts on this date
-
-    // 1. Determine daily role presence
+    const originalDailyShifts = shiftsToProcess.filter(shift => shift.date.slice(0, 10) === date);
+    if (originalDailyShifts.length === 0) return; // Skip if no shifts on this date
+
+    // --- 0. PREPARE: Create copies with original tips preserved ---
+    // We need original tips for calculating tipouts PAID, but will modify tips for pooling.
+    const dailyShifts: (Shift & { originalCashTips: number, originalCreditTips: number })[] = originalDailyShifts.map(s => ({
+        ...s,
+        originalCashTips: s.cashTips,
+        originalCreditTips: s.creditTips,
+    }));
+
+    // --- 1. TIP POOLING (Based on tipPoolGroup) ---
+    const tipPools = new Map<string, {
+      totalCashTips: number;
+      totalCreditTips: number;
+      totalHours: number;
+      shifts: (Shift & { originalCashTips: number, originalCreditTips: number })[];
+      // Add fields to track pool's total tipout obligations
+      totalPaidBarTipout: number;
+      totalPaidHostTipout: number;
+      totalPaidSaTipout: number;
+    }>();
+
+    // Determine daily role presence first (needed for pool tipout calculation)
     let dailyHasHost = false;
     let dailyHasSA = false;
     let dailyHasBar = false;
     dailyShifts.forEach(shift => {
-      if (roleReceivesTipoutType(shift, 'host')) dailyHasHost = true;
-      if (roleReceivesTipoutType(shift, 'sa')) dailyHasSA = true;
-      if (roleReceivesTipoutType(shift, 'bar')) dailyHasBar = true;
+      // Check if an active config exists for the type and receivesTipout is not explicitly false
+      const hostConfig = findActiveConfig(shift, 'host');
+      if (hostConfig && hostConfig.receivesTipout !== false) dailyHasHost = true;
+      
+      const saConfig = findActiveConfig(shift, 'sa');
+      if (saConfig && saConfig.receivesTipout !== false) dailyHasSA = true;
+      
+      const barConfig = findActiveConfig(shift, 'bar');
+      if (barConfig && barConfig.receivesTipout !== false) dailyHasBar = true; 
+    });
+
+    // Group shifts into pools and calculate pool's gross tips/hours
+    dailyShifts.forEach(shift => {
+      const poolGroup = getShiftTipPoolGroup(shift);
+      if (poolGroup) {
+        if (!tipPools.has(poolGroup)) {
+          tipPools.set(poolGroup, { 
+            totalCashTips: 0, 
+            totalCreditTips: 0, 
+            totalHours: 0, 
+            shifts: [],
+            totalPaidBarTipout: 0,
+            totalPaidHostTipout: 0,
+            totalPaidSaTipout: 0
+          });
+        }
+        const pool = tipPools.get(poolGroup)!;
+        pool.totalCashTips += shift.originalCashTips; // Pool based on original collected tips
+        pool.totalCreditTips += shift.originalCreditTips;
+        pool.totalHours += Number(shift.hours);
+        pool.shifts.push(shift);
+      }
+      // Shifts not in a pool are processed later individually
     });
 
-    // 2. Calculate total daily tipout pools (paid INTO pools)
+    // Calculate total tipouts paid BY each pool and then the net rates
+    tipPools.forEach((pool) => {
+      // Calculate total tipouts paid by this pool's shifts
+      pool.shifts.forEach(shiftInPool => {
+        const shiftWithOriginalTips = {
+          ...shiftInPool,
+          cashTips: shiftInPool.originalCashTips,
+          creditTips: shiftInPool.originalCreditTips
+        };
+        const { barTipout, hostTipout, saTipout } = calculateTipouts(shiftWithOriginalTips, dailyHasHost, dailyHasSA, dailyHasBar);
+        if (rolePaysTipoutType(shiftInPool, 'bar')) pool.totalPaidBarTipout += barTipout;
+        if (rolePaysTipoutType(shiftInPool, 'host')) pool.totalPaidHostTipout += hostTipout;
+        if (rolePaysTipoutType(shiftInPool, 'sa')) pool.totalPaidSaTipout += saTipout;
+      });
+
+      // Calculate NET pool tips (assuming tipouts paid from credit)
+      // Adjust this logic if cash tips also contribute to tipouts
+      const poolNetCashTips = pool.totalCashTips; 
+      const poolNetCreditTips = pool.totalCreditTips - pool.totalPaidHostTipout - pool.totalPaidSaTipout; // Remove bar tipout from pool deduction
+
+      if (pool.totalHours > 0) {
+        const netPoolCashRate = poolNetCashTips / pool.totalHours;
+        const netPoolCreditRate = poolNetCreditTips / pool.totalHours;
+
+        // Adjust each shift's tips to reflect their NET share of the pool
+        pool.shifts.forEach(shift => {
+          shift.cashTips = Number(shift.hours) * netPoolCashRate;
+          shift.creditTips = Number(shift.hours) * netPoolCreditRate;
+          console.log(`Shift ${shift.id} (${shift.role.name}) NET adjusted tips: Cash=$${shift.cashTips.toFixed(2)}, Credit=$${shift.creditTips.toFixed(2)} from NET pool rate`);
+        });
+      } else {
+         pool.shifts.forEach(shift => {
+            shift.cashTips = 0;
+            shift.creditTips = 0;
+         });
+      }
+    });
+    // Shifts NOT in a pool retain their original cashTips/creditTips at this stage
+    // They will have tipouts calculated individually later.
+
+    // --- 2. Calculate total daily tipout pools (paid INTO distribution pools) ---
+    // This is for roles RECEIVING tipouts (Host, SA, Bar). It needs the total contributions
+    // from ALL shifts (pooled or not), calculated based on their ORIGINAL performance.
     let dailyBarTipoutPool = 0;
     let dailyHostTipoutPool = 0;
     let dailySATipoutPool = 0;
 
     dailyShifts.forEach(shift => {
-      const { barTipout, hostTipout, saTipout } = calculateTipouts(shift, dailyHasHost, dailyHasSA, dailyHasBar);
+      // Use original tips/sales for calculating contributions to distribution pools
+      const shiftWithOriginalTips = {
+          ...shift,
+          cashTips: shift.originalCashTips,
+          creditTips: shift.originalCreditTips
+      };
+      const { barTipout, hostTipout, saTipout } = calculateTipouts(shiftWithOriginalTips, dailyHasHost, dailyHasSA, dailyHasBar);
       if (rolePaysTipoutType(shift, 'bar')) dailyBarTipoutPool += barTipout;
       if (rolePaysTipoutType(shift, 'host')) dailyHostTipoutPool += hostTipout;
       if (rolePaysTipoutType(shift, 'sa')) dailySATipoutPool += saTipout;
     });
 
-    // 3. Calculate daily hours for roles within distribution groups
+    // --- 4. Calculate daily hours for roles within DISTRIBUTION groups (for receiving tipouts) ---
     const dailyDistributionGroupHours = new Map<string, number>();
     dailyShifts.forEach(shift => {
       shift.role?.configs.forEach(config => {
@@ -168,88 +333,146 @@ export const calculateEmployeeRoleSummariesDaily = (shiftsToProcess: Shift[]): E
       });
     });
 
-    // 4. Process each shift for the day: Accumulate stats, paid tipouts, and distribute received tipouts
+    // --- 5. Process each shift for the day: Calculate net received tipouts and final payroll tips ---
     dailyShifts.forEach(shift => {
-      if (!shift.employee || !shift.role) return; // Should not happen with valid data
+      if (!shift.employee || !shift.role) return;
 
-      const key = `${shift.employee.id}-${shift.role.name}`;
-      // Initialize or get existing summary for the employee/role combo
-      const existing = summaries.get(key) || {
-        employeeId: shift.employee.id,
-        employeeName: shift.employee.name,
-        roleName: shift.role.name,
-        totalHours: 0,
-        totalCashTips: 0,
-        totalCreditTips: 0,
-        totalBarTipout: 0,   // Net amount: +received / -paid
-        totalHostTipout: 0,  // Net amount: +received / -paid
-        totalSaTipout: 0,    // Net amount: +received / -paid
-        cashTipsPerHour: 0,    // Will calculate at the end
-        creditTipsPerHour: 0,   // Will calculate at the end
-        totalTipsPerHour: 0,   // Will calculate at the end
-        basePayRate: Number(shift.role.basePayRate), // Use shift's rate, assumes consistency
-        totalPayrollTips: 0, // Will calculate at the end
-        totalLiquorSales: 0,
-        payrollTotal: 0,     // Will calculate at the end
-      };
-
-      // Accumulate basic stats from this shift
-      existing.totalHours += Number(shift.hours);
-      existing.totalCashTips += Number(shift.cashTips);
-      existing.totalCreditTips += Number(shift.creditTips);
-      existing.totalLiquorSales += Number(shift.liquorSales);
-      existing.basePayRate = Number(shift.role.basePayRate); // Update base rate (usually consistent)
-
-      // Calculate tipouts this specific shift pays out
-      const { barTipout: paidBar, hostTipout: paidHost, saTipout: paidSA } = calculateTipouts(shift, dailyHasHost, dailyHasSA, dailyHasBar);
+      const isInPool = !!getShiftTipPoolGroup(shift);
+      let netBarTipout = 0;
+      let netHostTipout = 0;
+      let netSaTipout = 0;
+      let payrollTips = 0;
 
-      // Accumulate PAID amounts as negative values to the net total
-      if (rolePaysTipoutType(shift, 'bar')) existing.totalBarTipout -= paidBar;
-      if (rolePaysTipoutType(shift, 'host')) existing.totalHostTipout -= paidHost;
-      if (rolePaysTipoutType(shift, 'sa')) existing.totalSaTipout -= paidSA;
+      // --- Calculate Received Tipouts (Needed for both Pooled and Non-Pooled Payroll Tips) ---
+      let receivedBar = 0;
+      let receivedHost = 0;
+      let receivedSA = 0;
 
-      // Distribute RECEIVED amounts from *daily* pools
+      // Calculate received amounts from *daily* distribution pools based on this shift's hours and role config
       if (roleReceivesTipoutType(shift, 'bar')) {
         const distributionGroup = getRoleDistributionGroup(shift, 'bar');
         const groupTotalHours = distributionGroup ? dailyDistributionGroupHours.get(distributionGroup) : 0;
         if (distributionGroup && groupTotalHours && groupTotalHours > 0 && dailyBarTipoutPool > 0) {
-          const share = Number(shift.hours) / groupTotalHours;
-          existing.totalBarTipout += share * dailyBarTipoutPool;
+          receivedBar = (Number(shift.hours) / groupTotalHours) * dailyBarTipoutPool;
         }
       }
       if (roleReceivesTipoutType(shift, 'host')) {
-        const distributionGroup = getRoleDistributionGroup(shift, 'host');
-        const groupTotalHours = distributionGroup ? dailyDistributionGroupHours.get(distributionGroup) : 0;
-        if (distributionGroup && groupTotalHours && groupTotalHours > 0 && dailyHostTipoutPool > 0) {
-          const share = Number(shift.hours) / groupTotalHours;
-          existing.totalHostTipout += share * dailyHostTipoutPool;
-        }
+          const distributionGroup = getRoleDistributionGroup(shift, 'host');
+          const groupTotalHours = distributionGroup ? dailyDistributionGroupHours.get(distributionGroup) : 0;
+          if (distributionGroup && groupTotalHours && groupTotalHours > 0 && dailyHostTipoutPool > 0) {
+            receivedHost = (Number(shift.hours) / groupTotalHours) * dailyHostTipoutPool;
+          }
       }
       if (roleReceivesTipoutType(shift, 'sa')) {
-        const distributionGroup = getRoleDistributionGroup(shift, 'sa');
-        const groupTotalHours = distributionGroup ? dailyDistributionGroupHours.get(distributionGroup) : 0;
-        if (distributionGroup && groupTotalHours && groupTotalHours > 0 && dailySATipoutPool > 0) {
-          const share = Number(shift.hours) / groupTotalHours;
-          existing.totalSaTipout += share * dailySATipoutPool;
-        }
+          const distributionGroup = getRoleDistributionGroup(shift, 'sa');
+          const groupTotalHours = distributionGroup ? dailyDistributionGroupHours.get(distributionGroup) : 0;
+          if (distributionGroup && groupTotalHours && groupTotalHours > 0 && dailySATipoutPool > 0) {
+            receivedSA = (Number(shift.hours) / groupTotalHours) * dailySATipoutPool;
+          }
+      }
+      // --- End Calculate Received Tipouts ---
+
+      if (isInPool) {
+        // Pooled shifts already have NET tips assigned (shift.creditTips reflects net pool share).
+        // Their contribution to distribution pools was handled at the pool level.
+        // Instead of always zero, show negative if paid tipout (contributed to pool), positive if received (role).
+        const { barTipout, hostTipout, saTipout } = calculateTipouts(shift, dailyHasHost, dailyHasSA, dailyHasBar);
+        const paidBar = rolePaysTipoutType(shift, 'bar') ? barTipout : 0;
+        const paidHost = rolePaysTipoutType(shift, 'host') ? hostTipout : 0;
+        const paidSA = rolePaysTipoutType(shift, 'sa') ? saTipout : 0;
+        // Show net tipouts as received - paid (can be negative or positive)
+        netBarTipout = receivedBar - paidBar;
+        netHostTipout = receivedHost - paidHost;
+        netSaTipout = receivedSA - paidSA;
+        // Payroll Tips for pooled roles = Net Pooled Credit Share + Received Distribution Tipouts - All Paid Tipouts
+        payrollTips = shift.creditTips + receivedBar + receivedHost + receivedSA - paidBar - paidHost - paidSA;
+      } else {
+        // NON-POOLED shifts: Calculate tipouts paid and received individually.
+        const shiftWithOriginalTips = {
+            ...shift,
+            cashTips: shift.originalCashTips,
+            creditTips: shift.originalCreditTips
+        };
+        const { barTipout: paidBar, hostTipout: paidHost, saTipout: paidSA } = calculateTipouts(shiftWithOriginalTips, dailyHasHost, dailyHasSA, dailyHasBar);
+        
+        // Note: receivedBar, receivedHost, receivedSA already calculated above
+
+        // Calculate NET tipouts for this non-pooled shift (for display)
+        netBarTipout = receivedBar - (rolePaysTipoutType(shift, 'bar') ? paidBar : 0);
+        netHostTipout = receivedHost - (rolePaysTipoutType(shift, 'host') ? paidHost : 0);
+        netSaTipout = receivedSA - (rolePaysTipoutType(shift, 'sa') ? paidSA : 0);
+
+        // Payroll Tips = Original Credit Tips + Net Tipouts Received/Paid
+        payrollTips = shift.originalCreditTips + netBarTipout + netHostTipout + netSaTipout;
+        
+        // Use original tips for aggregation base if not pooled
+        shift.cashTips = shift.originalCashTips;
+        shift.creditTips = shift.originalCreditTips; 
       }
 
-      // Update the summary map
-      summaries.set(key, existing);
+      // Store the processed shift data for final aggregation
+      dailyProcessedShifts.push({
+        ...shift, // Includes original or net pooled cash/credit tips
+        originalCashTips: shift.originalCashTips,
+        originalCreditTips: shift.originalCreditTips,
+        netBarTipout, // For non-pooled: net paid/received; For pooled: 0
+        netHostTipout,// For non-pooled: net paid/received; For pooled: 0
+        netSaTipout,  // For non-pooled: net paid/received; For pooled: 0
+        payrollTips, // Contains the final payroll-relevant tip amount (fixed)
+        tipPoolGroup: getShiftTipPoolGroup(shift),
+      });
     });
-  }); // End of daily loop
+  }); // --- End of daily loop ---
+
+  // --- 6. Aggregate Processed Shifts into Final Summaries ---
+  const summaries = new Map<string, EmployeeRoleSummary>();
+  dailyProcessedShifts.forEach(procShift => {
+      const key = `${procShift.employee.id}-${procShift.role.name}`;
+      const existing = summaries.get(key) || {
+        employeeId: procShift.employee.id,
+        employeeName: procShift.employee.name,
+        roleName: procShift.role.name,
+        totalHours: 0,
+        totalCashTips: 0,        // Accumulate NET cash tips (pooled or original)
+        totalCreditTips: 0,      // Accumulate NET credit tips (pooled or original)
+        totalGrossCreditTips: 0, // New: Accumulate original gross credit tips
+        totalBarTipout: 0,       // Accumulate NET amounts (0 for pooled in current logic)
+        totalHostTipout: 0,      // Accumulate NET amounts (0 for pooled in current logic)
+        totalSaTipout: 0,        // Accumulate NET amounts (0 for pooled in current logic)
+        cashTipsPerHour: 0,
+        creditTipsPerHour: 0,
+        totalTipsPerHour: 0,
+        basePayRate: Number(procShift.role.basePayRate),
+        totalPayrollTips: 0,     // Accumulate final payrollTips amount
+        totalLiquorSales: 0,
+        payrollTotal: 0,
+        tipPoolGroup: procShift.tipPoolGroup,
+      };
+
+      // Accumulate totals from the processed shift
+      existing.totalHours += Number(procShift.hours);
+      existing.totalCashTips += procShift.cashTips; // Use net pooled or original cash
+      existing.totalCreditTips += procShift.creditTips; 
+      existing.totalGrossCreditTips += procShift.originalCreditTips;   // New accumulation of gross credit
+      existing.totalLiquorSales += Number(procShift.liquorSales);
+      existing.totalBarTipout += procShift.netBarTipout;   // Will be 0 for pooled shifts in current logic
+      existing.totalHostTipout += procShift.netHostTipout; // Will be 0 for pooled shifts in current logic
+      existing.totalSaTipout += procShift.netSaTipout;   // Will be 0 for pooled shifts in current logic
+      existing.totalPayrollTips += procShift.payrollTips; // Use the calculated payrollTips
+      existing.basePayRate = Number(procShift.role.basePayRate); 
+
+      summaries.set(key, existing);
+  });
 
-  // 5. Final calculations after processing all days and aggregating
+  // --- 7. Final calculations on aggregated summaries ---
   const finalSummaries = Array.from(summaries.values());
   finalSummaries.forEach(summary => {
-    // Calculate totalPayrollTips (CreditTips collected + Net Received/Paid Tipouts)
-    summary.totalPayrollTips = summary.totalCreditTips + summary.totalBarTipout + summary.totalHostTipout + summary.totalSaTipout;
-
     // Calculate final per-hour rates based on aggregated totals
+    // Cash tips per hour uses the net/original cash amount.
     summary.cashTipsPerHour = summary.totalHours > 0 ? summary.totalCashTips / summary.totalHours : 0;
-    // Use totalPayrollTips for credit/hr as it reflects the distributed/net amount
+    // Credit tips per hour uses the final payrollTips amount.
     summary.creditTipsPerHour = summary.totalHours > 0 ? summary.totalPayrollTips / summary.totalHours : 0;
-    // Total tips per hour is cash + payroll tips component
+    // Total tips per hour is cash + payroll tips component.
     summary.totalTipsPerHour = summary.totalHours > 0 ? (summary.totalCashTips + summary.totalPayrollTips) / summary.totalHours : 0;
 
     // Calculate final payroll total
diff --git a/src/utils/tipoutCalculations.test.ts b/src/utils/tipoutCalculations.test.ts
index a5c6f38..c4174bf 100644
--- a/src/utils/tipoutCalculations.test.ts
+++ b/src/utils/tipoutCalculations.test.ts
@@ -1,3 +1,4 @@
+import { describe, it, expect } from '@jest/globals';
 import { calculateTipouts, roleReceivesTipoutType, rolePaysTipoutType, getRoleDistributionGroup } from './tipoutCalculations';
 // We need to import the types. Adjust the path if they are defined elsewhere or re-define them here.
 // Assuming types might be in a central types file or directly in tipoutCalculations.ts and exported.
@@ -133,6 +134,19 @@ describe('calculateTipouts', () => {
 
         expect(calculateTipouts(shift, true, true, true)).toEqual({ barTipout: 0, hostTipout: 0, saTipout: 0 });
     });
+
+    it('should calculate bar tipout based on individual liquor sales after pooling', () => {
+        const configs: RoleConfig[] = [
+            { id: 'cfg1', tipoutType: 'bar', percentageRate: 10, effectiveFrom: '2023-01-01', effectiveTo: null, paysTipout: true },
+            { id: 'cfg2', tipoutType: 'host', percentageRate: 7, effectiveFrom: '2023-01-01', effectiveTo: null, paysTipout: true },
+            { id: 'cfg3', tipoutType: 'sa', percentageRate: 4, effectiveFrom: '2023-01-01', effectiveTo: null, paysTipout: true },
+        ];
+        const shift = createMockShift({ roleConfigs: configs, cashTips: 50, creditTips: 150, liquorSales: 400 });
+
+        // Bar tipout is calculated based on individual liquor sales after pooling
+        // 10% of 400 liquor sales = 40
+        expect(calculateTipouts(shift, true, true, true)).toEqual({ barTipout: 40, hostTipout: 14, saTipout: 8 });
+    });
 });
 
 
@@ -259,4 +273,76 @@ describe('getRoleDistributionGroup', () => {
         expect(getRoleDistributionGroup(shiftNoRole, 'bar')).toBe(null);
         expect(getRoleDistributionGroup(shiftNullConfigs, 'bar')).toBe(null);
     });
+});
+
+// --- findActiveConfig Tests (via calculateTipouts) ---
+
+describe('findActiveConfig', () => {
+    // Need access to the non-exported findActiveConfig.
+    // We can test it indirectly via the exported functions, 
+    // or make it exportable for testing, or copy its logic here.
+    // For simplicity here, let's assume we test its *effect* via calculateTipouts.
+    
+    const createDatedMockShift = (date: string, configs: RoleConfig[]): Shift => ({
+        id: 'datedShift',
+        date: date, // YYYY-MM-DD
+        hours: 8,
+        cashTips: 100,
+        creditTips: 200,
+        liquorSales: 500,
+        role: {
+            id: 'role1',
+            name: 'Server',
+            basePayRate: 10,
+            configs: configs,
+        },
+    });
+
+    it('should use config active on the shift date', () => {
+        const configs: RoleConfig[] = [
+            { id: 'cfg1', tipoutType: 'host', percentageRate: 1, effectiveFrom: '2024-01-01', effectiveTo: '2024-01-31', paysTipout: true },
+            { id: 'cfg2', tipoutType: 'host', percentageRate: 2, effectiveFrom: '2024-02-01', effectiveTo: null, paysTipout: true }, // Active in Feb
+        ];
+        const shiftJan = createDatedMockShift('2024-01-15', configs);
+        const shiftFeb = createDatedMockShift('2024-02-15', configs);
+
+        // Jan shift uses 1% -> hostTipout = (100+200) * 0.01 = 3
+        expect(calculateTipouts(shiftJan, true, false, false).hostTipout).toBeCloseTo(3);
+        // Feb shift uses 2% -> hostTipout = (100+200) * 0.02 = 6
+        expect(calculateTipouts(shiftFeb, true, false, false).hostTipout).toBeCloseTo(6);
+    });
+
+    it('should handle effectiveTo date correctly', () => {
+         const configs: RoleConfig[] = [
+            { id: 'cfg1', tipoutType: 'host', percentageRate: 1, effectiveFrom: '2024-01-01', effectiveTo: '2024-01-15', paysTipout: true }, // Active until Jan 15th inclusive
+        ];
+        const shiftOnEnd = createDatedMockShift('2024-01-15', configs);
+        const shiftAfterEnd = createDatedMockShift('2024-01-16', configs);
+
+        // Shift on end date uses the config
+        expect(calculateTipouts(shiftOnEnd, true, false, false).hostTipout).toBeCloseTo(3);
+         // Shift after end date does not use the config
+        expect(calculateTipouts(shiftAfterEnd, true, false, false).hostTipout).toBeCloseTo(0);
+    });
+
+     it('should handle effectiveFrom date correctly', () => {
+        const configs: RoleConfig[] = [
+            { id: 'cfg1', tipoutType: 'host', percentageRate: 1, effectiveFrom: '2024-01-15', effectiveTo: null, paysTipout: true }, // Active from Jan 15th inclusive
+        ];
+        const shiftOnStart = createDatedMockShift('2024-01-15', configs);
+        const shiftBeforeStart = createDatedMockShift('2024-01-14', configs);
+
+        // Shift on start date uses the config
+        expect(calculateTipouts(shiftOnStart, true, false, false).hostTipout).toBeCloseTo(3);
+        // Shift before start date does not use the config
+        expect(calculateTipouts(shiftBeforeStart, true, false, false).hostTipout).toBeCloseTo(0);
+     });
+
+     it('should return null if no config is active for the date', () => {
+         const configs: RoleConfig[] = [
+            { id: 'cfg1', tipoutType: 'host', percentageRate: 1, effectiveFrom: '2024-02-01', effectiveTo: null, paysTipout: true }, // Only active from Feb
+        ];
+        const shiftJan = createDatedMockShift('2024-01-15', configs);
+        expect(calculateTipouts(shiftJan, true, false, false).hostTipout).toBeCloseTo(0);
+     });
 }); 
\ No newline at end of file
diff --git a/src/utils/tipoutCalculations.ts b/src/utils/tipoutCalculations.ts
index 8b894cd..544bb1f 100644
--- a/src/utils/tipoutCalculations.ts
+++ b/src/utils/tipoutCalculations.ts
@@ -31,8 +31,39 @@ type Shift = {
   liquorSales: number;
 };
 
+import { isWithinInterval, parseISO, isBefore, isEqual } from 'date-fns';
+
+// Helper function to find the active configuration for a specific type and date
+const findActiveConfig = (shift: Shift, tipoutType: string): RoleConfig | null => {
+  if (!shift.role?.configs) return null;
+  
+  const shiftDate = parseISO(shift.date); // Parse the shift date string once
+  
+  const activeConfig = shift.role.configs.find(config => {
+    if (config.tipoutType !== tipoutType) return false;
+    
+    const effectiveFrom = parseISO(config.effectiveFrom);
+    // Check if shift date is on or after effectiveFrom
+    const isAfterOrOnFrom = isEqual(shiftDate, effectiveFrom) || isBefore(effectiveFrom, shiftDate);
+    if (!isAfterOrOnFrom) return false;
+    
+    // Check effectiveTo
+    if (config.effectiveTo) {
+      const effectiveTo = parseISO(config.effectiveTo);
+      // Check if shift date is on or before effectiveTo
+      const isOnOrBeforeTo = isEqual(shiftDate, effectiveTo) || isBefore(shiftDate, effectiveTo);
+      return isOnOrBeforeTo;
+    } else {
+      // If effectiveTo is null, it's active indefinitely from effectiveFrom
+      return true;
+    }
+  });
+  
+  return activeConfig || null;
+};
+
 /**
- * Calculate tipouts for a shift based on role configurations
+ * Calculate tipouts for a shift based on ACTIVE role configurations for the shift's date
  * 
  * @param shift The shift to calculate tipouts for
  * @param hasHost Whether hosts worked that day (affects host tipouts)
@@ -40,7 +71,7 @@ type Shift = {
  * @param hasBar Whether bartenders worked that day (affects bar tipouts)
  * @returns Object containing calculated tipout amounts
  */
-export const calculateTipouts = (shift: Shift, hasHost: boolean, hasSA: boolean, hasBar: boolean = false) => {
+export const calculateTipouts = (shift: Shift, hasHost: boolean, hasSA: boolean, hasBar: boolean = false): { barTipout: number, hostTipout: number, saTipout: number } => {
   if (!shift.role?.configs) return { barTipout: 0, hostTipout: 0, saTipout: 0 };
 
   console.log('calculateTipouts input:', { 
@@ -57,48 +88,28 @@ export const calculateTipouts = (shift: Shift, hasHost: boolean, hasSA: boolean,
   let hostTipout = 0;
   let saTipout = 0;
 
-  // Find the applicable configurations for this shift
-  shift.role.configs.forEach(config => {
-    // Only apply tipout if this role is configured to pay this type of tipout
-    const paysTipout = config.paysTipout !== false; // default to true if not specified
-    console.log('Config:', { 
-      type: config.tipoutType, 
-      rate: config.percentageRate, 
-      paysTipout,
-      totalTips,
-      liquorSales: shift.liquorSales,
-      would_apply_host: hasHost && config.tipoutType === 'host',
-      would_apply_sa: hasSA && config.tipoutType === 'sa',
-      would_apply_bar: hasBar && config.tipoutType === 'bar'
-    });
+  // Find ACTIVE config for each tipout type
+  const activeBarConfig = findActiveConfig(shift, 'bar');
+  const activeHostConfig = findActiveConfig(shift, 'host');
+  const activeSaConfig = findActiveConfig(shift, 'sa');
 
-    if (!paysTipout) return;
+  // Calculate Bar Tipout
+  if (hasBar && activeBarConfig && activeBarConfig.paysTipout !== false) {
+    barTipout = Number(shift.liquorSales) * (activeBarConfig.percentageRate / 100);
+    console.log(`Calculated bar tipout: ${barTipout} using rate ${activeBarConfig.percentageRate}%`);
+  }
 
-    switch (config.tipoutType) {
-      case 'bar':
-        // Bar tipout is calculated based on liquor sales
-        // Only calculate if there are bartenders to receive the tipout
-        if (hasBar) {
-          barTipout = Number(shift.liquorSales) * (config.percentageRate / 100);
-          console.log('Calculated bar tipout:', barTipout);
-        }
-        break;
-      case 'host':
-        if (hasHost) {
-          // Host tipout is calculated based on total tips
-          hostTipout = totalTips * (config.percentageRate / 100);
-          console.log('Calculated host tipout:', hostTipout);
-        }
-        break;
-      case 'sa':
-        if (hasSA) {
-          // SA tipout is calculated based on total tips
-          saTipout = totalTips * (config.percentageRate / 100);
-          console.log('Calculated SA tipout:', saTipout);
-        }
-        break;
-    }
-  });
+  // Calculate Host Tipout
+  if (hasHost && activeHostConfig && activeHostConfig.paysTipout !== false) {
+    hostTipout = totalTips * (activeHostConfig.percentageRate / 100);
+    console.log(`Calculated host tipout: ${hostTipout} using rate ${activeHostConfig.percentageRate}%`);
+  }
+
+  // Calculate SA Tipout
+  if (hasSA && activeSaConfig && activeSaConfig.paysTipout !== false) {
+    saTipout = totalTips * (activeSaConfig.percentageRate / 100);
+    console.log(`Calculated SA tipout: ${saTipout} using rate ${activeSaConfig.percentageRate}%`);
+  }
 
   const result = { barTipout, hostTipout, saTipout };
   console.log('Final tipout calculation:', result);
@@ -106,35 +117,29 @@ export const calculateTipouts = (shift: Shift, hasHost: boolean, hasSA: boolean,
 };
 
 /**
- * Helper function to check if a role receives a specific tipout type
+ * Helper function to check if a role receives a specific tipout type based on ACTIVE config
  */
 export const roleReceivesTipoutType = (shift: Shift, tipoutType: string): boolean => {
-  if (!shift.role?.configs) return false;
-  
-  return shift.role.configs.some(config => 
-    config.tipoutType === tipoutType && config.receivesTipout
-  );
+  const activeConfig = findActiveConfig(shift, tipoutType);
+  return !!activeConfig && !!activeConfig.receivesTipout;
 };
 
 /**
- * Helper function to check if a role pays a specific tipout type
+ * Helper function to check if a role pays a specific tipout type based on ACTIVE config
  */
 export const rolePaysTipoutType = (shift: Shift, tipoutType: string): boolean => {
-  if (!shift.role?.configs) return false;
-  
-  return shift.role.configs.some(config => 
-    config.tipoutType === tipoutType && config.paysTipout !== false
-  );
+  const activeConfig = findActiveConfig(shift, tipoutType);
+  // Default paysTipout to true if undefined or null, explicitly check for false
+  return !!activeConfig && activeConfig.paysTipout !== false;
 };
 
 /**
- * Helper function to get a role's distribution group for a tipout type
+ * Helper function to get a role's distribution group for a tipout type based on ACTIVE config
  */
 export const getRoleDistributionGroup = (shift: Shift, tipoutType: string): string | null => {
-  if (!shift.role?.configs) return null;
-  
-  const config = shift.role.configs.find(c => 
-    c.tipoutType === tipoutType && c.receivesTipout && c.distributionGroup
-  );
-  return config?.distributionGroup || null;
+  const activeConfig = findActiveConfig(shift, tipoutType);
+  if (activeConfig && activeConfig.receivesTipout && activeConfig.distributionGroup) {
+    return activeConfig.distributionGroup;
+  }
+  return null;
 }; 
\ No newline at end of file


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 4**

<details>
<summary>🔭 Outside diff range comments (3)</summary><blockquote>

<details>
<summary>src/app/(dashboard)/roles/[id]/edit/page.tsx (1)</summary><blockquote>

`95-107`: _🛠️ Refactor suggestion_

**Pool-group suggestions never loaded for new roles**  

Because `fetchExistingPoolGroups()` is invoked _after_ the early–return for `params.id === 'new'`, the datalist is always empty when creating a brand-new role. Users have to type blindly, defeating the purpose of the helper list.  
Move the call outside the conditional (or call it before `return`) so it executes for both edit and create flows.

```diff
   useEffect(() => {
-    if (params.id === 'new') {
-      // Creating a new role
-      setLoading(false)
-      return
-    }
-
-    // Fetch existing role
-    fetchRole()
-    fetchConfigs()
-    fetchExistingPoolGroups()
+    // Always populate the datalist
+    fetchExistingPoolGroups()
+
+    if (params.id === 'new') {
+      setLoading(false)
+      return
+    }
+
+    fetchRole()
+    fetchConfigs()
   }, [params.id, fetchRole, fetchConfigs, fetchExistingPoolGroups])
```

</blockquote></details>
<details>
<summary>src/app/api/reports/route.ts (1)</summary><blockquote>

`22-37`: _⚠️ Potential issue_

**Unused `roleConfigs` query = wasted round-trip**  

`roleConfigs` is fetched but never referenced afterwards. Remove the query or integrate it; otherwise each report request performs needless I/O.

</blockquote></details>
<details>
<summary>src/utils/reportCalculations.ts (1)</summary><blockquote>

`326-334`: _🛠️ Refactor suggestion_

**Distribution-group hours ignore effective-date filtering**  
When building `dailyDistributionGroupHours` every config is counted, even ones not effective on the current shift date, which will skew the denominator and under-pay/over-pay roles.

Refactor to reuse `findActiveConfig`:

```diff
-  shift.role?.configs.forEach(config => {
-    if (config.receivesTipout && config.distributionGroup) {
-      const currentHours = dailyDistributionGroupHours.get(config.distributionGroup) || 0;
-      dailyDistributionGroupHours.set(config.distributionGroup, currentHours + Number(shift.hours));
-    }
-  });
+  ['bar','host','sa'].forEach(type => {
+    const active = findActiveConfig(shift, type);
+    if (active?.receivesTipout && active.distributionGroup) {
+      const hrs = dailyDistributionGroupHours.get(active.distributionGroup) || 0;
+      dailyDistributionGroupHours.set(active.distributionGroup, hrs + Number(shift.hours));
+    }
+  });
```

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (10)</summary><blockquote>

<details>
<summary>src/utils/tipoutCalculations.test.ts (1)</summary><blockquote>

`278-347`: **Well-structured tests for date-based configuration selection**

These tests effectively validate the behavior of selecting the active role configuration based on shift date, even though the `findActiveConfig` function itself isn't directly exported. The approach of testing through the exported `calculateTipouts` function is a good workaround.

The tests cover important scenarios including:
- Configuration selection when multiple configs exist with different date ranges
- Proper handling of both `effectiveFrom` and `effectiveTo` boundary dates (inclusive)
- The case when no config is active for a given date

Consider adding one additional test for when multiple configurations for the same tipout type are active on the same date, to clarify expected behavior in that edge case.

</blockquote></details>
<details>
<summary>.cursor/rules/tip-logic.mdc (1)</summary><blockquote>

`1-73`: **Excellent documentation of tip calculation logic**

This comprehensive documentation clearly explains the tip pooling and distribution system, covering all aspects from daily presence checks through tip pooling, distribution, and payroll calculations.

The detailed breakdown of how bar tipouts differ from host and SA tipouts (being calculated and deducted after pooling at the individual level) is particularly valuable for understanding the system's behavior.

Consider enhancing this documentation with a simple diagram or concrete numerical example to help visualize the flow of tips through the system, especially for complex scenarios like daily pooling with multiple tipout types.

</blockquote></details>
<details>
<summary>src/utils/reportCalculations.test.ts (1)</summary><blockquote>

`1-290`: **Consider adding multi-pool test scenario**

While the test coverage is excellent, consider adding a test case that verifies behavior when multiple tip pools exist on the same day. This would ensure that tips don't cross pool boundaries and that distribution works correctly in more complex scenarios.

</blockquote></details>
<details>
<summary>src/app/(dashboard)/roles/[id]/edit/page.tsx (1)</summary><blockquote>

`81-93`: **Silent failure hides connectivity issues**  

`fetchExistingPoolGroups` swallows network/JSON errors and proceeds without user feedback. At minimum, store a warning in `error` so the banner already in place can surface the issue.

</blockquote></details>
<details>
<summary>src/utils/tipoutCalculations.ts (3)</summary><blockquote>

`34-35`: **Dead import increases bundle size**  

`isWithinInterval` is imported but never used after the refactor.

```diff
-import { isWithinInterval, parseISO, isBefore, isEqual } from 'date-fns';
+import { parseISO, isBefore, isEqual } from 'date-fns';
```

---

`42-60`: **Date comparison can be simplified & made clearer**  

Multiple `parseISO` + `isBefore/isEqual` checks are verbose and error-prone. `isWithinInterval` (already imported) can concisely cover both bounds and eliminates manual equality checks.

```diff
-  const activeConfig = shift.role.configs.find(config => {
-    ...
-    const isAfterOrOnFrom = isEqual(shiftDate, effectiveFrom) || isBefore(effectiveFrom, shiftDate);
-    ...
-    if (config.effectiveTo) {
-      const effectiveTo = parseISO(config.effectiveTo);
-      const isOnOrBeforeTo = isEqual(shiftDate, effectiveTo) || isBefore(shiftDate, effectiveTo);
-      return isOnOrBeforeTo;
-    } else {
-      return true;
-    }
-  });
+  const activeConfig = shift.role.configs.find(config => {
+    if (config.tipoutType !== tipoutType) return false;
+    const interval = {
+      start: parseISO(config.effectiveFrom),
+      end: config.effectiveTo ? parseISO(config.effectiveTo) : new Date('9999-12-31'),
+    };
+    return isWithinInterval(shiftDate, interval);
+  });
```

---

`140-143`: **Optional chaining removes the lint warning**  

Static analysis flagged this spot; using `activeConfig?.receivesTipout` clarifies intent.

```diff
-  if (activeConfig && activeConfig.receivesTipout && activeConfig.distributionGroup) {
-    return activeConfig.distributionGroup;
-  }
-  return null;
+  return activeConfig?.receivesTipout ? activeConfig.distributionGroup ?? null : null;
```

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 Biome (1.9.4)</summary>

[error] 141-141: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</blockquote></details>
<details>
<summary>src/app/(dashboard)/reports/page.tsx (2)</summary><blockquote>

`1101-1130`: **Improve accessibility & semantics for the tip-pool toggle**  
The “Show / Hide” text inside the button is not announced as a stateful toggle by most screen-readers.  
Add `aria-pressed`, an explicit label and/or use `<button aria-expanded>` to convey expanded/collapsed state.

```diff
-<button
-  type="button"
-  onClick={() => setShowPoolSummaries(v => !v)}
-  className="text-sm text-indigo-600 hover:underline"
->
-  {showPoolSummaries ? 'Hide' : 'Show'}
-</button>
+<button
+  type="button"
+  aria-expanded={showPoolSummaries}
+  onClick={() => setShowPoolSummaries(v => !v)}
+  className="text-sm text-indigo-600 hover:underline"
+>
+  {showPoolSummaries ? 'Hide summaries' : 'Show summaries'}
+</button>
```

---

`1284-1308`: **Column tool-tips are great, but titles leak internal jargon**  
The long `title` attributes might be truncated on some browsers and are hard to read on mobile.  
Consider moving these explanations to a help-icon with a popover / tooltip component for better UX.

</blockquote></details>
<details>
<summary>src/utils/reportCalculations.ts (1)</summary><blockquote>

`291-295`: **Remove debug `console.log`**  
The per-shift `console.log` inside the hot loop will spam the server / browser console and leak sensitive payroll numbers.

```diff
-          console.log(`Shift ${shift.id} (${shift.role.name}) NET adjusted tips: Cash=$${shift.cashTips.toFixed(2)}, Credit=$${shift.creditTips.toFixed(2)} from NET pool rate`);
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: CodeRabbit UI**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between db0fa3b98807b955017277b69690abbcf9dbc843 and 5a3600be31c4e68c9682976f90c0ae756a3da160.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `bun.lock` is excluded by `!**/*.lock`

</details>

<details>
<summary>📒 Files selected for processing (13)</summary>

* `.cursor/rules/tip-logic.mdc` (1 hunks)
* `prisma/migrations/20250430012212_add_tip_pool_group/migration.sql` (1 hunks)
* `prisma/schema.prisma` (1 hunks)
* `src/app/(dashboard)/reports/page.tsx` (10 hunks)
* `src/app/(dashboard)/roles/[id]/edit/page.tsx` (6 hunks)
* `src/app/api/reports/route.ts` (3 hunks)
* `src/app/api/roles/[id]/config/route.ts` (3 hunks)
* `src/app/api/tip-pool-groups/route.ts` (1 hunks)
* `src/types/reports.ts` (4 hunks)
* `src/utils/reportCalculations.test.ts` (1 hunks)
* `src/utils/reportCalculations.ts` (3 hunks)
* `src/utils/tipoutCalculations.test.ts` (3 hunks)
* `src/utils/tipoutCalculations.ts` (2 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>🧬 Code Graph Analysis (1)</summary>

<details>
<summary>src/app/api/tip-pool-groups/route.ts (2)</summary><blockquote>

<details>
<summary>src/app/api/roles/[id]/config/route.ts (1)</summary>

* `GET` (5-23)

</details>
<details>
<summary>src/middleware.ts (1)</summary>

* `config` (62-69)

</details>

</blockquote></details>

</details><details>
<summary>🪛 Biome (1.9.4)</summary>

<details>
<summary>src/utils/tipoutCalculations.ts</summary>

[error] 141-141: Change to an optional chain.

Unsafe fix: Change to an optional chain.


(lint/complexity/useOptionalChain)

</details>

</details>

</details>

<details>
<summary>🔇 Additional comments (25)</summary><blockquote>

<details>
<summary>prisma/migrations/20250430012212_add_tip_pool_group/migration.sql (1)</summary>

`1-2`: **Migration looks good but consider constraints**

The migration correctly adds the new `tipPoolGroup` column to the `RoleConfig` table. This aligns with the overall PR objective of implementing tip pool groups.

While the nullable TEXT column provides flexibility, consider whether any additional constraints might be beneficial, such as a CHECK constraint to prevent empty strings or a minimum length requirement.

</details>
<details>
<summary>src/app/api/roles/[id]/config/route.ts (3)</summary>

`3-3`: **Good type import for better type safety**

The import of the `RoleConfig` type from frontend types improves type safety compared to using `any`. This helps maintain consistency between frontend and backend data structures.

---

`42-43`: **Improved type safety for config parameter**

Replacing the implicit `any` type with the explicit `FrontendRoleConfig` type provides better type checking and autocompletion support, reducing the potential for errors.

---

`52-54`: **New tipPoolGroup field correctly included**

The `tipPoolGroup` property is now properly included in the data passed to `prisma.roleConfig.create()`, ensuring it gets saved to the database when creating new role configurations.

</details>
<details>
<summary>prisma/schema.prisma (2)</summary>

`70-70`: **Schema correctly updated with tipPoolGroup field**

The `tipPoolGroup String?` field has been properly added to the `RoleConfig` model, matching the database migration. The field is correctly defined as optional (nullable) to maintain compatibility with existing records.

---

`75-76`: **Improved schema organization**

Moving the `@@index([roleId])` directive below the `@@unique` constraint improves readability and follows a consistent organization pattern in the schema.

</details>
<details>
<summary>src/app/api/tip-pool-groups/route.ts (5)</summary>

`1-3`: **Good imports setup**

The imports are minimal and appropriately aliased, importing only what's needed for the endpoint functionality.

---

`4-5`: **API signature matches intended use**

The GET handler function signature is correct for a Next.js route handler and doesn't require any parameters since it's fetching all unique tip pool groups.

---

`6-17`: **Well-structured database query with proper filtering**

The query correctly:
- Finds role configurations with non-null and non-empty tip pool groups
- Selects only the relevant field (tipPoolGroup)
- Uses the `distinct` operator to avoid duplicates

This is an efficient approach to get unique tip pool group names from the database.

---

`19-23`: **Double validation ensures clean data**

The additional filtering after the database query provides an extra safety layer to ensure no null or empty values slip through. The TypeScript type assertion (`group is string`) is particularly good practice for maintaining type safety.

---

`24-28`: **Proper error handling with informative messages**

The endpoint includes appropriate error handling with:
- Error logging to the console
- A clear error message returned to the client
- Correct HTTP status code (500) for server errors

This follows best practices for API error handling.

</details>
<details>
<summary>src/utils/tipoutCalculations.test.ts (1)</summary>

`138-149`: **Test case accurately verifies bar tipout after pooling**

This new test case effectively validates that bar tipout is calculated correctly as 10% of liquor sales after tip pooling, along with the correct calculation of host and SA tipouts based on their respective percentages (7% and 4% of total tips).

The comment on line 147 clearly explains the expected calculation, and the assertion properly verifies the anticipated values.

</details>
<details>
<summary>src/types/reports.ts (4)</summary>

`20-21`: **Clear distinction between distribution and pool groups**

The distinction between `distributionGroup` (for receiving tipouts from others) and `tipPoolGroup` (for pooling collected tips with others) is well-documented, making the purpose of each field clear.

---

`40-41`: **Optional configs property added to Shift type**

This addition supports multiple role configurations per shift, enabling date-based active config selection.

---

`74-80`: **Enhanced documentation for tip-related fields**

The addition of `totalGrossCreditTips` and the detailed documentation for `totalBarTipout` provide clear context on how these values are calculated and used in the new pooling system. The comment explaining that pooled roles can now have negative or positive tipout values is particularly important for understanding the business logic.

---

`89-90`: **Added tipPoolGroup to EmployeeRoleSummary**

The addition of this optional field provides important context for reporting and helps identify which tip pool a particular employee/role belongs to.

</details>
<details>
<summary>src/utils/reportCalculations.test.ts (6)</summary>

`6-56`: **Well-structured test helpers and mock data setup**

The mock functions for creating employees, role configs, roles, and shifts provide a clean, flexible foundation for the test cases. The optional parameters with sensible defaults (especially in `mockRoleConfig`) allow for concise test data creation while maintaining clarity.

---

`96-142`: **Comprehensive tests for calculateOverallSummary**

These tests effectively verify that the overall summary calculations work correctly, including basic totals and tipout aggregation both with and without SA shifts present. The detailed comments explaining the expected calculations provide good context for understanding the test logic.

---

`143-182`: **Clear non-pooled scenario test**

This test case provides a simple baseline for understanding how tipouts work in a non-pooled context, verifying that a server correctly pays bar tipout and a host correctly receives it. The explicit calculations and assertions for both participants establish confidence in the basic tipout mechanics.

---

`181-262`: **Detailed server pool with bar tipout scenario test**

This comprehensive test effectively verifies the complex pooling and distribution logic, checking that:

1. Servers in the pool have their tips combined and distributed proportionally based on hours
2. Host and SA tipouts are paid from the pool before distribution
3. Bar tipout is calculated and paid by each server individually based on their liquor sales
4. Receiving roles (Bar, Host, SA) get the correct amounts from the respective tipout pools

The detailed expectations with calculated values provide excellent documentation of the expected behavior.

---

`264-282`: **Edge case: Zero hour shifts handled properly**

Good test coverage for the edge case of zero-hour shifts, ensuring that tip calculations properly handle this scenario without errors. The assertions verify that per-hour calculations don't result in division by zero errors and that payroll values are zeroed out appropriately.

---

`284-289`: **Edge case: Empty shifts array handled properly**

Testing the edge case of an empty shifts array ensures defensive coding and prevents potential errors when no data is available.

</details>
<details>
<summary>src/app/api/reports/route.ts (1)</summary>

`88-90`: **Default flags silently changed – verify business logic**  

`receivesTipout` now defaults to `false` and `paysTipout` to `true`. This reverses earlier behaviour and may alter calculations. Confirm with domain experts before release.

</details>
<details>
<summary>src/app/(dashboard)/reports/page.tsx (1)</summary>

`1164-1176`: **Grid count mismatch risk**  
The mobile card view was changed from a 3-column to a 4-column grid (`grid-cols-4`), but only **three** non-tipout items are shown when `gross credit tips` is hidden by feature flag or data omission.  
Double-check that the number of `<div>` children always matches `grid-cols-*` to avoid uneven spacing in edge cases.

</details>
<details>
<summary>src/utils/reportCalculations.ts (1)</summary>

`282-288`: **Negative pool net-credit guard**  
`poolNetCreditTips` can become negative if Host/SA tipouts exceed collected credit tips, yielding negative per-hour rates and cascading errors.  
Guard against this edge case with `Math.max(poolNetCreditTips, 0)` (or domain-specific fallback).

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-03T20:04:47Z

Review comments:
diff_hunk: @@ -427,6 +445,55 @@ export default function EditRolePage() {
                 </div>
               </div>
 
+              {/* Tip Pool Group Configuration */}
+              <div className="mt-6 p-4 bg-white/50 dark:bg-gray-800/50 rounded-lg border border-gray-200 dark:border-gray-700">
+                <h4 className="text-md font-medium text-gray-900 dark:text-white">Tip Pooling</h4>
+                <p className="mt-1 text-sm text-gray-500 dark:text-gray-400">
+                  Optionally assign this role to a tip pool group (e.g., "servers", "bartenders"). Roles within the same group on the same day will have their tips pooled and distributed based on hours worked.
+                  Leave blank if this role's tips should not be pooled.
+                </p>
+
+                <div className="mt-4">
+                  <label htmlFor="tipPoolGroupInput" className="block text-sm font-medium text-gray-700 dark:text-gray-300">
+                    Tip Pool Group Name
+                  </label>
+                  <div className="mt-1">
+                    <input
+                      type="text"
+                      id="tipPoolGroupInput"
+                      list="pool-groups-list"
+                      placeholder="Select existing or type new..." 
+                      value={configs.find(c => c.tipPoolGroup)?.tipPoolGroup || ''}
+                      onChange={(e) => {
+                        const poolName = e.target.value.trim() ? e.target.value.trim() : null;
+                        const updatedConfigs = configs.map(c => ({ ...c, tipPoolGroup: poolName }));
+                        if (updatedConfigs.length === 0 && poolName !== null) {
+                           setConfigs([{ 
+                             tipoutType: '', percentageRate: 0,
+                             effectiveFrom: new Date().toISOString().split('T')[0], effectiveTo: null,
+                             receivesTipout: false, paysTipout: false, distributionGroup: null,
+                             tipPoolGroup: poolName
+                           }]);
+                         } else if (configs.length > 0) {
+                           setConfigs(updatedConfigs);
+                         } else {
+                             setConfigs([]);
+                         }
+                      }}
path: src/app/(dashboard)/roles/[id]/edit/page.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**`onChange` can create an invalid phantom config row**  

If no configurations exist yet, the handler fabricates a new object whose `tipoutType` is `''` (empty string). `tipoutType` is `NOT NULL` in Prisma and probably validated on the backend, so saving will 500. A safer pattern is:

1. Store `tipPoolGroup` separately from `configs` when it is a role-wide attribute.  
2. Or, only add a config when the user explicitly creates one, not implicitly here.

```diff
- if (updatedConfigs.length === 0 && poolName !== null) {
-   setConfigs([{
-     tipoutType: '',                // <– invalid
-     ...
-   }])
- }
+ if (updatedConfigs.length === 0) {
+   // Nothing else to update – just keep configs empty,
+   // tipPoolGroup will persist once a real config is added.
+   return;
+ }
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
const poolName = e.target.value.trim() ? e.target.value.trim() : null;
const updatedConfigs = configs.map(c => ({ ...c, tipPoolGroup: poolName }));
if (updatedConfigs.length === 0) {
  // Nothing else to update – just keep configs empty,
  // tipPoolGroup will persist once a real config is added.
  return;
} else if (configs.length > 0) {
  setConfigs(updatedConfigs);
} else {
  setConfigs([]);
}
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-03T20:04:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -111,38 +111,49 @@ export async function GET(request: NextRequest) {
         const allShiftsData = await fetchShiftsFromDB(startDate, endDate);
 
         if (!allShiftsData || allShiftsData.length === 0) {
-            return NextResponse.json({ summary: null, employeeSummaries: [] });
+            return NextResponse.json({ summary: null, employeeSummaries: [], roleConfigs: {} });
         }
 
         // Get unique roles and their current configs
-        const roleConfigMap = new Map();
+        const roleConfigMap = new Map<string, Record<string, number>>();
         allShiftsData.forEach(shift => {
             if (!roleConfigMap.has(shift.role.name)) {
-                const roleConfigs = {
-                    bar: shift.role.configs.find(c => c.tipoutType === 'bar')?.percentageRate || 0,
-                    host: shift.role.configs.find(c => c.tipoutType === 'host')?.percentageRate || 0,
+                // Storing percentage rates directly for frontend display
+                const roleConfigRates = {
+                    barTipout: shift.role.configs.find(c => c.tipoutType === 'bar')?.percentageRate || 0,
+                    hostTipout: shift.role.configs.find(c => c.tipoutType === 'host')?.percentageRate || 0,
                     sa: shift.role.configs.find(c => c.tipoutType === 'sa')?.percentageRate || 0,
+                    // We might want other config details here later if needed
                 };
-                roleConfigMap.set(shift.role.name, roleConfigs);
+                roleConfigMap.set(shift.role.name, roleConfigRates);
             }
path: src/app/api/reports/route.ts
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Inconsistent key names (‘sa’ vs. ‘saTipout’) will break consumers**  

`barTipout` and `hostTipout` include the suffix but `sa` does not. Pick one convention to avoid front-end conditional code and runtime `undefined`s.

```diff
-    const roleConfigRates = {
-        barTipout: ...,
-        hostTipout: ...,
-        sa: shift.role.configs.find(c => c.tipoutType === 'sa')?.percentageRate || 0,
+    const roleConfigRates = {
+        barTipout: ...,
+        hostTipout: ...,
+        saTipout: shift.role.configs.find(c => c.tipoutType === 'sa')?.percentageRate || 0,
     };
```



<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
        // Get unique roles and their current configs
        const roleConfigMap = new Map<string, Record<string, number>>();
        allShiftsData.forEach(shift => {
            if (!roleConfigMap.has(shift.role.name)) {
                // Storing percentage rates directly for frontend display
                const roleConfigRates = {
                    barTipout: shift.role.configs.find(c => c.tipoutType === 'bar')?.percentageRate || 0,
                    hostTipout: shift.role.configs.find(c => c.tipoutType === 'host')?.percentageRate || 0,
                    saTipout: shift.role.configs.find(c => c.tipoutType === 'sa')?.percentageRate || 0,
                    // We might want other config details here later if needed
                };
                roleConfigMap.set(shift.role.name, roleConfigRates);
            }
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-03T20:04:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -833,6 +835,19 @@ function ReportsContent() {
   // Sorting logic remains the same
   displayedEmployeeSummaries = displayedEmployeeSummaries.slice().sort((a, b) => a.employeeName.localeCompare(b.employeeName))
 
+  // Option B: compute per-pool aggregates
+  const poolSummaries: [string, { totalHours: number; totalGrossCreditTips: number; totalNetCreditShare: number }][] = Object.entries(
+    displayedEmployeeSummaries.reduce((acc, s) => {
+      if (s.tipPoolGroup) {
+        if (!acc[s.tipPoolGroup]) acc[s.tipPoolGroup] = { totalHours: 0, totalGrossCreditTips: 0, totalNetCreditShare: 0 };
+        acc[s.tipPoolGroup].totalHours += s.totalHours;
+        acc[s.tipPoolGroup].totalGrossCreditTips += s.totalGrossCreditTips;
+        acc[s.tipPoolGroup].totalNetCreditShare += s.totalCreditTips;
+      }
+      return acc;
+    }, {} as Record<string, { totalHours: number; totalGrossCreditTips: number; totalNetCreditShare: number }>)
+  );
path: src/app/(dashboard)/reports/page.tsx
user: login: coderabbitai[bot]
body: _🛠️ Refactor suggestion_

**Potential double-counting when `groupByEmployee` is false**  
`poolSummaries` is derived from `displayedEmployeeSummaries`, which may contain multiple rows for the same employee in different roles when `groupByEmployee === false`.  
That means an employee working two pooled roles on the same day will have their hours and tips summed **twice** for the pool, inflating the pool metrics.

Consider aggregating from the raw `reportData.employeeSummaries` first and/or de-duplicating by `(employeeId, tipPoolGroup, day)` before accumulating.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-03T20:04:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -3,8 +3,75 @@ import {
   Shift,
   ReportSummary,
   EmployeeRoleSummary,
+  RoleConfig,
 } from '@/types/reports';
 
+import { isWithinInterval, parseISO, isBefore, isEqual } from 'date-fns';
+
path: src/utils/reportCalculations.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Unused import `isWithinInterval`**  
`isWithinInterval` is imported but never referenced, which will fail the TS compiler with `no-unused-vars`.

```diff
-import { isWithinInterval, parseISO, isBefore, isEqual } from 'date-fns';
+import { parseISO, isBefore, isEqual } from 'date-fns';
```




<!-- suggestion_start -->

<details>
<summary>📝 Committable suggestion</summary>

> ‼️ **IMPORTANT**
> Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

`````suggestion
-import { isWithinInterval, parseISO, isBefore, isEqual } from 'date-fns';
+import { parseISO, isBefore, isEqual } from 'date-fns';
`````

</details>

<!-- suggestion_end -->

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-05-03T20:04:46Z
author_association: CONTRIBUTOR

PR comments:
user: login: vercel[bot]
created_at: 2025-05-03T20:00:24Z
body: [vc]: #2C2faJELCQbI5mY/LxTGIzjsmreR/FXSwbewFigboiE=:eyJpc01vbm9yZXBvIjp0cnVlLCJ0eXBlIjoiZ2l0aHViIiwicHJvamVjdHMiOlt7Im5hbWUiOiJ0aXBvdXQiLCJsaXZlRmVlZGJhY2siOnsicmVzb2x2ZWQiOjAsInVucmVzb2x2ZWQiOjAsInRvdGFsIjowLCJsaW5rIjoidGlwb3V0LWdpdC10aXAtcG9vbC1ncm91cHMta3lsZWtpbmNlcnMtcHJvamVjdHMudmVyY2VsLmFwcCJ9LCJpbnNwZWN0b3JVcmwiOiJodHRwczovL3ZlcmNlbC5jb20va3lsZWtpbmNlcnMtcHJvamVjdHMvdGlwb3V0L0JDd0dQcmptWTd3NHY5dFVZZWdDRndvRTdDZVkiLCJwcmV2aWV3VXJsIjoidGlwb3V0LWdpdC10aXAtcG9vbC1ncm91cHMta3lsZWtpbmNlcnMtcHJvamVjdHMudmVyY2VsLmFwcCIsIm5leHRDb21taXRTdGF0dXMiOiJERVBMT1lFRCIsInJvb3REaXJlY3RvcnkiOm51bGx9XX0=
**The latest updates on your projects**. Learn more about [Vercel for Git ↗︎](https://vercel.link/github-learn-more)

| Name | Status | Preview | Comments | Updated (UTC) |
| :--- | :----- | :------ | :------- | :------ |
| **tipout** | ✅ Ready ([Inspect](https://vercel.com/kylekincers-projects/tipout/BCwGPrjmY7w4v9tUYegCFwoE7CeY)) | [Visit Preview](https://tipout-git-tip-pool-groups-kylekincers-projects.vercel.app) | 💬 [**Add feedback**](https://vercel.live/open-feedback/tipout-git-tip-pool-groups-kylekincers-projects.vercel.app?via=pr-comment-feedback-link) | May 3, 2025 8:14pm |


author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-05-03T20:00:26Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- This is an auto-generated comment: skip review by coderabbit.ai -->

> [!IMPORTANT]
> ## Review skipped
> 
> Auto reviews are disabled on base/target branches other than the default branch.
> 
> 
> 
> Please check the settings in the CodeRabbit UI or the `.coderabbit.yaml` file in this repository. To trigger a single review, invoke the `@coderabbitai review` command.
> 
> You can disable this status message by setting the `reviews.review_status` to `false` in the CodeRabbit configuration file.

<!-- end of auto-generated comment: skip review by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough

This set of changes introduces a comprehensive daily tip pooling and distribution mechanism, with supporting UI, database, API, type, and test updates. The tip pooling logic is implemented in the report calculation utilities, using new helper functions to select active configurations based on shift dates and to group shifts by tip pool. The database schema is extended to include a `tipPoolGroup` field for role configurations, and new API endpoints are added to manage and retrieve tip pool groups. The reports UI is enhanced to display tip pool summaries and gross credit tips, and the role edit page now allows assignment of roles to tip pool groups. Types are updated throughout to support the new fields, and extensive new tests verify the pooling and tipout logic.

## Changes

| File(s)                                                                                                 | Change Summary                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                       |
|---------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| .cursor/rules/tip-logic.mdc                                                                             | Added a new documentation file summarizing the tip calculation, pooling, and distribution logic, including implementation details, formulas, and references to relevant files and types.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| prisma/migrations/20250430012212_add_tip_pool_group/migration.sql<br>prisma/schema.prisma               | Added a new `tipPoolGroup` column to the `RoleConfig` table (migration and schema). Adjusted formatting and moved index directive in schema.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| src/types/reports.ts                                                                                    | Extended `RoleConfig` and `EmployeeRoleSummary` types to include `tipPoolGroup` and clarified comments. Added `totalGrossCreditTips` to `EmployeeRoleSummary`. Updated `Shift` type to include optional `configs`.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                  |
| src/app/(dashboard)/reports/page.tsx                                                                    | Added state and UI for displaying tip pool summaries. Aggregated summaries by `tipPoolGroup`, added gross credit tips to summaries and UI, updated table headers, and included explanatory tooltips.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| src/app/(dashboard)/roles/[id]/edit/page.tsx                                                           | Extended `RoleConfig` to include `tipPoolGroup`. Added state and UI for assigning roles to tip pool groups, including fetching existing groups and providing a datalist input.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| src/app/api/reports/route.ts                                                                            | Modified shift query to include full role configs (including `tipPoolGroup`). Updated mapping and response structure, adjusted default values, improved error handling and logging.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             |
| src/app/api/roles/[id]/config/route.ts                                                                  | Added explicit typing for configs and included `tipPoolGroup` in role config creation.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| src/app/api/tip-pool-groups/route.ts                                                                    | Added new API route to fetch distinct non-null, non-empty `tipPoolGroup` names from the database.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| src/utils/reportCalculations.ts                                                                         | Rewrote tip calculation logic to process shifts day-by-day, group by tip pool, and distribute tips and tipouts according to new pooling logic. Added helpers for active config selection and pool grouping. Aggregated results by employee-role after daily processing.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |
| src/utils/reportCalculations.test.ts                                                                    | Added comprehensive unit tests covering basic, pooled, non-pooled, and edge-case scenarios for the new tip pooling and distribution logic.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| src/utils/tipoutCalculations.ts<br>src/utils/tipoutCalculations.test.ts                                 | Added `findActiveConfig` helper to select active role config by date and tipout type. Refactored tipout calculation and related helpers to use active configs. Added and updated tests for correct config selection and tipout calculation.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |

## Sequence Diagram(s)

```mermaid
sequenceDiagram
    participant UI as Reports Page
    participant API as Reports API
    participant DB as Database
    participant Utils as Report Calculations

    UI->>API: Request shifts and role configs (with tipPoolGroup)
    API->>DB: Query shifts with role configs
    DB-->>API: Return shifts with configs (including tipPoolGroup)
    API->>Utils: Call calculateEmployeeRoleSummariesDaily(shifts)
    Utils->>Utils: For each day, group shifts by tipPoolGroup
    Utils->>Utils: Aggregate tips, hours, tipouts per pool
    Utils->>Utils: Distribute pooled tips and tipouts
    Utils->>Utils: Aggregate by employee-role
    Utils-->>API: Return employee-role summaries (with gross/net tips, pool info)
    API-->>UI: Respond with summaries and configs
    UI->>UI: Aggregate and display pool summaries and employee summaries
```

## Poem

> In the garden of tips, where the carrots grow tall,  
> We pooled all our greens—now there's plenty for all!  
> With configs and summaries, the logic hops true,  
> Each bunny gets credit for the work that they do.  
> The pools are now clearer, the shares more precise—  
> Hop, hop for fair tips, and a system that's nice!  
> 🥕✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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 -->

<!-- internal state end -->
<!-- tips_start -->

---



<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=KyleKincer/tipout&utm_content=4):

- Review comments: Directly reply to a review comment made by CodeRabbit. Example:
  - `I pushed a fix in commit <commit_id>, please review it.`
  - `Generate unit testing code for this file.`
  - `Open a follow-up GitHub issue for this discussion.`
- Files and specific lines of code (under the "Files changed" tab): Tag `@coderabbitai` in a new review comment at the desired location with your query. Examples:
  - `@coderabbitai generate unit testing code for this file.`
  -	`@coderabbitai modularize this function.`
- PR comments: Tag `@coderabbitai` in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
  - `@coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.`
  - `@coderabbitai read src/utils.ts and generate unit testing code.`
  - `@coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.`
  - `@coderabbitai help me debug CodeRabbit configuration file.`

### Support

Need help? Create a ticket on our [support page](https://www.coderabbit.ai/contact-us/support) for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai generate sequence diagram` to generate a sequence diagram of the changes in this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

- Add `@coderabbitai ignore` anywhere in the PR description to prevent this PR from being reviewed.
- Add `@coderabbitai summary` to generate the high-level summary at a specific location in the PR description.
- Add `@coderabbitai` anywhere in the PR title to generate the title automatically.

### CodeRabbit Configuration File (`.coderabbit.yaml`)

- You can programmatically configure CodeRabbit by adding a `.coderabbit.yaml` file to the root of your repository.
- Please see the [configuration documentation](https://docs.coderabbit.ai/guides/configure-coderabbit) for more information.
- If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json`

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: KyleKincer
created_at: 2025-05-03T20:00:49Z
body: @coderabbitai review
author_association: OWNER
user: login: coderabbitai[bot]
created_at: 2025-05-03T20:00:54Z
body: <!-- This is an auto-generated reply by CodeRabbit -->
<details>
<summary>✅ Actions performed</summary>

Review triggered.

> Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

</details>
author_association: CONTRIBUTOR