From 6517a3f4cddbce1af717ccae562f86d0f6a8f324 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Tue, 26 May 2026 05:15:10 +0000 Subject: [PATCH] sync: update auto sync (A@67794d69b70f7d291333020c5ffd071a7836d177) --- .assets_mapping | 3 + .env.example | 19 + .env.production | 6 + .gitignore | 106 + .gitkeep | 0 .npmrc | 1 + .../plan.md | 74 + .../todos.json | 20 + .../todos.json | 26 + .../todos.json | 26 + .../todos.json | 32 + .../todos.json | 32 + .../todos.json | 26 + .../todos.json | 20 + .../todos.json | 14 + .../todos.json | 20 + .../todos.json | 20 + .../todos.json | 20 + .../todos.json | 38 + .../todos.json | 26 + .../todos.json | 38 + .../todos.json | 44 + .../todos.json | 38 + .../todos.json | 20 + .../todos.json | 32 + .../todos.json | 32 + .../todos.json | 26 + .../todos.json | 44 + .../todos.json | 14 + .../todos.json | 20 + .../todos.json | 26 + .../todos.json | 32 + .../todos.json | 38 + .../todos.json | 32 + .../todos.json | 56 + .../todos.json | 38 + .../todos.json | 38 + .../todos.json | 26 + .../todos.json | 20 + .../todos.json | 26 + .../todos.json | 38 + .../todos.json | 56 + .../todos.json | 20 + .../todos.json | 20 + .../todos.json | 32 + AGENTS.md | 1800 ++++ README.md | 690 +- assets/CLAUDE.md | 65 + assets/入库流程图.docx | Bin 0 -> 14024 bytes assets/微信图片_20260525195238_49_125.jpg | Bin 0 -> 6538 bytes e2e/auth.setup.ts | 18 + e2e/collaboration-workflow.spec.ts | 182 + e2e/core-workflow.spec.ts | 149 + functions/data-consistency-check/index.ts | 176 + functions/fabric-warehouse-inbound/index.ts | 148 + index.html | 56 + jest.config.js | 34 + .../20260523_052639_create_base_tables.sql | 46 + .../20260523_052709_create_plan_tables.sql | 48 + ...0260523_052735_create_warehouse_tables.sql | 40 + ...2800_create_process_and_payment_tables.sql | 32 + .../20260523_052842_enable_rls_all_tables.sql | 73 + .../20260523_052936_create_test_users.sql | 66 + ...523_053030_seed_companies_and_profiles.sql | 37 + ...20260523_053110_seed_plans_and_related.sql | 37 + ...523_124703_add_company_id_to_inventory.sql | 14 + ...24742_create_product_yarn_ratios_table.sql | 24 + ...31935_add_production_price_to_products.sql | 6 + ..._151243_add_rolls_to_inventory_records.sql | 6 + .../20260523_162407_drop_old_rls_policies.sql | 40 + ...260523_162428_create_helper_function_1.sql | 23 + ...260523_162436_create_helper_function_2.sql | 32 + .../20260523_162443_create_companies_rls.sql | 22 + .../20260523_162456_create_profiles_rls.sql | 24 + ...523_162514_create_production_plans_rls.sql | 31 + ...60523_162530_create_plan_factories_rls.sql | 39 + ..._162547_create_warehouse_inventory_rls.sql | 45 + .../20260523_162604_create_yarn_rls.sql | 53 + ...523_162626_create_payments_process_rls.sql | 77 + ...523_162653_drop_remaining_old_policies.sql | 28 + .../20260523_162700_fix_profiles_rls.sql | 22 + ...33_add_company_id_to_inventory_records.sql | 12 + ...3_162846_add_company_id_to_yarn_ratios.sql | 12 + ...9_add_company_id_to_plan_process_steps.sql | 12 + ...23_162913_update_inventory_records_rls.sql | 31 + ...20260523_162926_update_yarn_ratios_rls.sql | 32 + ...3_162941_update_plan_process_steps_rls.sql | 32 + .../20260523_163000_sync_company_id_data.sql | 27 + ...523_163054_add_share_link_rls_policies.sql | 37 + .../20260523_163116_fix_share_link_rls_v2.sql | 41 + ...260523_163245_add_missing_rls_policies.sql | 52 + ...60523_163255_fix_inventory_records_rls.sql | 32 + ...20260523_163429_fix_plan_factories_rls.sql | 34 + ...0523_163537_fix_plan_process_steps_rls.sql | 51 + ...23_163644_fix_select_policies_security.sql | 50 + ..._164422_remove_duplicate_factory_links.sql | 17 + ...1_add_unique_constraint_plan_factories.sql | 15 + ...0260523_165555_create_audit_logs_table.sql | 6 + ...260523_165857_fix_inventory_insert_rls.sql | 6 + ...0260523_165900_fix_payments_insert_rls.sql | 6 + ...0523_165901_fix_yarn_ratios_delete_rls.sql | 6 + ...23_165913_fix_process_steps_select_rls.sql | 6 + ...0260523_170000_create_audit_logs_table.sql | 46 + ...0523_170036_fix_yarn_ratios_select_rls.sql | 6 + ...0523_191341_create_notifications_table.sql | 19 + .../20260523_191350_add_notifications_rls.sql | 20 + ...524_001_add_rolls_to_inventory_records.sql | 5 + .../20260524_002_create_accounts_payable.sql | 53 + .../20260524_003_add_price_to_inventory.sql | 14 + .../20260524_004_add_accounts_payable_rls.sql | 65 + .../20260524_005_create_price_history.sql | 58 + ...60524_006_create_product_price_history.sql | 37 + ...0260524_054617_create_accounts_payable.sql | 6 + ...20260524_054623_add_price_to_inventory.sql | 6 + ...260524_054637_add_accounts_payable_rls.sql | 6 + .../20260524_055450_create_price_history.sql | 6 + ...24_055840_create_product_price_history.sql | 6 + ...24_100730_add_display_name_to_profiles.sql | 6 + ...0524_144737_create_washing_plans_table.sql | 66 + ..._create_product_outbound_records_table.sql | 41 + ...55028_fix_plan_factories_insert_policy.sql | 23 + ..._155840_create_finished_products_table.sql | 77 + ..._create_washing_plan_completions_table.sql | 63 + ...nished_product_inventory_records_table.sql | 61 + ..._160012_create_auto_import_function_v2.sql | 64 + ...0524_160018_create_auto_import_trigger.sql | 12 + ...24_163600_create_washing_process_steps.sql | 54 + .../20260524_165641_fix_washing_plans_rls.sql | 6 + ...260524_165658_fix_auto_import_function.sql | 6 + ...60524_165708_fix_finished_products_rls.sql | 6 + .../20260524_170000_fix_washing_plans_rls.sql | 25 + ...260524_170001_fix_auto_import_function.sql | 96 + ...60524_170002_fix_finished_products_rls.sql | 49 + ...60525_060503_add_product_image_support.sql | 14 + ...20260525_060536_add_product_images_rls.sql | 28 + outputs/demo-animation-guide.md | 109 + outputs/demo-files.zip | Bin 0 -> 3782 bytes outputs/textile-system-demo.excalidraw | 691 ++ package.json | 57 + playwright.config.ts | 44 + pnpm-lock.yaml | 8201 +++++++++++++++++ postcss.config.js | 6 + public/index.html | 39 + .../.skill-version | 1 + skills/excalidraw-diagram-generator/SKILL.md | 613 ++ .../references/element-types.md | 497 + .../references/excalidraw-schema.md | 350 + .../scripts/.gitignore | 34 + .../scripts/README.md | 193 + .../scripts/add-arrow.py | 312 + .../scripts/add-icon-to-diagram.py | 404 + .../scripts/split-excalidraw-library.py | 183 + ...business-flow-swimlane-template.excalidraw | 334 + .../class-diagram-template.excalidraw | 558 ++ .../data-flow-diagram-template.excalidraw | 279 + .../templates/er-diagram-template.excalidraw | 662 ++ .../templates/flowchart-template.excalidraw | 179 + .../templates/mindmap-template.excalidraw | 244 + .../relationship-template.excalidraw | 145 + .../sequence-diagram-template.excalidraw | 509 + skills/meoo-cloud/.skill-version | 1 + skills/meoo-cloud/SKILL.md | 194 + .../meoo-cloud/references/authentication.md | 203 + skills/meoo-cloud/references/database.md | 184 + .../meoo-cloud/references/edge-functions.md | 149 + skills/meoo-cloud/references/realtime.md | 90 + skills/meoo-cloud/references/storage.md | 85 + .../meoo-cloud/references/troubleshooting.md | 99 + skills/mobile-dev/.skill-version | 1 + skills/mobile-dev/SKILL.md | 198 + skills/react-project/.skill-version | 1 + skills/react-project/SKILL.md | 85 + src/App.tsx | 148 + src/__tests__/hooks/useNetworkStatus.test.ts | 139 + src/__tests__/hooks/useResponsive.test.ts | 79 + src/__tests__/hooks/useTheme.test.ts | 72 + src/__tests__/setup.ts | 101 + src/__tests__/utils/helpers.test.ts | 297 + src/components/ComingSoonModal.tsx | 146 + src/components/DemoDisclaimerModal.tsx | 172 + src/components/DemoWatermark.tsx | 11 + src/components/ErrorBoundary.tsx | 173 + src/components/InventoryRecordsModal.tsx | 175 + src/components/NetworkStatusBar.tsx | 113 + src/components/NotificationCenter.tsx | 249 + src/components/OnboardingGuide.tsx | 308 + src/components/PageTransition.tsx | 259 + src/components/PlanCard.tsx | 76 + src/components/ProcessFlow.tsx | 382 + src/components/ProgressBar.tsx | 48 + src/components/StatusBadge.tsx | 36 + src/components/VirtualList.tsx | 294 + src/components/plans/PlanEditModal.tsx | 282 + src/components/plans/PlanGroup.tsx | 357 + src/components/plans/ShareModal.tsx | 90 + src/components/warehouse/InboundForm.tsx | 164 + src/components/warehouse/InboundRecords.tsx | 130 + src/components/warehouse/OutboundForm.tsx | 196 + src/components/warehouse/ProductForm.tsx | 396 + src/components/warehouse/ProductList.tsx | 373 + src/components/warehouse/WarehouseNav.tsx | 53 + src/contexts/AuthContext.tsx | 215 + src/engine/processEngine.ts | 326 + src/hooks/useInventoryData.ts | 395 + src/hooks/useNetworkStatus.ts | 238 + src/hooks/usePlanData.ts | 259 + src/hooks/useRealtime.ts | 212 + src/hooks/useResponsive.ts | 82 + src/hooks/useTheme.ts | 96 + src/hooks/useVirtualScroll.ts | 252 + src/index.tsx | 14 + src/pages/DemoDataSharing.tsx | 437 + src/pages/ImportPlanPage.tsx | 295 + src/pages/LoginPage.tsx | 551 ++ src/pages/MemberManage.tsx | 764 ++ src/pages/RegisterPage.tsx | 569 ++ src/pages/RoleSelectPage.tsx | 113 + src/pages/purchaser/AccountsPayable.tsx | 342 + src/pages/purchaser/Dashboard.tsx | 272 + src/pages/purchaser/FactoryManage.tsx | 455 + src/pages/purchaser/FinishedWarehouse.tsx | 298 + src/pages/purchaser/NewPlan.tsx | 515 ++ src/pages/purchaser/NewWashingPlan.tsx | 500 + src/pages/purchaser/PlanOverview.tsx | 363 + src/pages/purchaser/WarehouseManage.tsx | 729 ++ src/pages/textile/Dashboard.tsx | 256 + src/pages/textile/FabricWarehouse.tsx | 466 + src/pages/textile/PaymentPending.tsx | 166 + src/pages/textile/PlanOverview.tsx | 823 ++ src/pages/textile/YarnWarehouse.tsx | 259 + src/pages/washing/CompletedFabric.tsx | 197 + src/pages/washing/Dashboard.tsx | 281 + src/pages/washing/FinishedWarehouse.tsx | 309 + src/pages/washing/PaymentPending.tsx | 169 + src/pages/washing/PendingFabric.tsx | 163 + src/pages/washing/PlanOverview.tsx | 657 ++ src/stores/index.ts | 260 + src/styles/index.css | 576 ++ src/supabase/client.ts | 21 + src/supabase/types.ts | 1753 ++++ src/types/index.ts | 235 + src/utils/constants.ts | 71 + src/utils/crypto.ts | 97 + src/utils/helpers.ts | 180 + src/utils/notifications.ts | 231 + src/utils/shareCrypto.ts | 204 + tailwind.config.js | 15 + tsconfig.json | 16 + webpack.config.js | 107 + 249 files changed, 44140 insertions(+), 2 deletions(-) create mode 100644 .assets_mapping create mode 100644 .env.example create mode 100644 .env.production create mode 100644 .gitignore create mode 100644 .gitkeep create mode 100644 .npmrc create mode 100644 .plan/34a2df39-85b1-4ed9-8a3e-34f56d54af64/plan.md create mode 100644 .todo/0589cf52-b6c9-4f19-af07-9eb17e004557/todos.json create mode 100644 .todo/0810f58f-05ef-46ac-91b1-4c05f22a70f4/todos.json create mode 100644 .todo/09dd1b69-c113-4ca2-a83a-044bf9c00003/todos.json create mode 100644 .todo/09f94466-5d0a-457b-b49d-42af313a8245/todos.json create mode 100644 .todo/0c6171a6-6db8-467a-80b3-94728c411830/todos.json create mode 100644 .todo/116bc950-799c-4fc3-84b3-9dc8fe9c321c/todos.json create mode 100644 .todo/1287da78-b83f-458a-bb81-b5389a8d9bf4/todos.json create mode 100644 .todo/16757baf-4462-461a-8427-1c8f99592c3c/todos.json create mode 100644 .todo/17d26d13-5ca3-4b05-86c1-24a8b4c04f6f/todos.json create mode 100644 .todo/21334c3e-e7b7-44af-8a00-547aa2bc901e/todos.json create mode 100644 .todo/229c2dad-a2d1-40fe-9472-aa756ad3bcaa/todos.json create mode 100644 .todo/2ab0661d-b5c8-41b8-8e7b-80adcd21c257/todos.json create mode 100644 .todo/2bb16aa0-0012-4d1d-9856-fb883e451936/todos.json create mode 100644 .todo/2f822c41-126f-49ca-a958-de257d6d0ed4/todos.json create mode 100644 .todo/347045da-9152-4f26-b622-d0ee136c7b40/todos.json create mode 100644 .todo/34a2df39-85b1-4ed9-8a3e-34f56d54af64/todos.json create mode 100644 .todo/41ab6bdf-945b-425c-8371-6f56f7ba76d0/todos.json create mode 100644 .todo/47a8e2c7-1664-4543-b230-beaa5434928c/todos.json create mode 100644 .todo/4f7ca20a-5b75-47ae-85a8-131ccb45ebe3/todos.json create mode 100644 .todo/59064931-0eb6-44bb-a507-73ab6e47ab9f/todos.json create mode 100644 .todo/6579237b-6a42-4416-ad83-850b5860b33c/todos.json create mode 100644 .todo/7357498c-c6e9-43c5-95e5-a0d7e8ebe2a9/todos.json create mode 100644 .todo/749df53a-50ed-4cc7-90fa-83707e71b2bf/todos.json create mode 100644 .todo/7d5c44a7-611c-46f4-a2e1-290eacae62f5/todos.json create mode 100644 .todo/92c47652-da34-40c6-bc61-d8e1ef7ddd1d/todos.json create mode 100644 .todo/996001fe-2c20-461e-9233-fb6ef3c9da3c/todos.json create mode 100644 .todo/b2daccc6-f74f-41b7-aafb-ddbf6739eaa3/todos.json create mode 100644 .todo/b7465073-2e3e-4d4f-b1be-94ce569b640e/todos.json create mode 100644 .todo/bade5c70-612b-440d-847c-f7312c88187f/todos.json create mode 100644 .todo/c495fd12-8fd0-47c2-9e5a-6fe7782d386f/todos.json create mode 100644 .todo/c63f2258-1a62-4039-b263-f4e9501ce51f/todos.json create mode 100644 .todo/cf7031fa-bf57-44a6-b961-008a2c550470/todos.json create mode 100644 .todo/d83d49e4-ff14-4373-bff0-a2a0f09d04f1/todos.json create mode 100644 .todo/dbcebc28-e199-47a0-b809-f8b84a38513b/todos.json create mode 100644 .todo/dd1e01c7-7aa6-45fd-84b7-b9dc727a7b42/todos.json create mode 100644 .todo/ee057eda-9766-4be1-82a2-181874851cc5/todos.json create mode 100644 .todo/f4105905-3cab-4ede-a5be-885cae061d16/todos.json create mode 100644 .todo/fcb48bf9-bcf9-4a85-9e32-eb3bcae83c1f/todos.json create mode 100644 AGENTS.md create mode 100644 assets/CLAUDE.md create mode 100644 assets/入库流程图.docx create mode 100644 assets/微信图片_20260525195238_49_125.jpg create mode 100644 e2e/auth.setup.ts create mode 100644 e2e/collaboration-workflow.spec.ts create mode 100644 e2e/core-workflow.spec.ts create mode 100644 functions/data-consistency-check/index.ts create mode 100644 functions/fabric-warehouse-inbound/index.ts create mode 100644 index.html create mode 100644 jest.config.js create mode 100644 migrations/20260523_052639_create_base_tables.sql create mode 100644 migrations/20260523_052709_create_plan_tables.sql create mode 100644 migrations/20260523_052735_create_warehouse_tables.sql create mode 100644 migrations/20260523_052800_create_process_and_payment_tables.sql create mode 100644 migrations/20260523_052842_enable_rls_all_tables.sql create mode 100644 migrations/20260523_052936_create_test_users.sql create mode 100644 migrations/20260523_053030_seed_companies_and_profiles.sql create mode 100644 migrations/20260523_053110_seed_plans_and_related.sql create mode 100644 migrations/20260523_124703_add_company_id_to_inventory.sql create mode 100644 migrations/20260523_124742_create_product_yarn_ratios_table.sql create mode 100644 migrations/20260523_131935_add_production_price_to_products.sql create mode 100644 migrations/20260523_151243_add_rolls_to_inventory_records.sql create mode 100644 migrations/20260523_162407_drop_old_rls_policies.sql create mode 100644 migrations/20260523_162428_create_helper_function_1.sql create mode 100644 migrations/20260523_162436_create_helper_function_2.sql create mode 100644 migrations/20260523_162443_create_companies_rls.sql create mode 100644 migrations/20260523_162456_create_profiles_rls.sql create mode 100644 migrations/20260523_162514_create_production_plans_rls.sql create mode 100644 migrations/20260523_162530_create_plan_factories_rls.sql create mode 100644 migrations/20260523_162547_create_warehouse_inventory_rls.sql create mode 100644 migrations/20260523_162604_create_yarn_rls.sql create mode 100644 migrations/20260523_162626_create_payments_process_rls.sql create mode 100644 migrations/20260523_162653_drop_remaining_old_policies.sql create mode 100644 migrations/20260523_162700_fix_profiles_rls.sql create mode 100644 migrations/20260523_162833_add_company_id_to_inventory_records.sql create mode 100644 migrations/20260523_162846_add_company_id_to_yarn_ratios.sql create mode 100644 migrations/20260523_162859_add_company_id_to_plan_process_steps.sql create mode 100644 migrations/20260523_162913_update_inventory_records_rls.sql create mode 100644 migrations/20260523_162926_update_yarn_ratios_rls.sql create mode 100644 migrations/20260523_162941_update_plan_process_steps_rls.sql create mode 100644 migrations/20260523_163000_sync_company_id_data.sql create mode 100644 migrations/20260523_163054_add_share_link_rls_policies.sql create mode 100644 migrations/20260523_163116_fix_share_link_rls_v2.sql create mode 100644 migrations/20260523_163245_add_missing_rls_policies.sql create mode 100644 migrations/20260523_163255_fix_inventory_records_rls.sql create mode 100644 migrations/20260523_163429_fix_plan_factories_rls.sql create mode 100644 migrations/20260523_163537_fix_plan_process_steps_rls.sql create mode 100644 migrations/20260523_163644_fix_select_policies_security.sql create mode 100644 migrations/20260523_164422_remove_duplicate_factory_links.sql create mode 100644 migrations/20260523_164431_add_unique_constraint_plan_factories.sql create mode 100644 migrations/20260523_165555_create_audit_logs_table.sql create mode 100644 migrations/20260523_165857_fix_inventory_insert_rls.sql create mode 100644 migrations/20260523_165900_fix_payments_insert_rls.sql create mode 100644 migrations/20260523_165901_fix_yarn_ratios_delete_rls.sql create mode 100644 migrations/20260523_165913_fix_process_steps_select_rls.sql create mode 100644 migrations/20260523_170000_create_audit_logs_table.sql create mode 100644 migrations/20260523_170036_fix_yarn_ratios_select_rls.sql create mode 100644 migrations/20260523_191341_create_notifications_table.sql create mode 100644 migrations/20260523_191350_add_notifications_rls.sql create mode 100644 migrations/20260524_001_add_rolls_to_inventory_records.sql create mode 100644 migrations/20260524_002_create_accounts_payable.sql create mode 100644 migrations/20260524_003_add_price_to_inventory.sql create mode 100644 migrations/20260524_004_add_accounts_payable_rls.sql create mode 100644 migrations/20260524_005_create_price_history.sql create mode 100644 migrations/20260524_006_create_product_price_history.sql create mode 100644 migrations/20260524_054617_create_accounts_payable.sql create mode 100644 migrations/20260524_054623_add_price_to_inventory.sql create mode 100644 migrations/20260524_054637_add_accounts_payable_rls.sql create mode 100644 migrations/20260524_055450_create_price_history.sql create mode 100644 migrations/20260524_055840_create_product_price_history.sql create mode 100644 migrations/20260524_100730_add_display_name_to_profiles.sql create mode 100644 migrations/20260524_144737_create_washing_plans_table.sql create mode 100644 migrations/20260524_153210_create_product_outbound_records_table.sql create mode 100644 migrations/20260524_155028_fix_plan_factories_insert_policy.sql create mode 100644 migrations/20260524_155840_create_finished_products_table.sql create mode 100644 migrations/20260524_155925_create_washing_plan_completions_table.sql create mode 100644 migrations/20260524_155928_create_finished_product_inventory_records_table.sql create mode 100644 migrations/20260524_160012_create_auto_import_function_v2.sql create mode 100644 migrations/20260524_160018_create_auto_import_trigger.sql create mode 100644 migrations/20260524_163600_create_washing_process_steps.sql create mode 100644 migrations/20260524_165641_fix_washing_plans_rls.sql create mode 100644 migrations/20260524_165658_fix_auto_import_function.sql create mode 100644 migrations/20260524_165708_fix_finished_products_rls.sql create mode 100644 migrations/20260524_170000_fix_washing_plans_rls.sql create mode 100644 migrations/20260524_170001_fix_auto_import_function.sql create mode 100644 migrations/20260524_170002_fix_finished_products_rls.sql create mode 100644 migrations/20260525_060503_add_product_image_support.sql create mode 100644 migrations/20260525_060536_add_product_images_rls.sql create mode 100644 outputs/demo-animation-guide.md create mode 100644 outputs/demo-files.zip create mode 100644 outputs/textile-system-demo.excalidraw create mode 100644 package.json create mode 100644 playwright.config.ts create mode 100644 pnpm-lock.yaml create mode 100644 postcss.config.js create mode 100644 public/index.html create mode 100644 skills/excalidraw-diagram-generator/.skill-version create mode 100644 skills/excalidraw-diagram-generator/SKILL.md create mode 100644 skills/excalidraw-diagram-generator/references/element-types.md create mode 100644 skills/excalidraw-diagram-generator/references/excalidraw-schema.md create mode 100644 skills/excalidraw-diagram-generator/scripts/.gitignore create mode 100644 skills/excalidraw-diagram-generator/scripts/README.md create mode 100644 skills/excalidraw-diagram-generator/scripts/add-arrow.py create mode 100644 skills/excalidraw-diagram-generator/scripts/add-icon-to-diagram.py create mode 100644 skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py create mode 100644 skills/excalidraw-diagram-generator/templates/business-flow-swimlane-template.excalidraw create mode 100644 skills/excalidraw-diagram-generator/templates/class-diagram-template.excalidraw create mode 100644 skills/excalidraw-diagram-generator/templates/data-flow-diagram-template.excalidraw create mode 100644 skills/excalidraw-diagram-generator/templates/er-diagram-template.excalidraw create mode 100644 skills/excalidraw-diagram-generator/templates/flowchart-template.excalidraw create mode 100644 skills/excalidraw-diagram-generator/templates/mindmap-template.excalidraw create mode 100644 skills/excalidraw-diagram-generator/templates/relationship-template.excalidraw create mode 100644 skills/excalidraw-diagram-generator/templates/sequence-diagram-template.excalidraw create mode 100644 skills/meoo-cloud/.skill-version create mode 100644 skills/meoo-cloud/SKILL.md create mode 100644 skills/meoo-cloud/references/authentication.md create mode 100644 skills/meoo-cloud/references/database.md create mode 100644 skills/meoo-cloud/references/edge-functions.md create mode 100644 skills/meoo-cloud/references/realtime.md create mode 100644 skills/meoo-cloud/references/storage.md create mode 100644 skills/meoo-cloud/references/troubleshooting.md create mode 100644 skills/mobile-dev/.skill-version create mode 100644 skills/mobile-dev/SKILL.md create mode 100644 skills/react-project/.skill-version create mode 100644 skills/react-project/SKILL.md create mode 100644 src/App.tsx create mode 100644 src/__tests__/hooks/useNetworkStatus.test.ts create mode 100644 src/__tests__/hooks/useResponsive.test.ts create mode 100644 src/__tests__/hooks/useTheme.test.ts create mode 100644 src/__tests__/setup.ts create mode 100644 src/__tests__/utils/helpers.test.ts create mode 100644 src/components/ComingSoonModal.tsx create mode 100644 src/components/DemoDisclaimerModal.tsx create mode 100644 src/components/DemoWatermark.tsx create mode 100644 src/components/ErrorBoundary.tsx create mode 100644 src/components/InventoryRecordsModal.tsx create mode 100644 src/components/NetworkStatusBar.tsx create mode 100644 src/components/NotificationCenter.tsx create mode 100644 src/components/OnboardingGuide.tsx create mode 100644 src/components/PageTransition.tsx create mode 100644 src/components/PlanCard.tsx create mode 100644 src/components/ProcessFlow.tsx create mode 100644 src/components/ProgressBar.tsx create mode 100644 src/components/StatusBadge.tsx create mode 100644 src/components/VirtualList.tsx create mode 100644 src/components/plans/PlanEditModal.tsx create mode 100644 src/components/plans/PlanGroup.tsx create mode 100644 src/components/plans/ShareModal.tsx create mode 100644 src/components/warehouse/InboundForm.tsx create mode 100644 src/components/warehouse/InboundRecords.tsx create mode 100644 src/components/warehouse/OutboundForm.tsx create mode 100644 src/components/warehouse/ProductForm.tsx create mode 100644 src/components/warehouse/ProductList.tsx create mode 100644 src/components/warehouse/WarehouseNav.tsx create mode 100644 src/contexts/AuthContext.tsx create mode 100644 src/engine/processEngine.ts create mode 100644 src/hooks/useInventoryData.ts create mode 100644 src/hooks/useNetworkStatus.ts create mode 100644 src/hooks/usePlanData.ts create mode 100644 src/hooks/useRealtime.ts create mode 100644 src/hooks/useResponsive.ts create mode 100644 src/hooks/useTheme.ts create mode 100644 src/hooks/useVirtualScroll.ts create mode 100644 src/index.tsx create mode 100644 src/pages/DemoDataSharing.tsx create mode 100644 src/pages/ImportPlanPage.tsx create mode 100644 src/pages/LoginPage.tsx create mode 100644 src/pages/MemberManage.tsx create mode 100644 src/pages/RegisterPage.tsx create mode 100644 src/pages/RoleSelectPage.tsx create mode 100644 src/pages/purchaser/AccountsPayable.tsx create mode 100644 src/pages/purchaser/Dashboard.tsx create mode 100644 src/pages/purchaser/FactoryManage.tsx create mode 100644 src/pages/purchaser/FinishedWarehouse.tsx create mode 100644 src/pages/purchaser/NewPlan.tsx create mode 100644 src/pages/purchaser/NewWashingPlan.tsx create mode 100644 src/pages/purchaser/PlanOverview.tsx create mode 100644 src/pages/purchaser/WarehouseManage.tsx create mode 100644 src/pages/textile/Dashboard.tsx create mode 100644 src/pages/textile/FabricWarehouse.tsx create mode 100644 src/pages/textile/PaymentPending.tsx create mode 100644 src/pages/textile/PlanOverview.tsx create mode 100644 src/pages/textile/YarnWarehouse.tsx create mode 100644 src/pages/washing/CompletedFabric.tsx create mode 100644 src/pages/washing/Dashboard.tsx create mode 100644 src/pages/washing/FinishedWarehouse.tsx create mode 100644 src/pages/washing/PaymentPending.tsx create mode 100644 src/pages/washing/PendingFabric.tsx create mode 100644 src/pages/washing/PlanOverview.tsx create mode 100644 src/stores/index.ts create mode 100644 src/styles/index.css create mode 100644 src/supabase/client.ts create mode 100644 src/supabase/types.ts create mode 100644 src/types/index.ts create mode 100644 src/utils/constants.ts create mode 100644 src/utils/crypto.ts create mode 100644 src/utils/helpers.ts create mode 100644 src/utils/notifications.ts create mode 100644 src/utils/shareCrypto.ts create mode 100644 tailwind.config.js create mode 100644 tsconfig.json create mode 100644 webpack.config.js diff --git a/.assets_mapping b/.assets_mapping new file mode 100644 index 0000000..7e7b68c --- /dev/null +++ b/.assets_mapping @@ -0,0 +1,3 @@ +CLAUDE.md | 文档 | https://conversation.cdn.meoo.host/conversations/312782936418197504/file/2026-05-23/1779512821061-CLAUDE.md?auth_key=d58c5d879e91e544e0693fab745459373d600d09a97cc7811e0af491c03cbf51 | /home/project/assets/CLAUDE.md +入库流程图.docx | 文档 | https://conversation.cdn.meoo.host/conversations/312782936418197504/file/2026-05-23/1779512821057-入库流程图.docx?auth_key=800c9bb00af9dd353cdb91d1966494e87255c6ae7b7e21c6e226b58b224d89b6 | /home/project/assets/入库流程图.docx +微信图片_20260525195238_49_125.jpg | 图片 | https://conversation.cdn.meoo.host/conversations/312782936418197504/image/2026-05-25/1779711506188-微信图片_20260525195238_49_125.jpg?auth_key=55d04ced266880baebbf150fa6cb06fd546e28a2aca7fd99022b2b887e978d5f | /home/project/assets/微信图片_20260525195238_49_125.jpg diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..fa2393b --- /dev/null +++ b/.env.example @@ -0,0 +1,19 @@ +# Supabase 配置 +VITE_SUPABASE_URL=https://your-project.supabase.co +VITE_SUPABASE_ANON_KEY=your-anon-key + +# 应用配置 +VITE_APP_NAME=织机(Demo版) +VITE_APP_VERSION=1.0.0 +VITE_APP_ENV=development + +# 功能开关 +VITE_ENABLE_ANALYTICS=false +VITE_ENABLE_SENTRY=false + +# API 配置 +VITE_API_TIMEOUT=30000 +VITE_API_RETRY_COUNT=3 + +# 缓存配置 +VITE_CACHE_DURATION=5000 diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..270e322 --- /dev/null +++ b/.env.production @@ -0,0 +1,6 @@ +# 生产环境配置 +VITE_APP_ENV=production +VITE_ENABLE_ANALYTICS=true +VITE_ENABLE_SENTRY=true +VITE_API_TIMEOUT=60000 +VITE_CACHE_DURATION=10000 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8ecf2e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,106 @@ +# 通用 +*.log +*.tmp +*.temp +*.swp +*.swo +*.bak +*.orig + +# 环境变量与密钥 +.env +.env.local +.env.*.local +*.pem +*.key + +# 系统文件 +.DS_Store +Thumbs.db + +# 编辑器与 IDE +.idea/ +.vscode/ + +# Node.js / JavaScript / TypeScript +node_modules/ +dist/ +build/ +.next/ +.nuxt/ +.cache/ +coverage/ +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +.pnpm-store/ + +# Python +__pycache__/ +*.py[cod] +*.pyo +*.pyd +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.venv/ +venv/ +env/ +ENV/ +pip-wheel-metadata/ +*.egg-info/ +.coverage +htmlcov/ + +# Java / Kotlin +target/ +*.class +*.jar +*.war +*.ear +.gradle/ +out/ + +# Go +bin/ +*.test +coverage.out + +# C / C++ +*.o +*.obj +*.so +*.a +*.lib +*.dll +*.dylib +*.exe +CMakeFiles/ +CMakeCache.txt +cmake-build-*/ +compile_commands.json +Makefile + +# Rust +target/ + +# C# +*.user +*.suo +*.userosscache +*.sln.docstates +bin/ +obj/ + +# Ruby & PHP / Composer:`/vendor/` 仅忽略仓库根目录下的 vendor(不包含 src/pages/vendor/ 等) +.bundle/ +/vendor/ + +# OneDay 中间产物目录(文件处理时的临时文件,不需要持久化) +tmp/ + +# other +.stats +.config +.accesslog diff --git a/.gitkeep b/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..7549542 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmmirror.com diff --git a/.plan/34a2df39-85b1-4ed9-8a3e-34f56d54af64/plan.md b/.plan/34a2df39-85b1-4ed9-8a3e-34f56d54af64/plan.md new file mode 100644 index 0000000..a9e2a54 --- /dev/null +++ b/.plan/34a2df39-85b1-4ed9-8a3e-34f56d54af64/plan.md @@ -0,0 +1,74 @@ +# 分享链接导入并同步关联计划功能 - 实现计划 + +## 功能概述 + +采购商(布行)在计划总览页面可以为每个计划生成分享链接,发送给纺织厂/水洗厂。对方通过链接打开系统后,自动导入并关联该计划到自己的公司,实现跨角色协作。 + +## 核心流程 + +1. **采购商生成分享链接**:在计划总览页面,点击"分享链接"按钮,生成包含计划ID的URL(如 `#/import?plan=xxx&type=textile`) +2. **工厂方打开链接**:纺织厂/水洗厂用户收到链接后打开,系统识别URL参数,展示计划详情和导入确认页面 +3. **确认导入**:工厂方确认后,系统在 `plan_factories` 表中创建关联记录,将计划绑定到工厂公司 +4. **同步生效**:导入成功后,工厂方的工作台和计划总览自动显示该计划 + +## 实现步骤 + +### 步骤1:新增路由和导入页面 + +- 在 `App.tsx` 中添加路由:`/import` → `ImportPlanPage` +- 创建 `src/pages/ImportPlanPage.tsx`,处理URL参数中的 `plan` 和 `type` + +### 步骤2:采购商计划总览 - 生成分享链接 + +修改 `src/pages/purchaser/PlanOverview.tsx`: +- 将现有的"分享链接"按钮从工厂级别移到计划级别 +- 点击后生成链接:`#/import?plan={planId}&type={factoryType}` +- 显示复制链接的弹窗,支持一键复制 + +### 步骤3:创建 ImportPlanPage 导入确认页面 + +创建 `src/pages/ImportPlanPage.tsx`: +- 从URL参数读取 `plan`(计划ID)和 `type`(工厂类型:textile/washing) +- 检查用户是否已登录且角色匹配(纺织厂用户只能导入textile类型,水洗厂只能导入washing类型) +- 展示计划详情(产品名、颜色、坯布码、计划产量、纱线配比等) +- 提供"确认导入"按钮 +- 导入逻辑:向 `plan_factories` 表插入 `{plan_id, factory_id: 当前公司ID, factory_type: type}` +- 导入成功后跳转到对应工作台 + +### 步骤4:防重复导入 + +- 导入前检查 `plan_factories` 是否已存在相同关联 +- 如果已存在,提示"该计划已导入"并跳转到工作台 + +### 步骤5:更新 AGENTS.md 路由表 + +添加 `/import` 路由记录 + +## 涉及文件 + +| 文件 | 操作 | 说明 | +|------|------|------| +| `src/App.tsx` | 编辑 | 添加 `/import` 路由 | +| `src/pages/ImportPlanPage.tsx` | 新建 | 导入确认页面 | +| `src/pages/purchaser/PlanOverview.tsx` | 编辑 | 分享链接按钮改为计划级别,添加复制链接弹窗 | +| `AGENTS.md` | 编辑 | 添加路由记录 | + +## 数据库变更 + +无需新增表或字段。使用现有的 `plan_factories` 表存储关联关系。 + +## URL格式设计 + +``` +#/import?plan={planId}&type={textile|washing} +``` + +- `plan`: 生产计划的UUID +- `type`: 工厂类型,决定纺织厂还是水洗厂可以导入 + +## ImportPlanPage 页面设计 + +1. 未登录 → 提示登录并跳转到 `/login` +2. 已登录但角色不匹配 → 提示角色不匹配 +3. 已登录且角色匹配 → 展示计划详情 + 确认导入按钮 +4. 已导入 → 提示已导入 + 跳转工作台 \ No newline at end of file diff --git a/.todo/0589cf52-b6c9-4f19-af07-9eb17e004557/todos.json b/.todo/0589cf52-b6c9-4f19-af07-9eb17e004557/todos.json new file mode 100644 index 0000000..3923f1b --- /dev/null +++ b/.todo/0589cf52-b6c9-4f19-af07-9eb17e004557/todos.json @@ -0,0 +1,20 @@ +[ + { + "id": "1", + "content": "修改入库记录为生产记录", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "修改仓库管理为生产仓库管理", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "修复坯布入库状态显示为进行中", + "status": "completed", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/0810f58f-05ef-46ac-91b1-4c05f22a70f4/todos.json b/.todo/0810f58f-05ef-46ac-91b1-4c05f22a70f4/todos.json new file mode 100644 index 0000000..ccde22f --- /dev/null +++ b/.todo/0810f58f-05ef-46ac-91b1-4c05f22a70f4/todos.json @@ -0,0 +1,26 @@ +[ + { + "id": "1", + "content": "在 App.tsx 中添加 DemoWatermark 组件", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "在采购商工作台添加限时免费标签", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "在纺织厂工作台添加限时免费标签", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "在水洗厂工作台添加限时免费标签", + "status": "completed", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/09dd1b69-c113-4ca2-a83a-044bf9c00003/todos.json b/.todo/09dd1b69-c113-4ca2-a83a-044bf9c00003/todos.json new file mode 100644 index 0000000..9bbc0b7 --- /dev/null +++ b/.todo/09dd1b69-c113-4ca2-a83a-044bf9c00003/todos.json @@ -0,0 +1,26 @@ +[ + { + "id": "1", + "content": "修改坯布入库,仓库位置默认为工厂地址或名称", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "添加进度97%时弹出计划完成确认", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "实现完成后记录锁定不可更改", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "运行测试验证功能正常", + "status": "completed", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/09f94466-5d0a-457b-b49d-42af313a8245/todos.json b/.todo/09f94466-5d0a-457b-b49d-42af313a8245/todos.json new file mode 100644 index 0000000..821f612 --- /dev/null +++ b/.todo/09f94466-5d0a-457b-b49d-42af313a8245/todos.json @@ -0,0 +1,32 @@ +[ + { + "id": "1", + "content": "数据库迁移:为 products 表添加图片字段", + "status": "in_progress", + "priority": "high" + }, + { + "id": "2", + "content": "更新类型定义:ProductWithInventory 添加图片字段", + "status": "pending", + "priority": "high" + }, + { + "id": "3", + "content": "修改 ProductForm:添加图片上传功能", + "status": "pending", + "priority": "high" + }, + { + "id": "4", + "content": "修改 ProductList:显示产品图片", + "status": "pending", + "priority": "high" + }, + { + "id": "5", + "content": "修改 WarehouseManage:处理图片数据", + "status": "pending", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/0c6171a6-6db8-467a-80b3-94728c411830/todos.json b/.todo/0c6171a6-6db8-467a-80b3-94728c411830/todos.json new file mode 100644 index 0000000..f55c213 --- /dev/null +++ b/.todo/0c6171a6-6db8-467a-80b3-94728c411830/todos.json @@ -0,0 +1,32 @@ +[ + { + "id": "1", + "content": "修改 Product 类型添加 last_in_time 字段", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "修改 fetchData 查询最近入库时间", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "在移动端卡片显示入库时间", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "在桌面端表格显示入库时间", + "status": "completed", + "priority": "medium" + }, + { + "id": "5", + "content": "验证修改", + "status": "completed", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/116bc950-799c-4fc3-84b3-9dc8fe9c321c/todos.json b/.todo/116bc950-799c-4fc3-84b3-9dc8fe9c321c/todos.json new file mode 100644 index 0000000..2580398 --- /dev/null +++ b/.todo/116bc950-799c-4fc3-84b3-9dc8fe9c321c/todos.json @@ -0,0 +1,26 @@ +[ + { + "content": "创建子账号管理页面 MemberManage.tsx", + "id": "1", + "priority": "high", + "status": "completed" + }, + { + "content": "在 App.tsx 添加子账号管理路由", + "id": "2", + "priority": "high", + "status": "completed" + }, + { + "content": "在各角色 Dashboard 添加子账号管理入口", + "id": "3", + "priority": "high", + "status": "completed" + }, + { + "content": "类型检查和编译验证", + "id": "4", + "priority": "high", + "status": "completed" + } +] \ No newline at end of file diff --git a/.todo/1287da78-b83f-458a-bb81-b5389a8d9bf4/todos.json b/.todo/1287da78-b83f-458a-bb81-b5389a8d9bf4/todos.json new file mode 100644 index 0000000..731c145 --- /dev/null +++ b/.todo/1287da78-b83f-458a-bb81-b5389a8d9bf4/todos.json @@ -0,0 +1,20 @@ +[ + { + "id": "1", + "content": "统一纺织厂仪表台与采购商仪表台设计风格", + "status": "in_progress", + "priority": "high" + }, + { + "id": "2", + "content": "统一水洗厂仪表台与采购商仪表台设计风格", + "status": "pending", + "priority": "high" + }, + { + "id": "3", + "content": "验证修改后的页面运行正常", + "status": "pending", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/16757baf-4462-461a-8427-1c8f99592c3c/todos.json b/.todo/16757baf-4462-461a-8427-1c8f99592c3c/todos.json new file mode 100644 index 0000000..b66909b --- /dev/null +++ b/.todo/16757baf-4462-461a-8427-1c8f99592c3c/todos.json @@ -0,0 +1,14 @@ +[ + { + "id": "1", + "content": "更新 AGENTS.md 文档", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "更新 README.md 文档", + "status": "completed", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/17d26d13-5ca3-4b05-86c1-24a8b4c04f6f/todos.json b/.todo/17d26d13-5ca3-4b05-86c1-24a8b4c04f6f/todos.json new file mode 100644 index 0000000..94a0712 --- /dev/null +++ b/.todo/17d26d13-5ca3-4b05-86c1-24a8b4c04f6f/todos.json @@ -0,0 +1,20 @@ +[ + { + "id": "1", + "content": "修改纺织厂PlanOverview,在流程节点显示最近一次入库时间", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "修改采购商PlanOverview,显示入库进度和最近入库时间", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "更新ProcessFlow组件,支持显示入库时间", + "status": "completed", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/21334c3e-e7b7-44af-8a00-547aa2bc901e/todos.json b/.todo/21334c3e-e7b7-44af-8a00-547aa2bc901e/todos.json new file mode 100644 index 0000000..69018a8 --- /dev/null +++ b/.todo/21334c3e-e7b7-44af-8a00-547aa2bc901e/todos.json @@ -0,0 +1,20 @@ +[ + { + "id": "1", + "content": "创建采购商成品仓库页面", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "添加路由配置", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "更新采购商Dashboard导航", + "status": "completed", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/229c2dad-a2d1-40fe-9472-aa756ad3bcaa/todos.json b/.todo/229c2dad-a2d1-40fe-9472-aa756ad3bcaa/todos.json new file mode 100644 index 0000000..d884236 --- /dev/null +++ b/.todo/229c2dad-a2d1-40fe-9472-aa756ad3bcaa/todos.json @@ -0,0 +1,20 @@ +[ + { + "id": "1", + "content": "修复采购商计划详情无法展开问题", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "修复仓库管理入库记录详情展示", + "status": "in_progress", + "priority": "high" + }, + { + "id": "3", + "content": "添加采购价变动记录功能", + "status": "pending", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/2ab0661d-b5c8-41b8-8e7b-80adcd21c257/todos.json b/.todo/2ab0661d-b5c8-41b8-8e7b-80adcd21c257/todos.json new file mode 100644 index 0000000..1591c7f --- /dev/null +++ b/.todo/2ab0661d-b5c8-41b8-8e7b-80adcd21c257/todos.json @@ -0,0 +1,38 @@ +[ + { + "id": "1", + "content": "重构水洗厂 Dashboard 页面,添加快捷操作、导入关联计划、水洗记录时间戳", + "status": "in_progress", + "priority": "high" + }, + { + "id": "2", + "content": "创建水洗厂待处理坯布页面", + "status": "pending", + "priority": "high" + }, + { + "id": "3", + "content": "创建水洗厂已完成坯布页面(含水洗时间、缩水率记录)", + "status": "pending", + "priority": "high" + }, + { + "id": "4", + "content": "创建水洗厂待结款页面", + "status": "pending", + "priority": "high" + }, + { + "id": "5", + "content": "更新 App.tsx 添加新路由", + "status": "pending", + "priority": "high" + }, + { + "id": "6", + "content": "验证构建和运行", + "status": "pending", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/2bb16aa0-0012-4d1d-9856-fb883e451936/todos.json b/.todo/2bb16aa0-0012-4d1d-9856-fb883e451936/todos.json new file mode 100644 index 0000000..94cc261 --- /dev/null +++ b/.todo/2bb16aa0-0012-4d1d-9856-fb883e451936/todos.json @@ -0,0 +1,26 @@ +[ + { + "id": "1", + "content": "将分享链接加密改为 JWT 标准方案", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "添加 JWT token 过期时间支持", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "优化实时订阅,合并订阅通道", + "status": "in_progress", + "priority": "high" + }, + { + "id": "4", + "content": "验证修改后代码运行正常", + "status": "pending", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/2f822c41-126f-49ca-a958-de257d6d0ed4/todos.json b/.todo/2f822c41-126f-49ca-a958-de257d6d0ed4/todos.json new file mode 100644 index 0000000..07a8673 --- /dev/null +++ b/.todo/2f822c41-126f-49ca-a958-de257d6d0ed4/todos.json @@ -0,0 +1,38 @@ +[ + { + "id": "1", + "content": "创建成品仓库表 finished_products 和 finished_inventory_records", + "status": "in_progress", + "priority": "high" + }, + { + "id": "2", + "content": "创建水洗厂完成计划页面 WashingPlanComplete", + "status": "pending", + "priority": "high" + }, + { + "id": "3", + "content": "实现水洗完成自动导入成品仓库逻辑", + "status": "pending", + "priority": "high" + }, + { + "id": "4", + "content": "创建成品仓库管理页面 FinishedWarehouse", + "status": "pending", + "priority": "medium" + }, + { + "id": "5", + "content": "更新路由和导航", + "status": "pending", + "priority": "medium" + }, + { + "id": "6", + "content": "验证类型检查和功能测试", + "status": "pending", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/347045da-9152-4f26-b622-d0ee136c7b40/todos.json b/.todo/347045da-9152-4f26-b622-d0ee136c7b40/todos.json new file mode 100644 index 0000000..b78e84d --- /dev/null +++ b/.todo/347045da-9152-4f26-b622-d0ee136c7b40/todos.json @@ -0,0 +1,44 @@ +[ + { + "content": "重写 LoginPage 对接 Supabase 认证", + "id": "2b", + "priority": "high", + "status": "completed" + }, + { + "content": "重写 RoleSelectPage 对接真实数据", + "id": "2c", + "priority": "high", + "status": "completed" + }, + { + "content": "重写采购商首页对接真实数据", + "id": "3", + "priority": "high", + "status": "in_progress" + }, + { + "content": "重写计划总览/新建计划对接真实数据", + "id": "4", + "priority": "high", + "status": "pending" + }, + { + "content": "重写纺织厂/水洗厂页面对接真实数据", + "id": "5", + "priority": "high", + "status": "pending" + }, + { + "content": "重写仓库/工厂管理对接真实数据", + "id": "6", + "priority": "high", + "status": "pending" + }, + { + "content": "类型检查和编译验证", + "id": "7", + "priority": "high", + "status": "pending" + } +] \ No newline at end of file diff --git a/.todo/34a2df39-85b1-4ed9-8a3e-34f56d54af64/todos.json b/.todo/34a2df39-85b1-4ed9-8a3e-34f56d54af64/todos.json new file mode 100644 index 0000000..f633ea2 --- /dev/null +++ b/.todo/34a2df39-85b1-4ed9-8a3e-34f56d54af64/todos.json @@ -0,0 +1,38 @@ +[ + { + "content": "优化 LoginPage 桌面端布局", + "id": "d1", + "priority": "high", + "status": "completed" + }, + { + "content": "优化 RoleSelectPage 桌面端布局", + "id": "d2", + "priority": "high", + "status": "in_progress" + }, + { + "content": "优化各 Dashboard 桌面端布局", + "id": "d3", + "priority": "high", + "status": "pending" + }, + { + "content": "优化 PlanOverview 桌面端布局", + "id": "d4", + "priority": "high", + "status": "pending" + }, + { + "content": "优化 ImportPlanPage 桌面端布局", + "id": "d5", + "priority": "medium", + "status": "pending" + }, + { + "content": "类型检查和编译验证", + "id": "d6", + "priority": "high", + "status": "pending" + } +] \ No newline at end of file diff --git a/.todo/41ab6bdf-945b-425c-8371-6f56f7ba76d0/todos.json b/.todo/41ab6bdf-945b-425c-8371-6f56f7ba76d0/todos.json new file mode 100644 index 0000000..ee67fa8 --- /dev/null +++ b/.todo/41ab6bdf-945b-425c-8371-6f56f7ba76d0/todos.json @@ -0,0 +1,20 @@ +[ + { + "id": "1", + "content": "修复采购商计划详情无法展开问题", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "修复仓库管理入库记录详情展示", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "添加采购价变动记录功能", + "status": "completed", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/47a8e2c7-1664-4543-b230-beaa5434928c/todos.json b/.todo/47a8e2c7-1664-4543-b230-beaa5434928c/todos.json new file mode 100644 index 0000000..0c498a2 --- /dev/null +++ b/.todo/47a8e2c7-1664-4543-b230-beaa5434928c/todos.json @@ -0,0 +1,32 @@ +[ + { + "id": "1", + "content": "PlanOverview.tsx 添加分页查询(limit/offset)", + "status": "in_progress", + "priority": "high" + }, + { + "id": "2", + "content": "WarehouseManage.tsx 添加分页查询", + "status": "pending", + "priority": "high" + }, + { + "id": "3", + "content": "添加分页UI组件(页码/加载更多)", + "status": "pending", + "priority": "medium" + }, + { + "id": "4", + "content": "优化Realtime订阅,改为增量更新", + "status": "pending", + "priority": "medium" + }, + { + "id": "5", + "content": "运行类型检查和测试", + "status": "pending", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/4f7ca20a-5b75-47ae-85a8-131ccb45ebe3/todos.json b/.todo/4f7ca20a-5b75-47ae-85a8-131ccb45ebe3/todos.json new file mode 100644 index 0000000..b8cd3e0 --- /dev/null +++ b/.todo/4f7ca20a-5b75-47ae-85a8-131ccb45ebe3/todos.json @@ -0,0 +1,32 @@ +[ + { + "id": "1", + "status": "completed", + "content": "修改 Product 类型添加 last_in_time 字段", + "priority": "high" + }, + { + "id": "2", + "status": "completed", + "content": "修改 fetchData 查询最近入库时间", + "priority": "high" + }, + { + "id": "3", + "status": "completed", + "content": "在移动端卡片显示入库时间", + "priority": "high" + }, + { + "id": "4", + "status": "completed", + "content": "在桌面端表格显示入库时间", + "priority": "medium" + }, + { + "id": "5", + "status": "completed", + "content": "验证修改", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/59064931-0eb6-44bb-a507-73ab6e47ab9f/todos.json b/.todo/59064931-0eb6-44bb-a507-73ab6e47ab9f/todos.json new file mode 100644 index 0000000..854d0a1 --- /dev/null +++ b/.todo/59064931-0eb6-44bb-a507-73ab6e47ab9f/todos.json @@ -0,0 +1,26 @@ +[ + { + "id": "1", + "content": "创建通知数据库表和RLS策略", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "创建通知组件和通知中心", + "status": "in_progress", + "priority": "high" + }, + { + "id": "3", + "content": "在流程节点完成时发送通知", + "status": "pending", + "priority": "high" + }, + { + "id": "4", + "content": "在Dashboard添加通知入口", + "status": "pending", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/6579237b-6a42-4416-ad83-850b5860b33c/todos.json b/.todo/6579237b-6a42-4416-ad83-850b5860b33c/todos.json new file mode 100644 index 0000000..329c781 --- /dev/null +++ b/.todo/6579237b-6a42-4416-ad83-850b5860b33c/todos.json @@ -0,0 +1,44 @@ +[ + { + "id": "1", + "content": "为 inventory_records 表添加 company_id 字段", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "为 yarn_ratios 表添加 company_id 字段", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "为 plan_process_steps 表添加 company_id 字段", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "更新 inventory_records 的 RLS 策略", + "status": "completed", + "priority": "high" + }, + { + "id": "5", + "content": "更新 yarn_ratios 的 RLS 策略", + "status": "completed", + "priority": "high" + }, + { + "id": "6", + "content": "更新 plan_process_steps 的 RLS 策略", + "status": "completed", + "priority": "high" + }, + { + "id": "7", + "content": "同步更新现有数据的 company_id", + "status": "completed", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/7357498c-c6e9-43c5-95e5-a0d7e8ebe2a9/todos.json b/.todo/7357498c-c6e9-43c5-95e5-a0d7e8ebe2a9/todos.json new file mode 100644 index 0000000..93d199a --- /dev/null +++ b/.todo/7357498c-c6e9-43c5-95e5-a0d7e8ebe2a9/todos.json @@ -0,0 +1,14 @@ +[ + { + "content": "修改 FactoryManage 添加新建工厂按钮和弹窗", + "id": "1", + "priority": "high", + "status": "in_progress" + }, + { + "content": "类型检查和编译验证", + "id": "2", + "priority": "high", + "status": "pending" + } +] \ No newline at end of file diff --git a/.todo/749df53a-50ed-4cc7-90fa-83707e71b2bf/todos.json b/.todo/749df53a-50ed-4cc7-90fa-83707e71b2bf/todos.json new file mode 100644 index 0000000..307d390 --- /dev/null +++ b/.todo/749df53a-50ed-4cc7-90fa-83707e71b2bf/todos.json @@ -0,0 +1,20 @@ +[ + { + "id": "1", + "content": "修改 WarehouseManage.tsx 数据获取逻辑,按工厂分组汇总", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "修改入库记录展示,按工厂分组显示", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "更新产品库存统计,显示各工厂明细", + "status": "completed", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/7d5c44a7-611c-46f4-a2e1-290eacae62f5/todos.json b/.todo/7d5c44a7-611c-46f4-a2e1-290eacae62f5/todos.json new file mode 100644 index 0000000..6b2b9b5 --- /dev/null +++ b/.todo/7d5c44a7-611c-46f4-a2e1-290eacae62f5/todos.json @@ -0,0 +1,26 @@ +[ + { + "id": "1", + "content": "创建水洗厂计划总览页面 WashingPlanOverview.tsx", + "status": "in_progress", + "priority": "high" + }, + { + "id": "2", + "content": "创建水洗厂仓库管理页面 WashingWarehouse.tsx", + "status": "pending", + "priority": "high" + }, + { + "id": "3", + "content": "更新 App.tsx 添加水洗厂路由", + "status": "pending", + "priority": "high" + }, + { + "id": "4", + "content": "更新 Dashboard.tsx 修正快捷操作路径", + "status": "pending", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/92c47652-da34-40c6-bc61-d8e1ef7ddd1d/todos.json b/.todo/92c47652-da34-40c6-bc61-d8e1ef7ddd1d/todos.json new file mode 100644 index 0000000..dc6b922 --- /dev/null +++ b/.todo/92c47652-da34-40c6-bc61-d8e1ef7ddd1d/todos.json @@ -0,0 +1,32 @@ +[ + { + "id": "1", + "content": "优化登录页移动端适配", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "优化仪表盘移动端适配", + "status": "in_progress", + "priority": "high" + }, + { + "id": "3", + "content": "优化计划总览移动端适配", + "status": "pending", + "priority": "high" + }, + { + "id": "4", + "content": "优化仓库管理移动端适配", + "status": "pending", + "priority": "high" + }, + { + "id": "5", + "content": "验证所有修改", + "status": "pending", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/996001fe-2c20-461e-9233-fb6ef3c9da3c/todos.json b/.todo/996001fe-2c20-461e-9233-fb6ef3c9da3c/todos.json new file mode 100644 index 0000000..adb87bd --- /dev/null +++ b/.todo/996001fe-2c20-461e-9233-fb6ef3c9da3c/todos.json @@ -0,0 +1,38 @@ +[ + { + "id": "1", + "content": "读取存储相关文档,了解图片上传实现方式", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "查看现有产品管理相关代码和数据结构", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "创建数据库迁移 - 添加产品图片字段和存储桶", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "修改产品列表组件 - 添加图片展示和上传功能", + "status": "completed", + "priority": "high" + }, + { + "id": "5", + "content": "修改产品表单组件 - 添加图片上传功能", + "status": "completed", + "priority": "high" + }, + { + "id": "6", + "content": "验证功能并运行测试", + "status": "completed", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/b2daccc6-f74f-41b7-aafb-ddbf6739eaa3/todos.json b/.todo/b2daccc6-f74f-41b7-aafb-ddbf6739eaa3/todos.json new file mode 100644 index 0000000..80f2fef --- /dev/null +++ b/.todo/b2daccc6-f74f-41b7-aafb-ddbf6739eaa3/todos.json @@ -0,0 +1,32 @@ +[ + { + "id": "1", + "content": "修复 useTheme hook 类型定义", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "修复测试文件类型错误", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "修复 ImportPlanPage 类型错误", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "修复 MemberManage 类型错误", + "status": "completed", + "priority": "high" + }, + { + "id": "5", + "content": "验证修复结果", + "status": "completed", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/b7465073-2e3e-4d4f-b1be-94ce569b640e/todos.json b/.todo/b7465073-2e3e-4d4f-b1be-94ce569b640e/todos.json new file mode 100644 index 0000000..de29142 --- /dev/null +++ b/.todo/b7465073-2e3e-4d4f-b1be-94ce569b640e/todos.json @@ -0,0 +1,56 @@ +[ + { + "id": "1", + "content": "读取数据库参考文档,了解RLS策略规范", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "创建成品仓库表 finished_products 及相关表结构", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "创建水洗计划完成记录表 washing_plan_completions", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "创建自动导入成品仓库的数据库函数", + "status": "completed", + "priority": "high" + }, + { + "id": "5", + "content": "更新类型定义文件", + "status": "completed", + "priority": "high" + }, + { + "id": "6", + "content": "创建水洗厂计划完成页面UI", + "status": "completed", + "priority": "medium" + }, + { + "id": "7", + "content": "创建成品仓库管理页面", + "status": "completed", + "priority": "medium" + }, + { + "id": "8", + "content": "更新路由配置", + "status": "completed", + "priority": "medium" + }, + { + "id": "9", + "content": "验证功能并运行测试", + "status": "completed", + "priority": "low" + } +] \ No newline at end of file diff --git a/.todo/bade5c70-612b-440d-847c-f7312c88187f/todos.json b/.todo/bade5c70-612b-440d-847c-f7312c88187f/todos.json new file mode 100644 index 0000000..53a529e --- /dev/null +++ b/.todo/bade5c70-612b-440d-847c-f7312c88187f/todos.json @@ -0,0 +1,38 @@ +[ + { + "id": "1", + "content": "读取 meoo-cloud Edge Functions 参考文档", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "创建入库事务处理 Edge Function", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "创建数据校验修复 Edge Function", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "修改前端入库逻辑调用 Edge Function", + "status": "completed", + "priority": "high" + }, + { + "id": "5", + "content": "部署 Edge Functions", + "status": "completed", + "priority": "high" + }, + { + "id": "6", + "content": "验证功能", + "status": "completed", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/c495fd12-8fd0-47c2-9e5a-6fe7782d386f/todos.json b/.todo/c495fd12-8fd0-47c2-9e5a-6fe7782d386f/todos.json new file mode 100644 index 0000000..ae3d23e --- /dev/null +++ b/.todo/c495fd12-8fd0-47c2-9e5a-6fe7782d386f/todos.json @@ -0,0 +1,38 @@ +[ + { + "id": "1", + "content": "查看纺织厂工作台当前代码结构", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "查看现有YarnWarehouse页面结构", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "创建原坯布仓库页面", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "修改纺织厂工作台添加两个仓库快捷操作", + "status": "completed", + "priority": "high" + }, + { + "id": "5", + "content": "添加路由配置", + "status": "completed", + "priority": "high" + }, + { + "id": "6", + "content": "验证所有修改", + "status": "completed", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/c63f2258-1a62-4039-b263-f4e9501ce51f/todos.json b/.todo/c63f2258-1a62-4039-b263-f4e9501ce51f/todos.json new file mode 100644 index 0000000..fcd622f --- /dev/null +++ b/.todo/c63f2258-1a62-4039-b263-f4e9501ce51f/todos.json @@ -0,0 +1,26 @@ +[ + { + "id": "1", + "content": "创建产品价格变更历史表", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "执行数据库迁移", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "修改产品编辑逻辑,记录价格变更", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "在产品详情中显示价格变更历史", + "status": "completed", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/cf7031fa-bf57-44a6-b961-008a2c550470/todos.json b/.todo/cf7031fa-bf57-44a6-b961-008a2c550470/todos.json new file mode 100644 index 0000000..786ccce --- /dev/null +++ b/.todo/cf7031fa-bf57-44a6-b961-008a2c550470/todos.json @@ -0,0 +1,20 @@ +[ + { + "id": "1", + "content": "更新 AGENTS.md 记录 webpack devServer 预览加载失败问题", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "更新 README.md 添加问题排查和解决方案", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "总结学习反思", + "status": "completed", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/d83d49e4-ff14-4373-bff0-a2a0f09d04f1/todos.json b/.todo/d83d49e4-ff14-4373-bff0-a2a0f09d04f1/todos.json new file mode 100644 index 0000000..97ef81f --- /dev/null +++ b/.todo/d83d49e4-ff14-4373-bff0-a2a0f09d04f1/todos.json @@ -0,0 +1,26 @@ +[ + { + "id": "1", + "content": "数据库迁移:profiles 表添加 display_name 字段", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "修改注册逻辑:存储用户名到 username,姓名到 display_name", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "修改账号管理:显示和编辑 display_name", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "修改首页副标题:引用 display_name 而非 username", + "status": "completed", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/dbcebc28-e199-47a0-b809-f8b84a38513b/todos.json b/.todo/dbcebc28-e199-47a0-b809-f8b84a38513b/todos.json new file mode 100644 index 0000000..f26bcc8 --- /dev/null +++ b/.todo/dbcebc28-e199-47a0-b809-f8b84a38513b/todos.json @@ -0,0 +1,38 @@ +[ + { + "id": "1", + "content": "创建产品定义相关数据库表(products, product_yarn_ratios, fabric_batches)", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "更新仓库管理页为三级菜单结构", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "实现产品管理功能(列表、新建、编辑)", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "实现原坯布入库功能(关联产品、记录批号和时间戳)", + "status": "completed", + "priority": "high" + }, + { + "id": "5", + "content": "更新路由配置", + "status": "completed", + "priority": "medium" + }, + { + "id": "6", + "content": "运行测试验证", + "status": "completed", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/dd1e01c7-7aa6-45fd-84b7-b9dc727a7b42/todos.json b/.todo/dd1e01c7-7aa6-45fd-84b7-b9dc727a7b42/todos.json new file mode 100644 index 0000000..a9082b0 --- /dev/null +++ b/.todo/dd1e01c7-7aa6-45fd-84b7-b9dc727a7b42/todos.json @@ -0,0 +1,56 @@ +[ + { + "id": "1", + "content": "检查 RLS 策略对数据查询的影响", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "检查 RLS 策略对数据插入的影响", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "检查 RLS 策略对数据更新的影响", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "修复 plan_process_steps 查询策略", + "status": "completed", + "priority": "high" + }, + { + "id": "5", + "content": "修复 inventory_records 插入策略", + "status": "completed", + "priority": "high" + }, + { + "id": "6", + "content": "修复 payments 插入策略", + "status": "completed", + "priority": "high" + }, + { + "id": "7", + "content": "修复 yarn_ratios 删除策略", + "status": "completed", + "priority": "high" + }, + { + "id": "8", + "content": "修复 plan_process_steps 查询策略", + "status": "completed", + "priority": "high" + }, + { + "id": "9", + "content": "验证修复后的功能", + "status": "completed", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/ee057eda-9766-4be1-82a2-181874851cc5/todos.json b/.todo/ee057eda-9766-4be1-82a2-181874851cc5/todos.json new file mode 100644 index 0000000..e92d0c9 --- /dev/null +++ b/.todo/ee057eda-9766-4be1-82a2-181874851cc5/todos.json @@ -0,0 +1,20 @@ +[ + { + "id": "1", + "content": "修改纺织厂PlanOverview,在流程节点显示最近一次入库时间", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "修改采购商PlanOverview,显示入库进度和最近入库时间", + "status": "in_progress", + "priority": "high" + }, + { + "id": "3", + "content": "更新ProcessFlow组件,支持显示入库时间", + "status": "completed", + "priority": "medium" + } +] \ No newline at end of file diff --git a/.todo/f4105905-3cab-4ede-a5be-885cae061d16/todos.json b/.todo/f4105905-3cab-4ede-a5be-885cae061d16/todos.json new file mode 100644 index 0000000..891f956 --- /dev/null +++ b/.todo/f4105905-3cab-4ede-a5be-885cae061d16/todos.json @@ -0,0 +1,20 @@ +[ + { + "id": "1", + "content": "修复导入计划页面支持水洗厂", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "添加水洗计划分享链接生成", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "验证数据同步机制", + "status": "in_progress", + "priority": "high" + } +] \ No newline at end of file diff --git a/.todo/fcb48bf9-bcf9-4a85-9e32-eb3bcae83c1f/todos.json b/.todo/fcb48bf9-bcf9-4a85-9e32-eb3bcae83c1f/todos.json new file mode 100644 index 0000000..a5b8a63 --- /dev/null +++ b/.todo/fcb48bf9-bcf9-4a85-9e32-eb3bcae83c1f/todos.json @@ -0,0 +1,32 @@ +[ + { + "id": "1", + "content": "阅读采购商工作台 Dashboard 和仓库管理相关代码", + "status": "completed", + "priority": "high" + }, + { + "id": "2", + "content": "设计水洗计划数据库表结构", + "status": "completed", + "priority": "high" + }, + { + "id": "3", + "content": "创建新建水洗计划页面组件", + "status": "completed", + "priority": "high" + }, + { + "id": "4", + "content": "添加路由配置", + "status": "completed", + "priority": "medium" + }, + { + "id": "5", + "content": "在 Dashboard 添加入口按钮", + "status": "completed", + "priority": "medium" + } +] \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..93cdb0e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,1800 @@ +# 采购计划系统 - AGENTS.md + +## 项目概述 + +纺织行业采购计划管理系统,支持三种角色(采购商/布行、纺织厂、水洗厂)的全流程协作管理。 + +## 系统架构 + +### 技术栈 + +| 层级 | 技术 | 说明 | +|------|------|------| +| 前端框架 | React 18 + TypeScript | 函数组件 + Hooks | +| 构建工具 | Webpack 5 | 开发服务器端口 3015 | +| 样式方案 | TailwindCSS 3 + PostCSS | 原子化 CSS | +| 动画库 | Framer Motion | 页面过渡、交互动画 | +| 图标库 | Lucide React | 现代化图标 | +| 图表库 | Recharts | 数据可视化 | +| 路由 | React Router v6 | HashRouter 模式 | +| 后端服务 | Meoo Cloud (Supabase) | PostgreSQL + Auth + RLS | +| 包管理 | pnpm | 依赖管理 | + +### 项目目录结构 + +``` +/home/project/ +├── AGENTS.md # 项目文档(本文件) +├── package.json # 项目依赖配置 +├── webpack.config.js # Webpack 配置 +├── tailwind.config.js # TailwindCSS 配置 +├── postcss.config.js # PostCSS 配置 +├── tsconfig.json # TypeScript 配置 +├── index.html # HTML 模板 +├── migrations/ # 数据库迁移文件 +│ ├── 20260523_052639_create_base_tables.sql +│ ├── 20260523_052709_create_plan_tables.sql +│ ├── 20260523_052735_create_warehouse_tables.sql +│ ├── 20260523_052800_create_process_and_payment_tables.sql +│ ├── 20260523_052842_enable_rls_all_tables.sql +│ ├── 20260523_052936_create_test_users.sql +│ ├── 20260523_053030_seed_companies_and_profiles.sql +│ ├── 20260523_053110_seed_plans_and_related.sql +│ ├── 20260523_124703_add_company_id_to_inventory.sql +│ ├── 20260523_124742_create_product_yarn_ratios_table.sql +│ └── 20260523_131935_add_production_price_to_products.sql +├── src/ +│ ├── index.tsx # 应用入口 +│ ├── App.tsx # 根组件(路由配置) +│ ├── styles/ +│ │ └── index.css # 全局样式 +│ ├── types/ +│ │ └── index.ts # 类型定义 +│ ├── supabase/ +│ │ ├── client.ts # Supabase 客户端(自动生成) +│ │ └── types.ts # 数据库类型(自动生成) +│ ├── utils/ +│ │ └── shareCrypto.ts # 分享链接加密工具 +│ ├── hooks/ +│ │ ├── useResponsive.ts # 响应式 Hook +│ │ └── useTheme.ts # 主题 Hook +│ ├── contexts/ +│ │ └── AuthContext.tsx # 认证上下文 +│ ├── components/ +│ │ ├── PlanCard.tsx # 计划卡片组件 +│ │ ├── ProcessFlow.tsx # 流程节点组件 +│ │ ├── ProgressBar.tsx # 进度条组件 +│ │ └── StatusBadge.tsx # 状态标签组件 +│ └── pages/ +│ ├── LoginPage.tsx # 登录页 +│ ├── RegisterPage.tsx # 注册页 +│ ├── RoleSelectPage.tsx # 角色选择页 +│ ├── MemberManage.tsx # 子账号管理页 +│ ├── ImportPlanPage.tsx # 导入计划页(分享链接) +│ ├── purchaser/ # 采购商页面 +│ │ ├── Dashboard.tsx +│ │ ├── PlanOverview.tsx +│ │ ├── NewPlan.tsx +│ │ ├── WarehouseManage.tsx +│ │ └── FactoryManage.tsx +│ ├── textile/ # 纺织厂页面 +│ │ ├── Dashboard.tsx +│ │ ├── PlanOverview.tsx +│ │ ├── YarnWarehouse.tsx +│ │ ├── FabricWarehouse.tsx +│ │ └── PaymentPending.tsx +│ └── washing/ # 水洗厂页面 +│ └── Dashboard.tsx +└── skills/ # 技能文档 + ├── meoo-cloud/ + ├── mobile-dev/ + └── react-project/ +``` + +### 重构后新增目录结构(2025-05-24) + +``` +src/ +├── components/ +│ ├── warehouse/ # 仓库管理组件 +│ │ ├── WarehouseNav.tsx # 仓库导航组件 +│ │ ├── ProductForm.tsx # 产品表单模态框 +│ │ ├── InboundForm.tsx # 入库表单模态框 +│ │ ├── ProductList.tsx # 产品列表组件(桌面+移动端) +│ │ └── InboundRecords.tsx # 入库记录列表 +│ └── plans/ # 计划管理组件 +│ ├── PlanGroup.tsx # 计划分组组件 +│ └── ShareModal.tsx # 分享弹窗组件 +├── hooks/ +│ ├── useInventoryData.ts # 库存数据获取Hook(带缓存) +│ └── usePlanData.ts # 计划数据获取Hook(带缓存) +└── utils/ + ├── constants.ts # 公共常量配置 + └── helpers.ts # 通用工具函数 +``` + +## 路由结构 + +| URL 路径 | 页面 | 说明 | +|----------|------|------| +| `/login` | LoginPage | 登录页 | +| `/role-select` | RoleSelectPage | 角色选择页 | +| `/purchaser` | PurchaserDashboard | 采购商首页 | +| `/purchaser/plans` | PlanOverview | 采购商计划总览 | +| `/purchaser/plans/new` | NewPlan | 新建纺织计划 | +| `/purchaser/warehouse` | WarehouseManage | 仓库管理 | +| `/purchaser/factories` | FactoryManage | 工厂管理 | +| `/textile` | TextileDashboard | 纺织厂首页 | +| `/textile/plans` | TextilePlanOverview | 纺织厂计划总览 | +| `/textile/yarn-warehouse` | YarnWarehouse | 原料纱仓库 | +| `/textile/payments` | PaymentPending | 待结款 | +| `/washing` | WashingDashboard | 水洗厂首页 | +| `/import` | ImportPlanPage | 通过分享链接导入关联计划 | + +## 数据安全 + +### RLS 策略设计 + +所有表启用 Row Level Security (RLS),基于公司 ID 进行数据隔离: + +| 表 | SELECT 策略 | 修改策略 | +|---|---|---| +| **companies** | 开放访问 | 仅本公司 | +| **profiles** | 本公司成员 | 仅自己 | +| **production_plans** | 采购商或关联工厂 | 仅采购商 | +| **plan_factories** | 开放访问 | 仅采购商 | +| **plan_process_steps** | 开放访问 | 采购商或关联工厂 | +| **inventory_records** | 相关方 | 仅本公司 | +| **yarn_ratios** | 开放访问 | 仅采购商 | +| **yarn_stock** | 仅本公司 | 仅本公司 | +| **warehouses** | 仅本公司 | 仅本公司 | +| **payments** | 付款方或收款方 | 相关方 | +| **products** | 仅本公司 | 仅本公司 | + +### 辅助函数 + +- `get_user_master_company_id()` - 获取当前用户(含子账号)的公司 ID + +## 数据架构 + +### ER 关系图 + +``` +auth.users (Supabase内置) + │ + ├── 1:1 ── profiles (用户配置) + │ │ + │ ├── N:1 ── companies (公司) + │ │ │ + │ │ ├── 1:N ── company_members (公司成员/子账号) + │ │ │ + │ │ ├── 1:N ── factories (工厂) + │ │ │ │ + │ │ │ ├── N:M ── production_plans (通过 plan_factories 关联) + │ │ │ + │ │ ├── 1:N ── warehouses (仓库) + │ │ │ │ + │ │ │ ├── 1:N ── inventory_records (库存记录) + │ │ │ + │ │ ├── 1:N ── yarn_stock (纱线库存) + │ │ + │ ├── 1:N ── production_plans (采购商创建的计划) + │ │ │ + │ │ ├── 1:N ── yarn_ratios (纱线配比) + │ │ │ + │ │ ├── 1:N ── plan_process_steps (流程节点) + │ │ │ + │ │ ├── N:M ── factories (通过 plan_factories 关联) + │ │ + │ ├── 1:N ── payments (结款记录) +``` + +### 表结构详细设计 + +#### 1. profiles - 用户配置表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID (PK) | 关联 auth.users.id | +| username | TEXT (UNIQUE) | 用户名 | +| phone | TEXT | 手机号 | +| company_id | UUID (FK) | 所属公司 | +| is_master | BOOLEAN | 是否主账号 | +| master_id | UUID (FK) | 主账号ID(子账号使用) | +| created_at | TIMESTAMPTZ | 创建时间 | + +#### 2. companies - 公司表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID (PK) | 公司ID | +| name | TEXT | 公司名称 | +| role | app_role | 公司类型:purchaser/textile/washing | +| address | TEXT | 公司地址 | +| contact_phone | TEXT | 联系电话 | +| created_at | TIMESTAMPTZ | 创建时间 | + +#### 3. company_members - 公司成员表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID (PK) | 成员记录ID | +| company_id | UUID (FK) | 公司ID | +| user_id | UUID (FK) | 用户ID | +| role | TEXT | 成员角色 | +| created_at | TIMESTAMPTZ | 加入时间 | + +#### 4. factories - 工厂表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID (PK) | 工厂ID | +| company_id | UUID (FK) | 所属公司ID | +| name | TEXT | 工厂名称 | +| type | factory_type | 工厂类型:textile/washing | +| address | TEXT | 工厂地址 | +| contact_person | TEXT | 联系人 | +| contact_phone | TEXT | 联系电话 | +| share_link | TEXT | 分享链接 | +| created_at | TIMESTAMPTZ | 创建时间 | + +#### 5. production_plans - 生产计划表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID (PK) | 计划ID | +| plan_code | TEXT (UNIQUE) | 系统自动生成计划唯一识别码 | +| product_name | TEXT | 成品名称 | +| color | TEXT | 颜色 | +| fabric_code | TEXT | 坯布唯一识别码(纯字母) | +| color_code | TEXT | 色号(数字排序) | +| remark | TEXT | 备注(60*40等) | +| purchaser_id | UUID (FK) | 采购商公司ID | +| target_quantity | INTEGER | 计划产量(米) | +| completed_quantity | INTEGER | 已完成产量(米) | +| yarn_usage_per_meter | DECIMAL | 每米布纱用量(g/m) | +| production_price | DECIMAL | 生产采购价(元/米) | +| status | plan_status | 状态:pending/producing/completed | +| start_time | TIMESTAMPTZ | 计划开始时间 | +| created_by | UUID (FK) | 创建人 | +| created_at | TIMESTAMPTZ | 创建时间 | +| updated_at | TIMESTAMPTZ | 更新时间 | + +#### 6. plan_factories - 计划与工厂关联表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID (PK) | 关联ID | +| plan_id | UUID (FK) | 计划ID | +| factory_id | UUID (FK) | 工厂ID | +| factory_type | factory_type | 工厂类型:textile/washing | +| created_at | TIMESTAMPTZ | 关联时间 | + +#### 7. yarn_ratios - 纱线配比表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID (PK) | 配比ID | +| plan_id | UUID (FK) | 计划ID | +| yarn_name | TEXT | 纱线名称 | +| ratio | DECIMAL | 配比比例 | +| amount_per_meter | DECIMAL | 每米用量(g/m) | +| total_amount | DECIMAL | 总用纱量 | +| created_at | TIMESTAMPTZ | 创建时间 | + +#### 8. plan_process_steps - 计划流程节点表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID (PK) | 节点ID | +| plan_id | UUID (FK) | 计划ID | +| step_type | step_type | 节点类型:confirm/yarn_purchase/dyeing/machine_start/fabric_warehouse | +| status | step_status | 状态:pending/active/completed | +| timestamp | TIMESTAMPTZ | 确认时间戳(精确到秒) | +| operator_id | UUID (FK) | 操作人 | +| notes | TEXT | 备注 | +| created_at | TIMESTAMPTZ | 创建时间 | + +#### 9. warehouses - 仓库表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID (PK) | 仓库ID | +| company_id | UUID (FK) | 所属公司ID | +| name | TEXT | 仓库名称 | +| type | warehouse_type | 类型:raw_fabric/fabric/finished/yarn | +| location | TEXT | 仓库位置 | +| created_at | TIMESTAMPTZ | 创建时间 | + +#### 10. inventory_records - 入库记录表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID (PK) | 记录ID | +| plan_id | UUID (FK) | 关联计划ID | +| warehouse_id | UUID (FK) | 仓库ID | +| quantity | DECIMAL | 入库米数 | +| warehouse_location | TEXT | 仓库位置 | +| operator_id | UUID (FK) | 操作人 | +| created_at | TIMESTAMPTZ | 入库时间 | + +#### 11. yarn_stock - 纱线库存表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID (PK) | 纱线ID | +| company_id | UUID (FK) | 所属公司ID | +| warehouse_id | UUID (FK) | 仓库ID | +| name | TEXT | 纱线名称 | +| spec | TEXT | 规格 | +| quantity | DECIMAL | 库存数量(kg) | +| min_stock | DECIMAL | 最低库存预警(kg) | +| unit | TEXT | 单位 | +| updated_at | TIMESTAMPTZ | 更新时间 | + +#### 12. payments - 结款记录表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID (PK) | 结款ID | +| plan_id | UUID (FK) | 关联计划ID | +| from_company_id | UUID (FK) | 付款方公司ID | +| to_company_id | UUID (FK) | 收款方公司ID | +| amount | DECIMAL | 结款金额 | +| quantity | DECIMAL | 结款数量(米) | +| price_per_meter | DECIMAL | 单价(元/米) | +| status | payment_status | 状态:pending/completed | +| paid_at | TIMESTAMPTZ | 结款时间 | +| created_at | TIMESTAMPTZ | 创建时间 | + +### 自定义枚举类型 + +```sql +-- 应用角色(公司类型) +CREATE TYPE app_role AS ENUM ('purchaser', 'textile', 'washing'); + +-- 工厂类型 +CREATE TYPE factory_type AS ENUM ('textile', 'washing'); + +-- 计划状态 +CREATE TYPE plan_status AS ENUM ('pending', 'producing', 'completed'); + +-- 流程节点类型 +CREATE TYPE step_type AS ENUM ('confirm', 'yarn_purchase', 'dyeing', 'machine_start', 'fabric_warehouse'); + +-- 流程节点状态 +CREATE TYPE step_status AS ENUM ('pending', 'active', 'completed'); + +-- 仓库类型 +CREATE TYPE warehouse_type AS ENUM ('raw_fabric', 'fabric', 'finished', 'yarn'); + +-- 结款状态 +CREATE TYPE payment_status AS ENUM ('pending', 'completed'); +``` + +## 技术栈 + +- 前端:React 18 + Webpack + TailwindCSS + Framer Motion + Lucide Icons +- 后端:Meoo Cloud (Supabase) - PostgreSQL + Auth + RLS +- 路由:HashRouter (react-router-dom v6) +- 包管理:pnpm + +## 开发规范 + +- 所有数据操作通过 Supabase client,禁止 mock 数据 +- RLS 策略命名:`__` 英文 snake_case +- 用户认证使用 `{username}@meoo.local` 虚拟邮箱 +- 操作结果必须通过 Toast 反馈给用户 +- UUID 作为所有表主键 + +## 账号安全策略 + +### 用户名唯一性 + +- **profiles 表 username 字段** 已设置数据库级唯一约束(UNIQUE) +- **注册时**:检查用户名是否已存在,返回具体错误提示 +- **子账号创建时**:同样进行全局用户名唯一性检查,避免与其他主账号或子账号冲突 +- **错误提示**:"该用户名已被使用,请更换其他用户名" + +## UI/UX 优化记录 + +### 2025-05-23 纺织厂工作台优化 + +#### 动画流畅度优化 +- **Framer Motion 动画参数优化**: + - 使用 `ease: [0.25, 0.46, 0.45, 0.94]` 替代默认 ease,提供更自然的动画曲线 + - 缩短动画时长(0.35s),减少用户等待感 + - 添加 `will-change-transform` 启用硬件加速 + - 优化 stagger 延迟(0.08s),避免动画过于密集 + +- **ProcessFlow 组件优化**: + - 当前步骤图标使用 `scale: [1, 1.15, 1]` 呼吸动画 + - 弹窗使用 `AnimatePresence` 实现平滑进出 + - 输入框展开/收起使用 `ease: [0.25, 0.46, 0.45, 0.94]` + +#### 移动端适配优化 +- **响应式断点**: + - 使用 `sm:`(640px)、`md:`(768px)断点 + - 字体大小:`text-[10px]`(移动端)→ `text-xs`(sm)→ `text-sm`(md) + - 间距:`p-3`(移动端)→ `sm:p-4` → `md:p-6` + +- **触控优化**: + - 按钮添加 `whileTap={{ scale: 0.98 }}` 点击反馈 + - 输入框添加 `touch-manipulation` 防止双击缩放 + - 输入框添加 `inputMode` 和 `pattern` 优化移动端键盘 + - 增大触控区域:`min-height: 44px`(CSS) + +- **布局适配**: + - 坯布入库输入框:移动端垂直排列(`flex-col`),桌面端水平排列(`sm:flex-row`) + - 统计卡片:3列网格,移动端缩小间距和字体 + - 表格:添加 `overflow-x-auto` 支持横向滚动 + +#### 实时数据更新 +- **FabricWarehouse 实时订阅**: + - 使用 Supabase Realtime 订阅 `inventory_records` 表 + - 入库操作后自动刷新,无需手动刷新页面 + - 组件卸载时自动取消订阅 + +#### 文件变更 +- `src/pages/textile/Dashboard.tsx` - 动画和移动端适配 +- `src/pages/textile/PlanOverview.tsx` - 动画和移动端适配 +- `src/pages/textile/YarnWarehouse.tsx` - 动画和移动端适配 +- `src/pages/textile/FabricWarehouse.tsx` - 动画、移动端适配、实时订阅 +- `src/pages/textile/PaymentPending.tsx` - 动画和移动端适配 +- `src/components/ProcessFlow.tsx` - 动画优化和移动端输入框 +- `src/styles/index.css` - 移动端触控优化CSS + +## 更新日志 + +### 2025-05-25 +- **产品图片上传功能** + - 为采购商原坯布仓库产品管理添加图片上传功能 + - 支持 JPG、PNG 格式图片上传,最大 5MB + - 产品列表展示缩略图,无图片时显示默认占位图标 + - 产品表单支持图片预览、重新上传和删除功能 + - 使用 Supabase Storage 存储图片文件 +- **数据库变更** + - `products` 表新增 `image_url` 字段 + - 创建 `product_images` 公共存储桶 + - 添加存储桶 RLS 策略 +- **UI 优化** + - 纺织厂工作台"原坯布仓库"更名为"已生产坯布" + - 更新首页快捷操作按钮标签 + +## Bug 修复记录 + +### 2025-05-25 TypeScript 类型兼容性问题 + +#### 问题描述 +添加图片功能后出现类型错误: +``` +Types of property 'image_url' are incompatible. +Type 'string | null | undefined' is not assignable to type 'string | null' +``` + +#### 根本原因 +- `ProductWithInventory` 接口扩展了 `Product` 类型 +- 数据库生成的 `Product` 类型中 `image_url` 为 `string | null` +- 但前端扩展类型中重复定义了 `image_url?: string | null`,导致类型冲突 + +#### 修复方案 +移除 `ProductWithInventory` 接口中重复的 `image_url` 字段定义,让类型从父接口继承。 + +#### 代码变更 +- **文件**:`src/types/index.ts` +- **改动**:删除 `ProductWithInventory` 接口中的 `image_url` 字段 + +#### 经验教训与最佳实践 + +**1. 类型扩展原则** +- 当接口 `extends` 另一个类型时,避免重复定义父类型中已存在的字段 +- 数据库生成的类型(`Tables<'products'>`)是单一事实来源,前端扩展接口只应添加计算属性或关联数据 +- 使用 `Omit` 或 `Pick` 可以明确表达类型意图 + +**2. 可选字段的语义** +- `?: string | null` 和 `: string | null` 在 TypeScript 中有不同含义 +- 数据库字段通常定义为 `: string | null`(必须存在,可为 null) +- 前端表单数据可能使用 `?: string`(可选,不存在或存在) +- 混用这两种语义会导致类型不兼容 + +**3. 类型检查时机** +- 在修改类型定义后,立即运行 `pnpm run typecheck` 验证 +- 类型错误往往在使用处才暴露,而非定义处 +- 建议启用 TypeScript 严格模式(`strict: true`)提前发现问题 + +**4. 数据库迁移后的类型同步** +- 执行数据库迁移后,Supabase 会自动更新 `src/supabase/types.ts` +- 需要检查前端自定义类型是否需要相应调整 +- 建议建立迁移后的类型检查清单 + +--- + +### 2025-05-23 采购商仓库管理数据同步问题 + +#### 问题描述 +采购商进入仓库管理页面时,系统崩溃报错: +``` +Uncaught TypeError: Cannot read properties of undefined (reading '66f0af18-8d29-493a-ab5a-9ded734d71b8') +``` + +修复后仍发现"各工厂库存"数据未正确与纺织厂数据同步。 + +#### 根本原因 +1. **变量作用域问题**:`planToFactoryName` 变量在代码块内部定义,但在后面的代码块中被访问,导致 `undefined` 错误 +2. **代码结构混乱**:工厂信息查询逻辑分散在多个地方,导致数据流不清晰 +3. **工厂分组键值错误**:按工厂分组库存时使用了错误的键值(`factory_id` 而非 `factory_name`) + +#### 修复方案 +1. **提前获取工厂信息**:将工厂信息查询(`plan_factories` + `companies`)移到 `fetchData` 函数开头,确保后续所有逻辑都能访问 +2. **统一变量命名**:避免重复定义相同用途的变量,使用清晰的命名区分不同阶段的映射关系 +3. **修复分组逻辑**:使用工厂名称作为分组键,确保正确汇总各工厂库存 + +#### 代码变更 +- **文件**:`src/pages/purchaser/WarehouseManage.tsx` +- **主要改动**: + - 将工厂信息查询提前到数据获取阶段的开头 + - 合并重复的查询逻辑,避免多次查询相同数据 + - 修复 `fabricCodeFactoryInventory` 的分组键使用 + - 确保 `planToFactoryName` 在整个函数作用域内可用 + +#### 经验教训 +- **块级作用域陷阱**:JavaScript/TypeScript 的块级作用域容易导致变量访问问题,特别是在复杂的异步数据获取逻辑中 +- **数据流清晰性**:相关数据查询应该集中在一起,避免分散在代码各处 +- **变量命名规范**:相同用途的变量应该统一命名,避免重复定义造成混淆 + +### 2025-05-23 入库记录弹窗组件 + +#### 功能描述 +创建统一的入库记录弹窗组件 `InventoryRecordsModal`,用于在采购商和纺织厂页面展示完整的入库记录详情。 + +#### 组件特性 +- **统计摘要**:显示总匹数和总米数 +- **记录列表**:按时间倒序显示所有入库记录 +- **详细信息**:包含日期时间、计划编号、工厂名称、批号、匹数、米数、来源 +- **动画效果**:使用 Framer Motion 实现平滑的弹窗动画 + +#### 使用方式 +```tsx +import { InventoryRecordsModal } from '../../components/InventoryRecordsModal'; + +// 在页面中使用 + setRecordsModalOpen(false)} + records={selectedRecords} + title="产品名称 - 入库记录" +/> +``` + +#### 文件变更 +- `src/components/InventoryRecordsModal.tsx` - 新建弹窗组件 +- `src/pages/purchaser/WarehouseManage.tsx` - 集成弹窗组件 +- `src/pages/textile/FabricWarehouse.tsx` - 集成弹窗组件 + +### 2025-05-23 移动端适配优化 + +#### 纺织厂原坯布仓库移动端适配 +- **桌面端**:保持表格布局,完整展示所有字段 +- **移动端**:使用卡片式布局,避免横向滚动 +- **响应式断点**:使用 `sm:` 断点(640px)区分桌面和移动端 + +#### 卡片设计规范 +- 计划编号和规格横向排列 +- 计划/入库数量分开显示 +- 最近一次入库记录显示在底部 +- "全部记录"按钮简化为"全部 (X条)"节省空间 +- 圆角卡片设计,带边框分隔 + +## Bug 修复知识库 + +### 分类一:JavaScript/TypeScript 作用域问题 + +#### Bug: 变量提升导致的 undefined 错误 +**现象**:`Uncaught TypeError: Cannot read properties of undefined (reading 'xxx')` + +**根本原因**: +- 在代码块内部定义的变量,在后面的代码块中被访问 +- JavaScript 块级作用域导致变量不可访问 + +**修复方案**: +- 将变量定义提前到函数作用域顶部 +- 确保变量在使用前已定义 + +**示例代码**: +```typescript +// 错误:planToFactoryName 在后面代码块中使用 +if (condition) { + const planToFactoryName = {}; +} +// planToFactoryName 在这里访问不到 + +// 正确:提前定义 +const planToFactoryName: Record = {}; +if (condition) { + // 填充数据 + planToFactoryName[id] = name; +} +// 可以正常访问 +``` + +**相关文件**:`src/pages/purchaser/WarehouseManage.tsx` + +--- + +### 分类二:数据一致性问题 + +#### Bug: 入库记录与计划数量不一致 +**现象**: +- 计划显示已入库 7400 米 +- 仓库只显示 4400 米 +- 数据不同步 + +**根本原因**: +- 纺织厂入库时,先检查是否有仓库 +- 有仓库时才创建 `inventory_records` 记录 +- 但无论是否有仓库,都更新 `completed_quantity` +- 导致 `completed_quantity` 增加,但 `inventory_records` 缺失 + +**修复方案**: +```typescript +// 错误逻辑 +if (warehouse) { + await supabase.from('inventory_records').insert({...}); +} +await supabase.from('production_plans').update({ completed_quantity: newQty }); + +// 正确逻辑 +await supabase.from('production_plans').update({ completed_quantity: newQty }); +await supabase.from('inventory_records').insert({ + warehouse_id: warehouse?.id || null, // 允许 null + ... +}); +``` + +**相关文件**:`src/pages/textile/PlanOverview.tsx` + +--- + +#### Bug: 数据修复 - 补充缺失的入库记录 +**现象**:历史数据存在 `completed_quantity` 与 `inventory_records` 不一致 + +**修复步骤**: +1. 查询不一致的数据: +```sql +SELECT p.id, p.plan_code, p.completed_quantity, COALESCE(SUM(ir.quantity), 0) as inventory_total +FROM production_plans p +LEFT JOIN inventory_records ir ON ir.plan_id = p.id +GROUP BY p.id +HAVING p.completed_quantity != COALESCE(SUM(ir.quantity), 0); +``` + +2. 为缺失记录的计划创建仓库(如果不存在) +3. 补充缺失的入库记录 + +**相关文件**:数据库修复脚本 + +--- + +### 分类三:空值处理问题 + +#### Bug: NaN 显示问题 +**现象**:弹窗中显示 "NaN 米" + +**根本原因**: +- 入库记录中的 `meters` 字段可能为 `undefined` 或 `null` +- 直接进行数学运算导致 `NaN` + +**修复方案**: +```typescript +// 错误 +const totalMeters = records.reduce((sum, r) => sum + r.meters, 0); + +// 正确 +const totalMeters = records.reduce((sum, r) => sum + (r.meters || r.quantity || 0), 0); + +// 显示时 +{record.meters || record.quantity || 0} 米 +``` + +**相关文件**:`src/components/InventoryRecordsModal.tsx` + +--- + +### 分类四:UI/UX 问题 + +#### Bug: 移动端表格横向溢出 +**现象**:移动端表格内容超出屏幕,需要横向滚动 + +**根本原因**: +- 使用 `
` 布局,列数过多 +- 没有针对移动端做适配 + +**修复方案**: +- 桌面端:保持表格布局 `hidden sm:block` +- 移动端:使用卡片式布局 `sm:hidden` +- 响应式断点:`sm:`(640px) + +**相关文件**:`src/pages/textile/FabricWarehouse.tsx` + +--- + +### 分类五:代码组织问题 + +#### Bug: 重复代码和逻辑分散 +**现象**: +- 相同功能在多个地方重复实现 +- 工厂查询逻辑分散在多处 +- 难以维护 + +**修复方案**: +- 提取公共组件(如 `InventoryRecordsModal`) +- 将相关数据查询集中在一起 +- 统一变量命名规范 + +**相关文件**: +- `src/components/InventoryRecordsModal.tsx`(新建) +- `src/pages/purchaser/WarehouseManage.tsx` +- `src/pages/textile/FabricWarehouse.tsx` + +--- + +## 开发规范总结 + +### 1. 数据一致性原则 +- **原子操作**:相关联的数据更新必须在同一事务中完成 +- **校验机制**:关键数据变更后,添加校验逻辑确保一致性 +- **修复脚本**:建立数据修复机制,处理历史不一致数据 + +### 2. 空值处理规范 +- **默认值**:所有数值计算必须提供默认值 +- **可选链**:使用 `?.` 和 `||` 处理可能为空的属性 +- **类型安全**:TypeScript 严格模式,定义完整的接口类型 + +### 3. 响应式设计规范 +- **移动优先**:先设计移动端,再适配桌面端 +- **断点选择**:使用标准断点 `sm:`(640px)、`md:`(768px) +- **布局切换**:表格 ↔ 卡片,根据屏幕宽度切换 + +### 4. 组件化开发 +- **单一职责**:每个组件只做一件事 +- **可复用性**:提取公共组件,避免重复代码 +- **Props 设计**:清晰的 Props 接口,支持灵活配置 + +### 5. 调试技巧 +- **日志输出**:关键节点添加 console.log 输出调试信息 +- **数据对比**:前后端数据对比,定位不一致问题 +- **数据库查询**:直接使用 SQL 查询验证数据状态 + +## 2025-05-24 代码重构记录 + +### 重构背景 +解决代码可维护性问题:单体文件过大(WarehouseManage.tsx 1992行)、代码重复、性能隐患。 + +### 重构内容 + +#### 1. 类型系统重构 +**文件**: `src/types/index.ts` +- 新增仓库管理相关类型:`ProductPriceHistory`, `ProductYarnRatio`, `ProductInRecord`, `FactoryInventory`, `ProductWithInventory`, `FabricBatch` +- 新增计划管理相关类型:`PlanWithSteps`, `PlanGroup`, `StatusConfig` +- 新增流程节点类型:`ProcessNode`, `StepTypeConfig` +- 新增表单类型:`YarnInput` +- 新增通用工具类型:`LoadingState`, `PaginationState` + +#### 2. 工具函数提取 +**文件**: `src/utils/constants.ts` +- `statusMap` - 状态映射配置 +- `simpleStatusMap` - 简化状态映射 +- `stepTypeConfig` - 步骤类型配置 +- `stepNames` - 步骤名称映射 +- `stepOrder` - 步骤顺序 +- `animationConfig` - 动画配置(标准/快速/慢速过渡、弹簧动画) +- `paginationConfig` - 分页配置 +- `progressThresholds` - 进度阈值 + +**文件**: `src/utils/helpers.ts` +- `calculateProgress` - 计算生产进度百分比 +- `formatDate` / `formatDateTime` / `formatTime` - 日期格式化 +- `sortByStepType` - 按步骤类型排序 +- `findFirstPendingIndex` - 查找第一个待确认节点 +- `generateProductCode` - 生成产品码 +- `extractParamsFromLink` - 从分享链接提取参数 +- `debounce` / `throttle` - 防抖节流函数 +- `safeParseNumber` / `safeParseInt` - 安全数字解析 +- `groupProductsByNameAndWeight` - 按名称和克重分组产品 +- `calculateSummary` - 计算汇总数据 + +#### 3. 自定义 Hooks 优化 +**文件**: `src/hooks/useInventoryData.ts` +- 功能:优化的库存数据获取 Hook +- 特性: + - 5秒数据缓存,避免重复请求 + - 并行数据获取(Promise.all) + - 实时订阅自动刷新 + - 错误处理 + +**文件**: `src/hooks/usePlanData.ts` +- 功能:优化的计划数据获取 Hook +- 特性: + - 10秒数据缓存 + - 分页加载支持 + - 操作人名称缓存 + - 实时订阅自动刷新 + +#### 4. 组件拆分 +**WarehouseManage.tsx 重构**: +- 原文件:1992行 → 新主文件:约300行 +- 拆分组件: + - `WarehouseNav.tsx` - 仓库类型和子菜单导航 + - `ProductForm.tsx` - 产品创建/编辑表单模态框 + - `InboundForm.tsx` - 入库记录表单模态框 + - `ProductList.tsx` - 产品列表(支持桌面端表格和移动端卡片) + - `InboundRecords.tsx` - 入库记录列表 + +**PlanOverview.tsx 重构**: +- 原文件:711行 → 新主文件:约250行 +- 拆分组件: + - `PlanGroup.tsx` - 工厂分组和计划卡片 + - `ShareModal.tsx` - 分享链接弹窗 + +### 性能优化 + +| 优化项 | 实现方式 | 效果 | +|--------|----------|------| +| 数据缓存 | useRef 缓存数据,5-10秒有效期 | 减少重复请求 | +| 分页加载 | 每次加载20条,支持加载更多 | 避免大数据量渲染 | +| 并行请求 | Promise.all 并行获取关联数据 | 减少等待时间 | +| useMemo/useCallback | 缓存计算结果和函数引用 | 减少重渲染 | +| 组件拆分 | 大文件拆分为职责单一组件 | 提升可维护性 | + +### 代码质量提升 + +| 指标 | 重构前 | 重构后 | +|------|--------|--------| +| 最大文件行数 | 1992行 | 约300行 | +| 类型定义分散度 | 多处重复定义 | 统一在 types/index.ts | +| 状态映射重复 | 多处硬编码 | 统一在 constants.ts | +| 动画配置重复 | 多处硬编码 | 统一在 constants.ts | +| 数据获取逻辑 | 分散在组件中 | 封装为自定义 Hooks | + +### 文件变更清单 + +**新增文件**: +- `src/utils/constants.ts` +- `src/utils/helpers.ts` +- `src/hooks/useInventoryData.ts` +- `src/hooks/usePlanData.ts` +- `src/components/warehouse/WarehouseNav.tsx` +- `src/components/warehouse/ProductForm.tsx` +- `src/components/warehouse/InboundForm.tsx` +- `src/components/warehouse/ProductList.tsx` +- `src/components/warehouse/InboundRecords.tsx` +- `src/components/plans/PlanGroup.tsx` +- `src/components/plans/ShareModal.tsx` + +**修改文件**: +- `src/types/index.ts` - 扩展类型定义 +- `src/pages/purchaser/WarehouseManage.tsx` - 重构为精简主文件 +- `src/pages/purchaser/PlanOverview.tsx` - 重构为精简主文件 + +## 2025-05-24 出入库历史功能开发记录 + +### 需求背景 +采购商工作台-原坯布仓库的产品管理模块需要增加出入库历史查看功能,方便用户追踪每个产品的库存变动记录。 + +### 遇到的问题 + +#### 问题1:类型定义不匹配 +**现象**:`ProductInRecord` 类型定义与数据库实际返回的字段不一致,导致类型检查报错。 + +**根本原因**: +- 原类型定义只包含前端展示需要的字段(`time`, `rolls`, `meters`, `source` 等) +- 但数据库查询返回的是完整的表字段(`id`, `product_id`, `company_id`, `created_at` 等) +- TypeScript 严格模式下,类型不匹配导致赋值错误 + +**错误信息**: +``` +Type '{ batch_no: string; company_id: string | null; created_at: string; ... }' is missing the following properties from type 'ProductInRecord': time, source +``` + +**解决方案**: +扩展 `ProductInRecord` 类型定义,包含所有数据库字段,同时保留前端展示用的可选扩展字段: +```typescript +export interface ProductInRecord { + // 数据库字段(必需) + id: string; + product_id: string; + company_id: string | null; + batch_no: string; + rolls: number; + meters: number; + notes: string | null; + operator_id: string | null; + warehouse_id: string | null; + created_at: string; + // 前端展示用扩展字段(可选) + time?: string; + plan_code?: string; + factory_name?: string; + source?: 'self' | 'textile'; + ... +} +``` + +**经验教训**: +- 类型定义应该与数据库表结构保持一致 +- 前端特有的展示字段应该标记为可选(`?`) +- 使用 TypeScript 严格模式可以提前发现这类问题 + +### 实现方案 + +#### 1. 组件层修改(ProductList.tsx) +**新增 Props**: +- `onViewStockHistory: (product: ProductWithInventory) => void` - 查看出入库历史回调 + +**桌面端操作列**: +- 在"价格历史"按钮前添加"出入库历史"按钮(紫色样式) +- 操作按钮顺序:出入库历史 → 价格历史 → 编辑 → 删除 + +**移动端操作按钮**: +- 第一行:出入库历史(紫色)、价格历史(绿色) +- 第二行:编辑(蓝色)、删除(红色) +- 分两行布局避免按钮过于拥挤 + +#### 2. 页面层修改(WarehouseManage.tsx) +**新增状态**: +```typescript +const [stockHistoryModalOpen, setStockHistoryModalOpen] = useState(false); +const [selectedStockHistory, setSelectedStockHistory] = useState([]); +``` + +**新增处理函数**: +```typescript +const handleViewStockHistory = useCallback(async (product: ProductWithInventory) => { + const { data } = await supabase + .from('product_inventory_records') + .select('*') + .eq('product_id', product.id) + .order('created_at', { ascending: false }); + + setSelectedStockHistory(data || []); + setSelectedProductName(`${product.product_name}-${product.weight}g-${product.color}`); + setStockHistoryModalOpen(true); +}, []); +``` + +**弹窗设计**: +- 标题:{产品名称} - 出入库历史 +- 内容展示:序号、时间、批号、匹数、米数、备注 +- 空状态:显示"暂无出入库记录" +- 样式:与价格历史弹窗保持一致(灰色卡片背景) + +#### 3. 类型层修改(types/index.ts) +**修复 ProductInRecord 类型**: +- 添加所有数据库表字段作为必需字段 +- 保留原有前端扩展字段作为可选字段 +- 确保与 `Tables<'product_inventory_records'>` 兼容 + +### 文件变更清单 + +**修改文件**: +- `src/components/warehouse/ProductList.tsx` - 添加出入库历史按钮和回调 +- `src/pages/purchaser/WarehouseManage.tsx` - 添加出入库历史弹窗和查询逻辑 +- `src/types/index.ts` - 修复 ProductInRecord 类型定义 + +### UI 设计规范 + +**按钮颜色体系**: +| 功能 | 颜色 | Tailwind 类 | +|------|------|-------------| +| 出入库历史 | 紫色 | `text-purple-600`, `bg-purple-50` | +| 价格历史 | 绿色 | `text-emerald-600`, `bg-emerald-50` | +| 编辑 | 蓝色 | `text-blue-600`, `bg-blue-50` | +| 删除 | 红色 | `text-red-600`, `bg-red-50` | + +**弹窗布局**: +- 最大宽度:`max-w-2xl`(约672px) +- 最大高度:`max-h-[80vh]`,内容区 `max-h-[60vh]` +- 卡片内边距:`p-4` +- 列表间距:`space-y-3` + +## 分享链接安全策略 + +### 加密机制 +- 分享链接使用 `src/utils/shareCrypto.ts` 进行加密/解密 +- 加密内容仅包含:`planId` + `factoryType` + `timestamp` +- 使用 Base64 + 盐值混淆 + 字符串反转进行加密 + +### 敏感信息保护 +- **分享链接不传输**:成品名称、颜色、色号等敏感信息 +- **导入页面不展示**:接收方通过链接导入计划时,页面不显示成品名称、颜色、色号 +- **仅展示**:计划编号、坯布识别码、计划产量、每米布纱用量、生产采购价、备注 + +--- + +## Bug 修复知识库(扩展) + +### 分类六:React 组件生命周期问题 + +#### Bug: 内存泄漏 - 未清理的订阅和定时器 +**现象**: +- 页面切换后控制台报错:`Can't perform a React state update on an unmounted component` +- 内存占用持续增长 +- 实时订阅重复触发 + +**根本原因**: +- 组件卸载时未清理 Supabase Realtime 订阅 +- 未清除 setInterval/setTimeout 定时器 +- 事件监听器未移除 + +**修复方案**: +```typescript +// 错误:未清理订阅 +useEffect(() => { + const channel = supabase + .channel('inventory_changes') + .on('postgres_changes', {...}, callback) + .subscribe(); +}, []); + +// 正确:清理订阅 +useEffect(() => { + const channel = supabase + .channel('inventory_changes') + .on('postgres_changes', {...}, callback) + .subscribe(); + + return () => { + channel.unsubscribe(); + }; +}, []); + +// 定时器清理 +useEffect(() => { + const timer = setInterval(fetchData, 5000); + return () => clearInterval(timer); +}, []); +``` + +**相关文件**:`src/hooks/useRealtime.ts`, `src/hooks/useInventoryData.ts` + +--- + +### 分类七:Supabase RLS 策略问题 + +#### Bug: RLS 策略导致数据无法插入 +**现象**: +- 插入数据时报错:`new row violates row-level security policy for table "xxx"` +- 部分用户无法查看数据 +- 子账号无法操作主账号数据 + +**根本原因**: +- RLS 策略条件过于严格 +- 未考虑子账号场景(需要通过 master_id 获取 company_id) +- 策略中使用了错误的字段比较 + +**修复方案**: +```sql +-- 错误:仅检查 user_id +CREATE POLICY users_insert_inventory ON inventory_records + FOR INSERT WITH CHECK (auth.uid() = operator_id); + +-- 正确:检查公司权限(支持子账号) +CREATE OR REPLACE FUNCTION get_user_master_company_id() +RETURNS UUID AS $$ +DECLARE + v_company_id UUID; + v_master_id UUID; +BEGIN + SELECT company_id, master_id INTO v_company_id, v_master_id + FROM profiles WHERE id = auth.uid(); + + IF v_master_id IS NOT NULL THEN + SELECT company_id INTO v_company_id + FROM profiles WHERE id = v_master_id; + END IF; + + RETURN v_company_id; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- 使用辅助函数创建策略 +CREATE POLICY company_insert_inventory ON inventory_records + FOR INSERT WITH CHECK ( + company_id = get_user_master_company_id() + ); +``` + +**相关文件**:`migrations/*_create_*_rls.sql` + +--- + +### 分类八:Webpack 构建问题 + +#### Bug: 开发服务器热更新失效 +**现象**: +- 修改代码后页面不自动刷新 +- 控制台报错 `WebSocket connection failed` +- 热模块替换 (HMR) 不工作 + +**根本原因**: +- Webpack devServer 配置缺少 `hot: true` +- `allowedHosts` 配置不正确 +- `historyApiFallback` 未启用 + +**修复方案**: +```javascript +// webpack.config.js +development: { + devServer: { + port: 3015, + hot: true, // 启用热更新 + historyApiFallback: true, // 支持前端路由 + allowedHosts: ['all', '.alibaba-inc.com'], // 允许所有主机 + client: { + overlay: { + errors: true, + warnings: false + } + } + } +} +``` + +**相关文件**:`webpack.config.js` + +--- + +#### Bug: 生产构建产物体积过大 +**现象**: +- 构建后的 JS 文件超过 2MB +- 首屏加载时间过长 +- 代码未压缩 + +**修复方案**: +```javascript +// webpack.config.js +const TerserPlugin = require('terser-webpack-plugin'); + +module.exports = { + optimization: { + minimize: true, + minimizer: [new TerserPlugin()], + splitChunks: { + chunks: 'all', + cacheGroups: { + vendor: { + test: /[\\/]node_modules[\\/]/, + name: 'vendors', + chunks: 'all' + } + } + } + } +}; +``` + +--- + +### 分类九:TypeScript 类型问题 + +#### Bug: 严格模式下的隐式 any 错误 +**现象**: +- 编译报错:`Parameter 'xxx' implicitly has an 'any' type` +- 类型推断失败 +- 第三方库类型定义缺失 + +**修复方案**: +```typescript +// tsconfig.json +{ + "compilerOptions": { + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true + } +} + +// 代码中显式声明类型 +// 错误 +const handleClick = (e) => { ... }; + +// 正确 +const handleClick = (e: React.MouseEvent) => { ... }; + +// 第三方库类型声明 +// 创建 src/types/declarations.d.ts +declare module 'some-untyped-lib' { + export function doSomething(): void; +} +``` + +--- + +#### Bug: 泛型约束导致的类型不匹配 +**现象**: +- 泛型组件使用时类型推断错误 +- `Type 'T' is not assignable to type 'xxx'` + +**修复方案**: +```typescript +// 错误:泛型约束不明确 +interface Props { + items: T[]; + renderItem: (item: T) => React.ReactNode; +} + +// 正确:添加约束 +interface Props { + items: T[]; + renderItem: (item: T) => React.ReactNode; +} + +// 使用 +interface User { + id: string; + name: string; +} + + items={users} renderItem={(user) => {user.name}} /> +``` + +--- + +### 分类十:状态管理问题 + +#### Bug: 状态更新异步导致的竞态条件 +**现象**: +- 快速点击按钮时数据不一致 +- 表单提交后状态未同步更新 +- 多个组件间状态不同步 + +**根本原因**: +- 直接修改状态而非使用函数式更新 +- 异步操作未正确处理依赖 +- 缺少乐观更新 + +**修复方案**: +```typescript +// 错误:直接依赖旧状态 +const handleIncrement = () => { + setCount(count + 1); // 可能使用过时的 count +}; + +// 正确:使用函数式更新 +const handleIncrement = () => { + setCount(prev => prev + 1); +}; + +// 复杂状态更新 +const [state, setState] = useState({ count: 0, loading: false }); + +const updateState = useCallback((updates: Partial) => { + setState(prev => ({ ...prev, ...updates })); +}, []); + +// 乐观更新示例 +const handleAddItem = async (item: Item) => { + // 乐观更新 UI + setItems(prev => [...prev, { ...item, id: 'temp-' + Date.now() }]); + + try { + const { data } = await supabase.from('items').insert(item).select().single(); + // 用真实数据替换临时数据 + setItems(prev => prev.map(i => i.id.startsWith('temp-') ? data : i)); + } catch (error) { + // 回滚 + setItems(prev => prev.filter(i => !i.id.startsWith('temp-'))); + toast.error('添加失败'); + } +}; +``` + +**相关文件**:`src/stores/index.ts` + +--- + +### 分类十一:表单处理问题 + +#### Bug: 受控组件与非受控组件混用警告 +**现象**: +- 控制台警告:`A component is changing an uncontrolled input to be controlled` +- 表单值初始化后无法修改 + +**根本原因**: +- 初始值为 `undefined` 或 `null`,后续变为有值 +- React 无法确定是受控还是非受控组件 + +**修复方案**: +```typescript +// 错误:初始值可能为 undefined +const [value, setValue] = useState(); + +// 正确:始终提供非 undefined 初始值 +const [value, setValue] = useState(''); + +// 对象表单 +const [form, setForm] = useState({ + name: '', + email: '', + age: 0 +}); + +// 处理可能为 null 的数据 +const [user, setUser] = useState(null); +// 渲染时 + +``` + +--- + +#### Bug: 表单验证时机问题 +**现象**: +- 提交时才显示验证错误,用户体验差 +- 实时验证过于频繁,性能问题 +- 异步验证导致表单状态混乱 + +**修复方案**: +```typescript +// 使用防抖进行实时验证 +const debouncedValidate = useMemo( + () => debounce((value: string) => { + const error = validateEmail(value); + setErrors(prev => ({ ...prev, email: error })); + }, 300), + [] +); + +// 表单提交验证 +const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + const errors = validateForm(formData); + if (Object.keys(errors).length > 0) { + setErrors(errors); + return; + } + + setSubmitting(true); + try { + await submitForm(formData); + } finally { + setSubmitting(false); + } +}; +``` + +--- + +### 分类十二:CSS/Tailwind 问题 + +#### Bug: Tailwind 类名未生效 +**现象**: +- 样式未应用 +- 自定义配置的颜色/间距未生效 +- 生产构建后样式丢失 + +**根本原因**: +- `tailwind.config.js` 配置错误 +- `content` 配置未包含所有文件路径 +- PostCSS 配置问题 + +**修复方案**: +```javascript +// tailwind.config.js +module.exports = { + content: [ + './src/**/*.{js,jsx,ts,tsx}', // 确保包含所有组件文件 + './index.html' + ], + theme: { + extend: { + colors: { + primary: { + 50: '#eff6ff', + 500: '#3b82f6', + 600: '#2563eb', + } + } + } + } +}; + +// postcss.config.js +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +}; +``` + +--- + +#### Bug: 移动端样式被桌面端覆盖 +**现象**: +- 移动端样式在桌面端显示正常,但在移动端错误 +- 响应式断点不生效 + +**根本原因**: +- Tailwind 的类名顺序问题 +- 未理解移动优先原则 + +**修复方案**: +```html + + + + + +
+ 移动端小字 → 平板中字 → 桌面大字 +
+ + + + +
+ 仅在小于 640px 时显示 +
+``` + +--- + +### 分类十三:性能优化问题 + +#### Bug: 大数据列表渲染卡顿 +**现象**: +- 列表超过 100 条时滚动卡顿 +- 内存占用高 +- 帧率下降 + +**修复方案**: +```typescript +// 1. 虚拟列表 +import { useVirtualizer } from '@tanstack/react-virtual'; + +const VirtualList = ({ items }) => { + const parentRef = useRef(null); + + const virtualizer = useVirtualizer({ + count: items.length, + getScrollElement: () => parentRef.current, + estimateSize: () => 50, + }); + + return ( +
+
+ {virtualizer.getVirtualItems().map((virtualItem) => ( +
+ {items[virtualItem.index]} +
+ ))} +
+
+ ); +}; + +// 2. 分页加载 +const [page, setPage] = useState(1); +const [hasMore, setHasMore] = useState(true); + +const loadMore = async () => { + const { data } = await supabase + .from('items') + .select('*') + .range(page * 20, (page + 1) * 20 - 1); + + if (data.length < 20) setHasMore(false); + setItems(prev => [...prev, ...data]); + setPage(p => p + 1); +}; + +// 3. 使用 React.memo 和 useMemo +const MemoizedItem = React.memo(({ item }) => { + return
{item.name}
; +}); +``` + +**相关文件**:`src/components/VirtualList.tsx` + +--- + +### 分类十六:沙箱环境兼容性问题 + +#### Bug: Webpack devServer 在沙箱环境中预览加载失败 +**现象**: +- 开发服务器启动正常,端口 3015 在监听 +- 但 curl 请求超时,无法获取 HTML 内容 +- 浏览器预览无法加载 + +**根本原因**: +1. **沙箱网络限制**:`net.ipv4.conf.all.forwarding = 0`(IP 转发被禁用) +2. **Webpack devServer 配置问题**: + - `host: '127.0.0.1'` 无法在沙箱中访问 + - `static` 配置指向不存在的 `public` 目录 + - `devMiddleware.writeToDisk` 与沙箱环境不兼容 +3. **与其他项目对比**:Vite 在沙箱中可以正常工作,Webpack devServer 存在兼容性问题 + +**修复方案**: +```javascript +// webpack.config.js - 标准配置 +module.exports = { + // ...其他配置 + devServer: { + port: 3015, + host: '0.0.0.0', // 必须使用 0.0.0.0 + allowedHosts: 'all', + hot: true, + historyApiFallback: true, + // 不要配置 static 和 devMiddleware,使用默认行为 + }, +}; +``` + +**关键配置点**: +1. **host: '0.0.0.0'** - 必须绑定到所有接口,而不是 127.0.0.1 +2. **不要配置 static** - 让 webpack 使用内存中的编译产物 +3. **不要配置 devMiddleware.writeToDisk** - 沙箱中不需要写入磁盘 +4. **保持简单** - 避免复杂的 devServer 配置 + +**验证方法**: +```bash +# 1. 构建验证 +pnpm run build + +# 2. 检查 dist 目录 +ls -la dist/ + +# 3. 启动开发服务器(沙箱中 curl 可能仍超时,但不影响实际功能) +pnpm run dev +``` + +**替代方案**: +- 使用 `pnpm run build` 生成静态文件 +- 在实际部署环境中测试预览功能 +- 考虑迁移到 Vite(沙箱兼容性更好) + +**相关文件**: +- `webpack.config.js` +- `package.json` + +--- + +## 学习反思与最佳实践 + +### 1. 环境差异意识 +**问题**:在本地开发环境正常工作的配置,在沙箱环境中可能失效。 +**教训**: +- 沙箱环境有严格的网络限制(IP 转发禁用、localhost 访问受限) +- 需要针对沙箱环境调整开发服务器配置 +- 不要假设所有环境行为一致 + +### 2. 配置简化原则 +**问题**:过度配置导致兼容性问题。 +**教训**: +- 使用框架/工具的默认配置作为起点 +- 只在必要时添加自定义配置 +- 复杂的配置组合可能在特定环境中失效 + +### 3. 调试方法改进 +**问题**:陷入反复修改配置的循环,没有定位真正原因。 +**教训**: +- 先检查环境限制(`sysctl net.ipv4.conf.all.forwarding`) +- 对比正常项目和异常项目的差异 +- 使用最小化配置验证(逐步添加配置项) +- 接受某些限制(如沙箱中 curl 无法访问) + +### 4. 文档记录重要性 +**问题**:花费大量时间排查已知问题。 +**教训**: +- 及时记录环境特定的配置要求 +- 建立常见问题知识库 +- 记录失败尝试和成功方案 + +### 5. 技术选型考虑 +**问题**:Webpack devServer 在沙箱中存在兼容性问题。 +**教训**: +- 技术选型需要考虑目标运行环境 +- Vite 在沙箱环境中兼容性更好 +- 对于新项目,优先考虑沙箱友好的工具链 + +--- + +## 沙箱环境开发指南 + +### 已知限制 +1. **网络访问**:localhost/127.0.0.1 访问受限 +2. **IP 转发**:`net.ipv4.conf.all.forwarding = 0` +3. **端口**:仅 3015 可用 +4. **进程限制**:某些命令执行超时 + +### 推荐配置 +```javascript +// webpack.config.js +devServer: { + port: 3015, + host: '0.0.0.0', + allowedHosts: 'all', + hot: true, + historyApiFallback: true, +} +``` + +### 验证流程 +1. `pnpm run typecheck` - 类型检查 +2. `pnpm run build` - 构建验证 +3. `ls -la dist/` - 检查产物 +4. `pnpm run dev` - 启动服务器(沙箱中预览可能受限) + +### 故障排查 +- 检查端口占用:`lsof -i :3015` +- 检查 IP 转发:`sysctl net.ipv4.conf.all.forwarding` +- 简化配置:移除 static/devMiddleware 等复杂配置 +- 对比其他项目:参考正常工作的项目配置 + +--- + +### 分类十四:浏览器兼容性问题 + +#### Bug: 旧版浏览器不支持新特性 +**现象**: +- 在 Safari 13 或 IE11 中白屏 +- 某些 API 报错 `xxx is not defined` + +**修复方案**: +```javascript +// webpack.config.js +module.exports = { + target: ['web', 'es5'], // 编译到 ES5 + module: { + rules: [ + { + test: /\.(js|jsx|ts|tsx)$/, + use: { + loader: 'babel-loader', + options: { + presets: [ + ['@babel/preset-env', { + targets: { + browsers: ['> 1%', 'last 2 versions', 'not dead'] + }, + useBuiltIns: 'usage', + corejs: 3 + }] + ] + } + } + } + ] + } +}; + +// 使用 polyfill +import 'core-js/stable'; +import 'regenerator-runtime/runtime'; +``` + +--- + +### 分类十五:测试相关问题 + +#### Bug: 异步测试超时 +**现象**: +- 测试报错 `Timeout - Async callback was not invoked within the 5000ms` +- Supabase 操作未正确 mock + +**修复方案**: +```typescript +// jest.config.js +module.exports = { + testTimeout: 10000, // 增加超时时间 + setupFilesAfterEnv: ['/src/__tests__/setup.ts'] +}; + +// setup.ts +import '@testing-library/jest-dom'; + +// Mock Supabase +jest.mock('../supabase/client', () => ({ + supabase: { + from: jest.fn(() => ({ + select: jest.fn().mockReturnThis(), + insert: jest.fn().mockReturnThis(), + update: jest.fn().mockReturnThis(), + delete: jest.fn().mockReturnThis(), + eq: jest.fn().mockReturnThis(), + single: jest.fn().mockResolvedValue({ data: null, error: null }) + })), + auth: { + getUser: jest.fn().mockResolvedValue({ + data: { user: { id: 'test-user-id' } } + }) + } + } +})); + +// 测试用例 +import { render, screen, waitFor } from '@testing-library/react'; + +test('loads data', async () => { + render(); + + await waitFor(() => { + expect(screen.getByText('Loaded')).toBeInTheDocument(); + }, { timeout: 3000 }); +}); +``` + +--- + +## 调试技巧总结 + +### 1. 网络请求调试 +```typescript +// 在 supabase 客户端添加日志 +const supabase = createClient(url, key, { + db: { + schema: 'public' + }, + global: { + fetch: (...args) => { + console.log('Supabase Request:', args[0]); + return fetch(...args); + } + } +}); +``` + +### 2. 性能分析 +```typescript +// 使用 React DevTools Profiler +// 或使用 console.time +console.time('dataFetch'); +const data = await fetchData(); +console.timeEnd('dataFetch'); + +// 使用 Performance API +const mark = performance.mark('start'); +// ... 操作 +performance.measure('operation', 'start'); +``` + +### 3. 错误边界 +```typescript +// ErrorBoundary.tsx +class ErrorBoundary extends React.Component< + { children: React.ReactNode }, + { hasError: boolean; error: Error | null } +> { + constructor(props: { children: React.ReactNode }) { + super(props); + this.state = { hasError: false, error: null }; + } + + static getDerivedStateFromError(error: Error) { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { + console.error('Error caught by boundary:', error, errorInfo); + // 上报错误到监控服务 + } + + render() { + if (this.state.hasError) { + return ; + } + return this.props.children; + } +} +``` + +### 4. 开发环境检查清单 +- [ ] 控制台无警告/错误 +- [ ] React DevTools 无不必要的重渲染 +- [ ] Network 面板无重复请求 +- [ ] Lighthouse 评分 > 90 +- [ ] 移动端模拟器测试通过 +- [ ] 类型检查通过 `pnpm run typecheck` +- [ ] 代码规范检查通过 `pnpm run lint` \ No newline at end of file diff --git a/README.md b/README.md index c6e8220..52faa51 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,688 @@ -# iloom-flatten -auto sync from kihaneseifu/iloom +# 纺织行业采购计划管理系统 + +一款专为纺织行业设计的全流程协作管理平台,连接采购商(布行)、纺织厂、水洗厂三方,实现从计划制定到成品交付的数字化管理。 + +## 核心功能 + +### 多角色协作 +- **采购商/布行**:创建生产计划、管理坯布库存、跟踪生产进度、处理结款 +- **纺织厂**:接收生产计划、管理生产流程、坯布入库、纱线库存管理 +- **水洗厂**:接收水洗订单、管理水洗流程 + +### 计划管理 +- 自动生成唯一计划编号和坯布识别码 +- 纱线配比配置与用量自动计算 +- 生产流程节点跟踪(确认计划 → 采纱 → 染纱 → 上机 → 坯布入库) +- 实时进度可视化,支持97%完成度自动提醒 + +### 库存管理 +- **原坯布仓库**:按坯布码分组管理,入库记录追溯 + - 产品管理:支持按名称、克重、颜色多级分组展示 + - 出入库历史:查看每个产品的完整库存变动记录 + - 价格历史:追踪采购价格变动 +- **原料纱仓库**:库存预警、最低库存提醒 +- 实时库存数据同步,入库自动更新 + +### 结款管理 +- 按完成米数自动计算结款金额 +- 待结款汇总统计 +- 一键确认结款,记录结款时间 + +### 分享与导入 +- 加密分享链接,保护敏感信息 +- 支持剪贴板自动识别导入 +- 扫码或链接快速关联计划 + +## 技术特性 + +### 前端技术栈 +- **React 18** + TypeScript - 类型安全的函数组件开发 +- **TailwindCSS** - 原子化CSS,快速构建响应式界面 +- **Framer Motion** - 流畅的页面过渡和交互动画 +- **Lucide React** - 现代化图标库 + +### 移动端优化 +- 响应式布局,适配手机、平板、桌面端 +- 触控优化:44px最小触控区域、防双击缩放 +- 输入优化:数字键盘自动唤起、输入模式适配 +- 动画优化:硬件加速、自然动画曲线 + +### 后端服务 +- **Meoo Cloud (Supabase)** - 云数据库 + 认证 + 实时订阅 +- **PostgreSQL** - 关系型数据库,支持复杂查询 +- **Row Level Security** - 行级安全策略,基于公司 ID 的数据隔离 +- **Realtime** - 实时数据同步,入库记录即时更新 + +### 数据安全 +- **RLS 策略** - 所有表启用行级安全,严格的数据访问控制 +- **公司隔离** - 基于 company_id 的数据隔离,确保数据安全 +- **子账号支持** - 主账号/子账号权限继承,统一公司数据访问 +- **分享加密** - 分享链接使用 Base64 + 盐值混淆加密,保护敏感信息 + +## 快速开始 + +### 环境要求 +- Node.js 18+ +- pnpm 8+ + +### 安装依赖 +```bash +pnpm install +``` + +### 开发模式 +```bash +pnpm run dev +``` +服务启动在 http://localhost:3015 + +### 构建生产版本 +```bash +pnpm run build +``` + +### 数据库迁移 +```bash +# 查看云服务状态 +meoo-cli cloud status + +# 执行数据库迁移 +meoo-cli cloud migrate --sql "..." --name "migration_name" --changes "变更说明" +``` + +## 项目结构 + +``` +src/ +├── components/ # 公共组件 +│ ├── ProcessFlow.tsx # 生产流程节点 +│ ├── PlanCard.tsx # 计划卡片 +│ ├── StatusBadge.tsx # 状态标签 +│ ├── InventoryRecordsModal.tsx # 入库记录弹窗 +│ ├── OnboardingGuide.tsx # 新用户引导 +│ ├── DemoDisclaimerModal.tsx # Demo免责声明 +│ └── ComingSoonModal.tsx # 功能开发中提示 +├── pages/ # 页面组件 +│ ├── purchaser/ # 采购商页面 +│ │ ├── Dashboard.tsx +│ │ ├── PlanOverview.tsx +│ │ ├── NewPlan.tsx +│ │ ├── WarehouseManage.tsx +│ │ └── FactoryManage.tsx +│ ├── textile/ # 纺织厂页面 +│ │ ├── Dashboard.tsx +│ │ ├── PlanOverview.tsx +│ │ ├── YarnWarehouse.tsx +│ │ ├── FabricWarehouse.tsx +│ │ └── PaymentPending.tsx +│ ├── washing/ # 水洗厂页面 +│ │ └── Dashboard.tsx +│ ├── LoginPage.tsx +│ ├── RegisterPage.tsx +│ ├── RoleSelectPage.tsx +│ ├── MemberManage.tsx +│ └── ImportPlanPage.tsx +├── hooks/ # 自定义Hooks +│ ├── useResponsive.ts # 响应式检测 +│ └── useTheme.ts # 主题管理 +├── contexts/ # React上下文 +│ └── AuthContext.tsx # 认证上下文 +├── styles/ # 全局样式 +│ └── index.css +├── supabase/ # Supabase客户端 +│ ├── client.ts +│ └── types.ts +└── utils/ # 工具函数 + ├── crypto.ts + └── shareCrypto.ts +``` + +## 组件说明 + +### InventoryRecordsModal 入库记录弹窗 +统一风格的入库记录详情弹窗,支持采购商和纺织厂页面复用。 + +**特性:** +- 统计摘要:显示总匹数和总米数 +- 记录列表:按时间倒序显示所有入库记录 +- 详细信息:日期时间、计划编号、工厂名称、批号、匹数、米数 +- 动画效果:Framer Motion 平滑动画 + +**使用示例:** +```tsx +import { InventoryRecordsModal } from '../../components/InventoryRecordsModal'; + + setRecordsModalOpen(false)} + records={selectedRecords} + title="产品名称 - 入库记录" +/> +``` + +### OnboardingGuide 新用户引导 +首次进入系统时展示的功能引导动画,支持采购商和纺织厂角色。 + +**特性:** +- 分步骤功能介绍 +- 支持跳转到对应功能区 +- 进度条和步骤指示器 +- 可跳过引导 + +### DemoDisclaimerModal Demo免责声明 +访问网站时显示的Demo阶段免责声明弹窗。 + +### ComingSoonModal 功能开发中 +点击未开发功能时显示的提示弹窗。 + +## 使用指南 + +### 采购商操作流程 +1. 注册/登录账号,选择"采购商"角色 +2. 创建新计划,填写产品信息、纱线配比 +3. 通过分享链接将计划发送给纺织厂 +4. 实时查看生产进度,管理坯布库存 +5. 生产完成后确认结款 + +### 纺织厂操作流程 +1. 注册/登录账号,选择"纺织厂"角色 +2. 通过分享链接或剪贴板导入计划 +3. 按流程节点确认生产进度 +4. 坯布入库时填写入库数量和匹数 +5. 查看待结款,确认收款 + +### 子账号管理 +- 主账号可创建子账号,分配不同权限 +- 子账号可独立操作,数据归属主账号公司 +- **用户名全局唯一**:子账号用户名不可与其他主账号或子账号重复 + +## 数据安全 + +- 分享链接采用加密传输,不包含敏感信息 +- 导入页面隐藏成品名称、颜色、色号等敏感字段 +- RLS策略确保数据隔离,仅授权用户可访问 +- 虚拟邮箱格式 `{username}@meoo.local` 保护隐私 + +## 浏览器支持 + +- Chrome 90+ +- Safari 14+ +- Firefox 88+ +- Edge 90+ + +## 更新日志 + +### 2025-05-25 +- **产品图片上传功能** + - 为采购商原坯布仓库产品管理添加图片上传功能 + - 支持在产品创建和编辑时上传产品图片(JPG、PNG格式,最大5MB) + - 产品列表展示40x40px缩略图,无图片时显示默认占位图标 + - 产品表单支持图片预览、重新上传和删除功能 + - 使用 Supabase Storage `product_images` 存储桶存储图片 + - 数据库 `products` 表新增 `image_url` 字段 +- **UI 优化** + - 纺织厂工作台"原坯布仓库"更名为"已生产坯布"(页面标题和首页快捷操作) + +### 2025-05-24 +- **新增出入库历史功能** + - 采购商原坯布仓库产品列表新增"出入库历史"按钮 + - 支持查看每个产品的完整库存变动记录(批号、匹数、米数、时间) + - 桌面端和移动端均支持 +- **类型系统优化** + - 修复 `ProductInRecord` 类型定义,与数据库表结构保持一致 + - 区分数据库必需字段和前端展示扩展字段 +- **新增组件** + - `OutboundForm.tsx` - 出库表单组件 + - `PlanEditModal.tsx` - 计划编辑弹窗 + - `NotificationCenter.tsx` - 通知中心 + - `NetworkStatusBar.tsx` - 网络状态栏 + - `VirtualList.tsx` - 虚拟列表组件 + - `ErrorBoundary.tsx` - 错误边界组件 + +### 2025-05-23 +- 优化纺织厂工作台UI动画流畅度 +- 全面适配移动端,优化触控体验 +- 添加实时数据订阅,入库记录自动更新 +- 优化坯布入库输入框布局 +- 修复采购商仓库管理数据同步问题 +- 创建统一的入库记录弹窗组件 `InventoryRecordsModal` + +### 2025-05-22 +- 项目初始化 +- 完成基础架构搭建 +- 实现用户认证系统 +- 完成数据库设计和迁移 + +## 测试 + +### 类型检查 +```bash +pnpm run typecheck +``` + +### 代码规范 +```bash +pnpm run lint +``` + +### 构建验证 +```bash +pnpm run build +``` + +## CI/CD 配置 + +项目使用 GitHub Actions 进行持续集成和部署: + +### 工作流配置 +```yaml +# .github/workflows/ci.yml +name: CI + +on: + push: + branches: [main, develop] + pull_request: + branches: [main] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: pnpm/action-setup@v2 + with: + version: 8 + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'pnpm' + - run: pnpm install + - run: pnpm run typecheck + - run: pnpm run lint + - run: pnpm run build +``` + +## 环境变量配置 + +项目使用以下环境变量: + +### 开发环境 (.env.local) +```env +# Supabase 配置 +VITE_SUPABASE_URL=https://your-project.supabase.co +VITE_SUPABASE_ANON_KEY=your-anon-key + +# 应用配置 +VITE_APP_NAME=织机(Demo版) +VITE_APP_VERSION=1.0.0 +``` + +### 生产环境 +生产环境变量通过 CI/CD 流水线注入,请勿在代码中硬编码敏感信息。 + +## API 文档 + +### Supabase 数据库 Schema + +#### 核心表结构 + +**companies** - 公司表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID | 主键 | +| name | TEXT | 公司名称 | +| role | app_role | 角色:purchaser/textile/washing | +| address | TEXT | 地址 | +| contact_phone | TEXT | 联系电话 | + +**production_plans** - 生产计划表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID | 主键 | +| plan_code | TEXT | 计划编号 | +| product_name | TEXT | 产品名称 | +| color | TEXT | 颜色 | +| fabric_code | TEXT | 坯布码 | +| color_code | TEXT | 色号 | +| purchaser_id | UUID | 采购商ID | +| target_quantity | INTEGER | 计划产量 | +| completed_quantity | INTEGER | 已完成产量 | +| status | plan_status | 状态 | + +**plan_factories** - 计划工厂关联表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID | 主键 | +| plan_id | UUID | 计划ID | +| factory_id | UUID | 工厂ID | +| factory_type | factory_type | 工厂类型 | + +**inventory_records** - 入库记录表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID | 主键 | +| plan_id | UUID | 计划ID | +| warehouse_id | UUID | 仓库ID | +| quantity | DECIMAL | 入库数量 | +| rolls | INTEGER | 入库匹数 | +| operator_id | UUID | 操作人ID | + +**product_inventory_records** - 产品出入库记录表 +| 字段 | 类型 | 说明 | +|------|------|------| +| id | UUID | 主键 | +| product_id | UUID | 产品ID | +| company_id | UUID | 公司ID | +| batch_no | TEXT | 批号 | +| rolls | INTEGER | 匹数 | +| meters | DECIMAL | 米数 | +| notes | TEXT | 备注 | +| operator_id | UUID | 操作人ID | +| created_at | TIMESTAMPTZ | 创建时间 | + +### 枚举类型 + +```sql +-- 应用角色 +app_role: purchaser | textile | washing + +-- 计划状态 +plan_status: pending | producing | completed + +-- 工厂类型 +factory_type: textile | washing + +-- 仓库类型 +warehouse_type: raw_fabric | fabric | finished | yarn + +-- 结款状态 +payment_status: pending | completed +``` + +### RLS 策略 + +所有表启用 Row Level Security,基于 company_id 进行数据隔离: + +```sql +-- 示例:production_plans 表 RLS 策略 +CREATE POLICY company_select_plans ON production_plans + FOR SELECT USING (purchaser_id = get_user_master_company_id()); + +CREATE POLICY company_insert_plans ON production_plans + FOR INSERT WITH CHECK (purchaser_id = get_user_master_company_id()); +``` + +### API 调用示例 + +**查询计划列表** +```typescript +const { data } = await supabase + .from('production_plans') + .select('*') + .eq('purchaser_id', companyId) + .order('created_at', { ascending: false }); +``` + +**创建入库记录** +```typescript +await supabase.from('inventory_records').insert({ + plan_id: planId, + warehouse_id: warehouseId, + quantity: quantity, + rolls: rolls, + operator_id: userId +}); +``` + +**实时订阅库存更新** +```typescript +supabase + .channel('inventory_changes') + .on('postgres_changes', { + event: '*', + schema: 'public', + table: 'inventory_records' + }, callback) + .subscribe(); +``` + +## 许可证 + +MIT License + +## 技术支持 + +如有问题或建议,请联系开发团队。 + +--- + +## 常见问题与解决方案 + +### Q1: 页面白屏/无法加载 +**可能原因**: +- Webpack 热更新失败 +- 类型检查错误阻止构建 +- 环境变量配置错误 + +**解决方案**: +```bash +# 1. 清除缓存重新构建 +rm -rf node_modules dist +pnpm install +pnpm run dev + +# 2. 检查类型错误 +pnpm run typecheck + +# 3. 检查环境变量 +cat .env.local +``` + +### Q2: 数据无法保存/读取 +**可能原因**: +- RLS 策略限制 +- 网络连接问题 +- 权限不足 + +**解决方案**: +```bash +# 1. 检查云服务状态 +meoo-cli cloud status + +# 2. 检查 RLS 策略 +meoo-cli cloud query --sql "\dt" +meoo-cli cloud query --sql "SELECT * FROM pg_policies WHERE tablename = 'your_table'" + +# 3. 检查用户权限 +meoo-cli cloud query --sql "SELECT * FROM profiles WHERE id = 'your-user-id'" +``` + +### Q3: 移动端样式错乱 +**可能原因**: +- Tailwind 配置问题 +- 响应式断点使用错误 +- CSS 未正确加载 + +**解决方案**: +```bash +# 1. 检查 Tailwind 配置 +cat tailwind.config.js + +# 2. 检查 PostCSS 配置 +cat postcss.config.js + +# 3. 重新构建样式 +pnpm run build +``` + +### Q4: 实时数据不更新 +**可能原因**: +- Realtime 未启用 +- 订阅配置错误 +- 网络问题 + +**解决方案**: +```typescript +// 检查订阅是否正确设置 +const channel = supabase + .channel('table_changes') + .on('postgres_changes', { + event: '*', + schema: 'public', + table: 'your_table' + }, (payload) => { + console.log('Change received:', payload); + }) + .subscribe((status) => { + console.log('Subscription status:', status); + }); +``` + +### Q5: 开发服务器预览加载失败(沙箱环境) +**现象**: +- `pnpm run dev` 启动正常,端口 3015 在监听 +- 但浏览器预览无法加载,curl 请求超时 + +**根本原因**: +- 沙箱环境网络限制:`net.ipv4.conf.all.forwarding = 0` +- Webpack devServer 配置不当:`host: '127.0.0.1'` 无法在沙箱访问 + +**解决方案**: +```javascript +// webpack.config.js - 正确配置 +devServer: { + port: 3015, + host: '0.0.0.0', // 必须使用 0.0.0.0 + allowedHosts: 'all', + hot: true, + historyApiFallback: true, + // 不要配置 static 和 devMiddleware +} +``` + +**验证方法**: +```bash +# 1. 类型检查 +pnpm run typecheck + +# 2. 构建验证(关键) +pnpm run build + +# 3. 检查产物 +ls -la dist/ + +# 4. 启动服务器(沙箱中预览可能受限,但构建成功即可部署) +pnpm run dev +``` + +**注意**:沙箱环境中 curl 可能仍超时,这不影响实际部署。只要 `pnpm run build` 成功,应用即可正常发布。 + +--- + +## 性能优化建议 + +### 1. 图片优化 +- 使用 WebP 格式 +- 实现懒加载 +- 使用 CDN 加速 + +### 2. 代码分割 +```typescript +// 使用 React.lazy 进行代码分割 +const HeavyComponent = React.lazy(() => import('./HeavyComponent')); + +function App() { + return ( + }> + + + ); +} +``` + +### 3. 数据缓存 +```typescript +// 使用 SWR 或 React Query 进行数据缓存 +import useSWR from 'swr'; + +const { data, error } = useSWR('/api/data', fetcher, { + revalidateOnFocus: false, + dedupingInterval: 5000 +}); +``` + +### 4. 虚拟列表 +对于大数据列表,使用虚拟列表组件避免渲染过多 DOM 节点。 + +--- + +## 安全最佳实践 + +### 1. 输入验证 +```typescript +// 始终验证用户输入 +const validateInput = (input: string): boolean => { + // 防止 XSS + const xssPattern = / { + const checkAuth = async () => { + const { data: { user } } = await supabase.auth.getUser(); + if (!user) { + navigate('/login'); + } + }; + checkAuth(); +}, []); +``` + +--- + +## 贡献指南 + +### 提交代码 +1. Fork 项目 +2. 创建功能分支 (`git checkout -b feature/xxx`) +3. 提交更改 (`git commit -m 'Add xxx'`) +4. 推送到分支 (`git push origin feature/xxx`) +5. 创建 Pull Request + +### 代码规范 +- 使用 TypeScript 严格模式 +- 遵循 ESLint 规则 +- 编写单元测试 +- 更新相关文档 + +### 提交信息规范 +``` +feat: 新增功能 +fix: 修复 bug +docs: 文档更新 +style: 代码格式调整 +refactor: 重构代码 +test: 测试相关 +chore: 构建/工具相关 +``` diff --git a/assets/CLAUDE.md b/assets/CLAUDE.md new file mode 100644 index 0000000..daced9b --- /dev/null +++ b/assets/CLAUDE.md @@ -0,0 +1,65 @@ +# CLAUDE.md + +Behavioral guidelines to reduce common LLM coding mistakes. Merge with project-specific instructions as needed. + +**Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment. + +## 1. Think Before Coding + +**Don't assume. Don't hide confusion. Surface tradeoffs.** + +Before implementing: +- State your assumptions explicitly. If uncertain, ask. +- If multiple interpretations exist, present them - don't pick silently. +- If a simpler approach exists, say so. Push back when warranted. +- If something is unclear, stop. Name what's confusing. Ask. + +## 2. Simplicity First + +**Minimum code that solves the problem. Nothing speculative.** + +- No features beyond what was asked. +- No abstractions for single-use code. +- No "flexibility" or "configurability" that wasn't requested. +- No error handling for impossible scenarios. +- If you write 200 lines and it could be 50, rewrite it. + +Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify. + +## 3. Surgical Changes + +**Touch only what you must. Clean up only your own mess.** + +When editing existing code: +- Don't "improve" adjacent code, comments, or formatting. +- Don't refactor things that aren't broken. +- Match existing style, even if you'd do it differently. +- If you notice unrelated dead code, mention it - don't delete it. + +When your changes create orphans: +- Remove imports/variables/functions that YOUR changes made unused. +- Don't remove pre-existing dead code unless asked. + +The test: Every changed line should trace directly to the user's request. + +## 4. Goal-Driven Execution + +**Define success criteria. Loop until verified.** + +Transform tasks into verifiable goals: +- "Add validation" → "Write tests for invalid inputs, then make them pass" +- "Fix the bug" → "Write a test that reproduces it, then make it pass" +- "Refactor X" → "Ensure tests pass before and after" + +For multi-step tasks, state a brief plan: +``` +1. [Step] → verify: [check] +2. [Step] → verify: [check] +3. [Step] → verify: [check] +``` + +Strong success criteria let you loop independently. Weak criteria ("make it work") require constant clarification. + +--- + +**These guidelines are working if:** fewer unnecessary changes in diffs, fewer rewrites due to overcomplication, and clarifying questions come before implementation rather than after mistakes. diff --git a/assets/入库流程图.docx b/assets/入库流程图.docx new file mode 100644 index 0000000000000000000000000000000000000000..e44a439faa5bebd94eb7faa8b7b7beb8971e0097 GIT binary patch literal 14024 zcmb7r1$10V&aRo6nH@97%*@Qp%*-5fY{zy?F*7sA%*@Qp%xphrW_M>M|Nig1SLbwh zbzOZWUFo)3RVB$u0)s#S{AwXGTYSGh|J@)zI!3mJat^k3j&yP#WGEj45Py*E)P!T3 z0s;Vd0096X{GCkS&W_g2+A2+9Sf-mEp@sMoUj8f$xd0O0T00&^27;1#g19=>+AwAz z!5ZP!6DNGhZnwUMczo;(15H;D-#gn`p~OA4KpiIw>1VtJZ?x`@kR6Vw)cxY!Ru#5r z_=7Fb<`%!YS+KIigKUDV6|S=u)`l-^lbapdSZ1h6Y4yPOfJ@wy&1oo&Q(K07yO5*R z;Twsfvj!HDS+%~ zm9zD*Y;=A&l7*Z$O&6OARw}!EpD^(9pemRM`iTnF+ZtA!ct;@vL_F769r#4d5qril zSOV_Vz~`;{Jwy1$BJDexJsBc(A~|2Kd{dAEH4mF3Hw{(_p#;ZN$Y7`=5A!y7K*sBj zrEgx4$24W8FQ64PP*r!A1$}fUN}4&cW~I|c!mFhd+{NQcONZ56(u`|$fRZWs#asio zx+Q!u=oo`Ge-?aQWc{a6plz#5EI*85|6vrwzZ=ES*1`CfRU%_YC4d=Fg6;#(>5X`$ z^93V<4^foDp`im%d$q-EmOI5w#1xikgKT>08ZW@T0M|PT5SQ~R^h}nbP_-K+9mZbI zn#8m3g!0!=&EkxTg95Pl@yOG|gkUueYUeLqznM}i2IM6Np#2EoKXvn@+G5{L4mM2N z&9nyUb0je)AJ;T9vzB3C@q&m={^3YYcE?1M?5xS%!M>en1<)uXhAderML%o@cyk>d zsv_1&WyCXO4vByx2!-^WnXM4?VcC*j^4gTY$coL_pr9$m9Mv_B=0Tx!1vyTs@6t^R zJH!|!k+)-iH7CwNiC4Z?+n&#!#BDLk0d^$d0>_Vbxh%fM?mcs!-#%?7g~$BCc?wzp z>r6p_mZ-(0>G-r`m`P+P&-fd~%kO!<(09)P=1rs#^KcdmP(I3>c~O(t=bASdVc^Mh3|n&_69htQ<@c_hFf{56d9` z!!pi}PPW#+Y*QD@Z<|7o5qutZhyU#yWSb9(NK6^HES6ThW+Kxr`5Qwo#R3GU`9kfx zj;MCSZRK!2?&&i?Doqw0P>i%e~t)CIr-Fa*p{7&(S7OoyO@p^P7)@>oO*S}Is( z0g~1zkI%(9=j4tIlq|Xo5#pONmYE;W^c6~CNx1EO7tZ{FbNZ+{POEpHR zaXIj&g~J&G>xK1+CmUb@in4Hag!rBWrO%oq z2_rrr0s!yZ6_V&0lWOv}!I|~-)obo_5Jr^2=^0j{w>?0<*tEXR8Qnd14~}G`&e(qH<#-C&jU9pFq}>ZeZfS%3Hnc+-g6;3S z2DC&m?J{t8cTwKZ#05TCv5L@D$Ps*lQnU`b`Ag9${Vl(iTUV8U7|1moobT>bLSs}|VR@k;u|MjbUUM{*T^(-sT&VhDyX4j>?6^FWdpEe}g$S6gJKM0oT8;&o zu)5nL(uLAKaQ2^ZwhA^8`nw5bf(2i=aJB@gdNMoE#0PXafNVfE?&xs<8@bs;0c_wG z5&f2d-9Lsd@IHnPK89q25d9#LND={sJKu#Z8p7WqA&~&3*PYe^>^Msb&{qwbc_FUJu^$l?zJVq4cH0fIG2pxI5q*IEE z6lTd)(26$1?liO6bsWW0fm++c(i)7K8xN&UwEt<8JkaO55R`_zy;|+$b@L3d=ZPRZ zxeF)tDXfLzM;NX(8t3Qt?Fz5A{Y!7hS3A$`w9W(RD=ft<#mEk<<#Ev7oa!$Lgq?~D ze1fKYB=i_$kbRM`ow|?5T8v3l%ufS9{o6wT<(aO@KJ3GUR)C@{Z1@fw`VJ)`NCB1$ z+sT`Z^hDYZ@XlI5?hX&!$$d*}mywxw_wg2^xRoIa zv!@a(CE+H^x0h86vO`OBaI1knC{`?tI4OMS-pi*7jS8&IJWv=K_jN`**l$WS*l$Jn zdyItyA3cv^ewXfdh(n8o%sAx=ezFqHKpB-Pu(Wy=$UQk`gJ3;MW#BYD5#=zOKILUH zGkC01tIW9G=BhO6HCPVy7Hp?#H`W8AOPh(ot<{eU&znL0=+I{8$UObnI7U_L8wA4u zuPk5sDN4`fD}bLIakVQI2x*DmddPLa6E5y0Xm6HilAWksG5L3HO3v{aubk+-c7-}7b-2DAL*;4=i9`){e9;I&@eT7}mIq zhE?3+?gk*Y|)uXLu32By`%Vn1tyOB@Md2oF*y*3kiNPZHfb%%Sz3I^Aw=t581^wS$r0V!z_hr{heV$DvM*Mz2YYyTDp*4?h$hbb67l$p(~&3sQvUX`t8ud3=sW`~4`rRocs68IicTQh*2UUMs~%k#WziR<%o3S^ifFIt$=yoaW*Hc(Bw z?{Tt)@B7}MWb=M|8`FRTclRWa4_X#E&HGUN8reF^a+}CN+bsCW*UFRSrnfUbclp^} z{YuGJ?!LIR{$!J)(xj*L1kQcerwN?Gd>%#48YWE(MJ@8yBEL~e=ma@ae;sRRS63zV z$H^}y@l7sL9w{7q#l1EW4mIiZ%RdCKcm4C3U2xly>ouO-Ik5?>_T@2QFUHxUvo%c& zh!ZI;y7yT}p6?jzyU|q)M7?4XP4Dl&=9j01ga+EGIIUNV;3xyHg8@XDV=2DPh`4y3 zXfMr%xlHU9^!b1~f@K6eXyV4Q0JJB%sSg3Y&VoR&w$811!DheNtL#ka=qYB<5VVxi zjlNqZCeizQ+XD(b&*_-2G(1HiS1Hrh9zGH#{W44L0~JU`UGE zEMs~t-r=iHb6MyaB>i7>eW?b+r~S7%tlNav5>4woTvR!pFQ^~%fS2e=3&cI>WxxoE zD3i5hpNE(SZ#?njI>&3$Fx4*m0nShlFGcoZ&m^1Phh#Om{MT!Kg5QMVJ zF^egce@E-tD$t1rnUt00&Xt(ysSdC=X~^lmd>}d0oB3>BJHio~rKd!dqcYcIWKm~; z^0WOQ%a_FFB9&?h;@g}kj`>C-IH`261@dIic(#7HGRR^}Vvop$*?j)?J$18_X5MgG zf3(#&`;3M>Js2bsS1uJq9A}c#6_lCl47l4&4ua=S<^(tHvvRv*x9($mr;DEbAe6LI zCML9=EqYxta~~xGS;EQ1Qa!hsr6ZqlLm2nmR1RXXM}ZN6nM+NAL|lNOd;K8^d;gM` zyadcrDVTJO4fTmr>eo~tAwc6cX0#pwam$v<4w5R+y`zX~_nbNlBX?L3N6QMBkTNloEnDU-LYs&@wA{CL!L6Thwbevdz8liaY7-Y(dv$w((cCv6 z0GJDZ&T>Org)?o0E^~u9*3dB&KHGHi@j+vAfXN4g&cjdiN9zj9{booGm#7KtI7={W za~m07eSr{E>BT zD^|P_tiJptQb^$;eVJAI2;8lHJ*mtK$@@hVShZ&Xx;wOGhA;gL8!oFJ3pk>Lbg{V4 z*f1IX4Gism4 zdQ4|SAR*%d07FJi_KNOgqZQWAso)9KHaNr@7Mef|V2y$|3TPn17bf9197E_G4H(Sw7NgS*MAOq|SDD>C{$l^s^(uu^!h zJb}~O2Tdia(jUTTJe2%1aiFH_V`kz2)T`5=(G0pDG58&`yQ0vp9R`XB!WjBOv;1aA z^uUTxa`#a8m1g!lw~cAm`u8oV=h|+EYxEn@)FS85IRk7^B7;UJ#>0!G9xxPnn;%5c zI!JWixKZH_DzHxhL|-p!lY3o5=nhB^$w&q^4bPxpexjv)qMNn5>vboN*V;d80&2)D z@0O#QJN&$91C~2Bx}_R6j`l8J3eUNkHNTlS{Rl6hU2V2Xlgh7_t=w>6EGq9ZGkl={ z=Mtymf@9p@dO|XEu{dLb$`ZRL#j{x(DoLhGF{TW?>x?23rH+e9Bzq8M$eN4{#OxYaE6$V50h72gDa5LpPK4A0$MFHWMEnPe`?0u1vc zl+9-aV?2(t6e5&z9S$qRC1tBH4@~(_|1W$YjmYoF%ugG7jC*|CR$Yv&MNA;t8F`4HfQ4 zF%yBf>z#*K0kADD?2YC)nZ?D%UyB)VmXro}A&3cv0tt!#LqGSutE`nu)2sm@+aa)& zw4vf1s!4z#U8znw9Y!*zPNQ|sEdGhvZ^&88wt4=5S;8C3PlOm^`eEx}WwG3O33Lu9 zIRkICHO<0#y;JjvuI#Zz*SaCQIB=dIr@cOSy}Y5B8NGrZOjoyCH=IMhdGBDJAkemS z)ALrNV9oIY>$^g%?A(OBlJ(6I=+7@-F}W|g8a(4)RT}*0XuauxE@Py?8?5C#J4z z^SazRnY^wMZBOO)@wz)8(`+Vo^}Fgx@VtFR_vL;4{BXQ~uEYH}l%&%Ojs5-{o$-2i zoajSv^KExxe*Det?PQlVC|7*7kk8M9?rKls6K-UJ>ePx5<;?0lGY$UdP)M~>CJ`^4Du$VilFdP4y^UxO|NSGEu;ly9)<1zwDKv#&>l z9mGHJd_*4%*o($-GU3%l@XKWG7KXft$eH9&QOP%*(MijMN0>)`SCPAIl9h^Q*9D!p zN$K^Hk^zNuOlL*A%p*8&cl%CXEAg1=7t@l{VqR7NZKzw)CSeV)b{k~5dD=_1fA)qo z=)$Q4-+Wp*GCQNki*xm5be*K;?VEr(9-a>Ae9@;5Ie*n#n;EzD#i1jkhcT@~{LE?H zdOr5De8!MfOSg}L57l~&R(a6#e4zk&$n=SJTyGl1rT2!i>>xmIEYQFut!(%r=y+7eg_Xkj#zLcx=_x|Bci;u)&)rA$ zF(H9~*=#DbXh<$Ncjxn8Dbh9>^Py)sQoGh;Dp6=upky4IUp~XjY|~I08c*nV>7BKh5}}kS$U0W z|4nk54Wz`}hlWk-?&N)f6;*bN*GFVG9j8%bR?{*n35f0bCZD?go1zGBNM(J$~<%hLH2By9o#vo!YYhh@# zY?&;Mr*#2xNl-h63Q=pu_+ zxyg~jbbc=ldZ4TtO(zn4nRgWJ6(98ZE&~?78;{ZD>ir?0^OjC~XEb|V8n z4xGLP{4MtVIHPj5Hnws4;~zs?TNYdWN1LwsJ8*R^YctV|>!O*SQS*t4?ul9Mh}pi) zE(Un1pY`G=r*YITU?Z%!Vqy^T0tqBi1q=v`omGzy4BLvvy0^Lu`!f`(c7^7Yuq;`h z&^M}&XNnxxu6^3ai=I}`+p)1u8dC=LVh@#~quV=bz&D55YA>HkN#8cYX(? zt5$q-qTAso$?9w?iP}Z#DD>wG@zO8`Fo#Mu1j&zVk?Tv?`D_yyQ>(fuJ*!!rBF!-{ zbZJc)+{UbynZ}01YT?ni>4#(f7~Ut@S^Og4KilwCZ>RBAofcLEbJnxe7>(gi8l-Pf zirmiTLoMvVa0mJ|hfMq4N@`K1-gumcVT6u|fUcxjxKpb_;>2}I8*CL_PudENeESC2 zc1LC$GG;q57u(qphDlB~jT|;NwRpI5d#@Op*JEO0u!)mbBHe}*ND6YJ(VOj#wuRfG zvfS<)UW=SqTr3eBu{ZOg5OvNnfwqo=&VI(9J*WGaT-3~@g&7;J-`zfZxBFaN2xQAL z_`)EmX;c<%#t}d6{M8AbMDY<%mndNv+;K3eBBCDiYu=Onk&P9CsY04>+hKQuNF3g} zE;vWGZzfH1>Dk5NL922D#^Kog5og5glx&@A#Z4E#_eOM^<@7R7fqDO^dP=X?m5#s|lYydTwc-Nd2dX0K9~2IH3c zw8~!cHQ0+!rDD5MEX?_RD@(@FOG3si`-eq#MHRFJ8=UIhSxm2e_o!P*NeRH&m2U}J z2cOVF`9JAkQE3wJs{c5jDlomhWmM;S*tLUGVor~a$piJ!(g9SS{4QU|!%;T=HtI&N z>ie{%$Qa_`E(lz-;Pth==CtH z@Bh88)43f-CQwVH$x11Ipo<&lxcU6XwS>*afJIgja!Mlne6VNSs0w6^5EC5JVWQ0G3GQ~$X3ubuYk>x`3_8&R8j zRPK`%5fg*UL|67F$RDLYD?Pl!ma8^9y=t6^dl!!;uh6X_BI8Xx0HIl+9K}dY4aN&! z>Ioy_R*fe%Nv0Pl-R4j2; zui`x%>6*(>bi4$rc;wc5LjLeAzQw+`<#!9cxRKDlENSK3nmd#SDVFN&;-1cE{XAzX zu8$5#0Ue>2QIhArEgnWa>aCqv4{BEbRsii}BL7w=wOJB!8`=IYDZLmwv;{`7BI?!i zvSneO(kB6KD{c=*d#9>zl|mmI$NKc*-^QiOtzoKxxKhZwBK%pyvJ_F>O(gw{z+It? zI_`>5CEP?tVp}W;qZiiD9I1VQCF(T*mCJju-z12%nyc@rN|WW0aE#C|^N~Fs8jK4O zNUKR-3%@MwWkGE%KpuzG5kB$|D0!VezhLA>%~5SnpA6KdYrVgtMda}d;ul1I-A4F; z39q|NA`qZt?AJ%7*Ir^uoKMii$j`cUhYcoJ^>WAqnb;;a@@A?Io4-hnb zGL$gWoG#!}^aDe1jF8m#kvO!anyTlt)g>`Gf`T$`=RhR=lKlq&knJ~h1*vp9}7-jfDF zjt%iEcg14Gwq)Ua>tMMp=g}x?rA-R1Qpfl(@fBr-kbq8>e=@&ThcK&euBYKL8z9Y+sS$K#lGfAzfTDvi; zVbETH5f0ZjZCjE^IwcBcye}x53=r+AM#)r(BB@{VqJDej&^fZRji+VfmLkxZ&pm&Pv1g>eje>ZDEL9(%{SHOgiT-bGoJ7 z^AmC$es2LZP5O?WHNk6)X<96v ziZ=Hpa|^MU{|ohd+vY=H$k@;4Ow4VnqqLNCUgjSr3toM*bQrt1)vd+nBZ1jwzSndG ztG+YvhvIPuCtUr_WIS}{F?JKaJePF6ne|HMJD#U*qML7uTYMxz#1W2lp4fdXk;Ph} zmDE{wg+baA+Zq^*&0c~{rT%*S)KCdd{4hFb!qQP5y~QA+?-(KN z=udz;+yKxsavqDWiXk`ntz=+EE5z>xEPv5cL+!NRFUhtf^zh@y@_ZKaR0uD(Vo1+n zkADQ8B__XO?ik1_Uu8(fLVJxxG9aG<3#|cV=CjBuv(Z%FG?MDOK_IuM@B$OiSWQTz zXbG;`xFhcS@zmYVHjFg*l_G_$I?v^>82C1rUXg=o{!9Bbe9T@GVI>*rHdx6O&b(|| zQ&{WKBxF*(#t#;qai(gbhDMS1mqq|J<_-A+ETb-H~98_2zY{uVM^DApsg4!V%RFF(4dhclIew|dQlu0!!1 zHZ{IO&qioF1I~^Ma|u*OD6eZxNJa&}O!}Ep%IyH%ilEOZ5cA9XIp0ILRsm75YkZo% zrhYP_t&A`zfi?)M#@Quht$aGp8XbjVQCz1rp}YKnBQKayRg?*OOGrx|M%ame1JUji z%VJ*z&WZ@c$B9%|%d$TL|GN1Y^dXYGHm!jq4&E$3hb^N0{Eg9-f z^dG(s8FlZVenh9w>9?new;3#eMCGWgMVf3rJr}}HH;mUvw_QA;7A@TQe1=x_9+BF3 z)2p;A{LCnbLgKeWw7Mchd%~Yb*QOamp1>^lRO}5+pl@M?Gz$1b^^gFBZ(BWUxoi{i zW29O0E=bi=i`pLmQ4G^6YuWtp267J0p+o7lBuU6DH|6-Q!>fJkt)`r$S>eA9RlnU} zz9>O2xSj`+M&|oCqVtJ4h;1WGdy?b;GDnMUO*EGHm0{f^8K9}G$4&MdxsGSY6;&^& zy9y};dWi*~`|`w;2M+65_fsar!&g`12TjvQ_wCV!pLMvXS2uwj=n~6rEg;nuX+u*f z)YtM->33=@%o||p=~sa%p-b_AeE|xvhT>Y41l##yuT-jv*O{6LWIa1P=Q=noUt2WV zW`qU-%JzB+7MY?BSU%~8g2#kvh`t2y#i8M6b~s8c#ud~^$R|Xp%B(ua{RfObRXVQH z){KtsMxY~KJzPF{#&+5HJA|zAy7qDR{yfK?3Xp6KzEFs@ZxEatwpQZCBQ47K>(xUw zWU_!;N!!GO<1n7uHJYCHY)pWuI?C=5* zqNHvfr(Fna@B%6fFtd@(ivkiIvXew^_0lOIL&-?lpMWk8@)wo`Bqm_nh|nFfG-%24 z78U5Y)-8#}DuYn&YAFC7$Vv!;jHEZUlzx_j0v<)lve>2nB(T#^x`p8r)a?iEGFj+i zl^U+#KJ5T?B(H4Y8gI4}t^jrHNHa7hLsvux4HIQqr4n159@n%hz);eA>&ZOI9&w;J zYU&M-Il-Aq>iSYswF7Z0fu5e$cRi(BDUWM^Hr`LmCiKmii&VB*gP0N=)mP{;7LM~a z6oU0m5(+W^Wc|uZbM&yPCVDb;FQg#fV>bxLmb75P8TX;woM?ci#({Gk zCCI47eZPrP^oJWG_=D|v!N%jgJwp$ z7{hDe2vrhUX;yAfM%PA=*G4*wvG9k3xV011(eR&gK`BR(2lTm8J(gDYmKLP(B zD$)^Gs3qP5-~oB;2~}=K8}CUoS`o=z?AHne%VX|=>cTI?Q1i5@rV)`Ynh0}VX*TM3xHO@VDk!J z>bCZUd+8eOklMYB9nOfsupz%`5v7~LyN%qezfi4BaZGJ*U$S`ywP`y8^oQ%-)R@+o zKD~3Av?0)@b)Lr>wb8StoZxpv47hX6M(fy9rS(Hk^QPlTt?)Rz5Fj2)8hLv8Bo{>5 z7EHT8F(V)x%)f26JjNR~L==D@MvOTotKn4a#9u%udP%}gkEsW?IlZJ<#kQ#@w9GFubYZI;=q#D9fOT6>N*mt?Mk2 zaVpW4V{nJug0-bRqgy*_8y0Er&^YVG9}@`S6V+s^I z9q^NFsay^Q#l!3c_4zDlI8ShQ7-kzkjFe} zp*EcTH3}mI+`unroVe2{(hQ2k9=Q+~?Se5!(Up~cw3ztt4?Ah}W>n^S7`jAeBRA43 z63;kp#$+CRiqgWq^t6FN_X*ify|K)4jfh2WwJinO)Eh=UowazGDUq@K{xhuC79oWzC6+-SuR zP}{)|*(4s2Ps}PNon%Q_ekdgM4|o?ba9%%#pIW<@KtN5=;4d9Mu;X9xleyM_0z3-D z6Hp}hDHf35b#Y8y$gVu3!zP6w$cF$^mY$^m&<#K&zjAgCkzFH;paE-^LyLsLQRq6Y zgzoF(wW^Dbt9Oazte=fES8CJ=&5>jY=j14;f2W=VuvgJn&>Z=RKg@oG71Bgn&@h`0c8J! zDZckcN1@H?W!aP#*yDQpOJOnZ)BKkDxT9O3c1TvV;WG6N@I-%koxKGbzgE~zuZV$B zAjhX@V?+_XV0xA#(?y%vS>0cIga7^L>EmDtK-a<8>i4{LKq4ai*Nl zq5VZ!7Q6b99E}ik4!TY_;?25DNi=0;D|C=1=IuwAyb8Pe@=dDF+cVI&v9i4N!hz9r z%*%1W!_g_wE$h4W1yq7EY(NWK-;+$|^_jjsG!c_Wpx{OBh7cyp=x*;*7}yTucTemZ za#|j$gXHbB@9OtE^s0ohbZIOkAzDK-gP|VLxP!{t>jO8_z&~ON7O+6gEz9{&WU8#* zmN{$7W}WbFI%D?3P#sFi$C5Yi{m}A2m8Nk z@;`@o|4p3(C}48?A8IW8P$$mE=O3vEe-i$EQ}$o_9k=MFhv_>HYz>_Bwx5b*5J}VN z%bbtl_S+}-O)|um#2#Lj1zD<-`zc8qEZenyFF56L&*Xj^ncRl~qa+Lo6&P`f;Q@R; zdc2BEZh)vT1V|<+EQOIf-OHtpmh72U z`drI>B(2VKW#VOBMp(TnF>IyKxgY5sbBL6n6Rca>21%hP)o2-MR;Rzm;o1^KZ2*{xaPUo z7x16^)n0?*J_a9&-zXm$!@m;0H3dIB|HoFfu9CZ*v7`1MS;60jZMx_Ygzw{@;A0w; z4Gyx4DS;w*5@?{G0Lz&GOMC{I5IQebm^HvlCU={_#$G(rVr(mAa{Vak3WAU(ol3la|9aL{Y4|F$Ps845LrSpzX; zD5%1P{4rSWV#ozs3ZlB4OrF1z-x2s*C_($8k zw)T>9TpzO~-_G`_Rh6{M(8F0FPI2pP;Um4wOvtO?IVbK(m{pFf1OY-7m(C0R9nR04 zX{g%j3w$8!GjXm%QE~| z6a6{C|L$piP4H(OOf&7jeai1lfA>7U5PvdV{}1Mm-^u@t*{_P6KijXOn_um3^*R5% z$=@qTTf7R>!F75B7B!8h>KAz_PF#UI7$?x#r>mB}rt9*n)f5HD#{qTRg z>0inAf3{yYjrH;MU#a)MZuq|&<(Jd?v;BgA(*MIV{Vwe9PV5)(PfgMO6Zk(|+V3;{ zemeEnOc_}J65;Q2{0{#;RQwArPxUYOKLW?!@xLdA{e{o@s7Cl7X=1;_f3IHn3(oy- zo&QzK@F)2HMf-doKfFJ9|2qrhpU>&#-@nHg!(tvP5JVyO1p^`#Oj`9$VC8%a+PE zG);pnDO(yuG{($3)%ShRci!`!^FDgd>AmLs?)zMGU-x~k-*x@3|MH^~>5~Arp0=(w z00IF3i17pHqktv=VP=LfgCP)R2n!2@g_VOW@Fm_wHwA6&06Y zOUue1RMghhH#}-=di>;BM`u@e&-30F_~DV)gg3;uq^armGao+A&do2ZtbSQr|GKgH zjq;rr2mt@2#kl??_D{Uv3|>sk%wT3HV|xG)Qvd^CI5R{{j)h0V1bW$%!H zu%?|&Lcw&I?~2b547u;%;$G(cIWk>4BA z0Ri9biSBHgq5(9Z#528lUJ$FZOABEP`pp37K)^~V9k_kG<>$0Y!8p9K-letM_!3A_ zJPP>TKnJWKhPGXLK8K2Dp{ta@xWN-9@51pZ5QK^P?*?cqKdv-+@vPV5&3I>8J&}{F zK7S?%s;7=H(E@%q#OQ!!k1C!EH{5)@_4WFI+JoZHw4+4MqIhY*L<{lT5xZ@>m-A+b z3v`_7Vnk_)@|{@JImxgg%mS+DKiI8IYynd|>X*Q<#(<+Ziyf}>=?8@}gaq=wfk*@9m~ESF${^AWzbxx0r* zn)`hE?5`t~PuB;q8}-X;NJv1JD;kx}=Ib81Db#x^cQ0JyH`25*EjGkme5R0_JSYmM zURm~vxqj^E*7gNST$XyCO(R*g_N;v8wF85Q51Q=U+F>wiOAW@>)qfS2yNGhXGxD+3 zp#B13QT@+cYoA`268YKV3N5X<>)P@&A049+CKuvRMe)E+p-FqV9abfhZ^cEaTc_3M z#uBnYBfqe`2gW6u>*A6`g)Cp>H*iol1PHlU^2;vg+9+XS&A=Qa#K{&(dkvddu36&G z%m{scUt+3so0z)( za3uIkUWkjH5^FP;=}JrLB$>;V5^^tG@n){|%Tu0Y$?oppWnRE<_Dxkt;d1<{{y>bt zh0I1(SL6J`>H$M(gAG@<0r8fq;ZUV{TaMrZ#m;?Bl1tQtk7whinDvbKpU$wHI)ZVY z+_K$(lwylO1=d9OlhmG%`TsSlX>0I5aiIG)xa8tA6&bV#&^ll-Fj{#Kg#8bX#ZF&?sdo$NI!l0VrSqoL4 zt~@dpvk+MyIEPiP82j|V3YO#2?opoQna`PlwIW)vYq;@rM-tgBDj&S zUWXaIFz|+g=Zr{axE-{W#-N!UYF=OGgem@4VkSryg)PGH-2D-8e>n8Eot{kXfxA8h zzND0pp+4!JyWXh-K^lYJhL|aQJXCK-Ap-R31|2y6bWjyk>1t&y{%*8WHEq7h^B}@| zqUy$MEH!XiKc8>3LpjbVXTefViuhp|eyW@&uu#Xh(dAb7;)uWo8h#^mxJy9j$)%F3 z$l6+kKzW3FC5uqYN z^RzxL)%FC#F>L^9d^un=)o)bg>1NCG)WsvDYV?n?a^g$G)}a>s<<%ko`siEc_8WSa zz4Jpm#;Q;9-UuQa*4J{}WJeNCb+S5=E(y2C*Yq5h$ntB21*5M!sr^y>_2oZa#mY+7Fs256!DJ#zQM5WgXYc;)ZV_g?4ua=WA9VshS75C1zf1s z%?H!Z{cfLH^|1VK|6-ttCc^tZY@F5n0Jram6FOIfYcqN}A(?l6=%Q=gyGpBriz1UW zpQ7H6JaE=-|Fv0_GePTfEgU!Wq{&y28;_fL0t;;Qif8YV1$U_fb^4s;IiX}&KVRusO%Uz*e3H@Xx)^5!och-_NG_IdUo> z=8kR*9(CA0kH7E50-MkD+>4;c_f&4}wTtXN)@Av0Q)|jbqr~m`5lNR1E{78?>C!Up zq|{DQC%qHb+6uJkK*W*jhWc$33v!%+Eh)g5%@M9`qWxK&q}#)6sda&(l*yS;JnBNI zR%v(7hL@#3pSf8xLx=|NEZi4i1VX1MbinGmCRg@Xtu2k0Gc!xO@H;m)gIlNit7VRZQL7rlY1{7u*+A@ z@$HwR^;Rk@JR^zY)n}p(^PP7{OkUZR38)DtBF|<{UOPTXiyELBGH5EDa$NJqJuFr*fRE$*;uqa!a7iR12{vAwz$_R`!rCl_Rp)PY@b8u z0IbPx)Wn^zh$K!3V)i-hVf=G@%}q+118zs`pS2%8MkcHC*WIW^6&oLNlJ|It+G}69 zI1y>kv|=2;F0M4=X?~4=`7GI86zfU{?9V?^K%NOT5=K9Z9eU_+FEEz9X*6apyD@~| zCBu^18qJ2r^j)sLRd_Xn0F~>;HG+F*h2^ZTAXYC)q*dp9NDADj@F*nKDFmKI+Em4; zCYQ~A%VKdrCln(OT|1O(BP4O4MmWfSt|&}{yyA=96AuKt78PqR0c_({e4 z2!1WnkK(yxJH%98F!!@94xsf&cgLmTN;2Emj#aGAE19*4XN?Pk`njsBzzR!LeU7f; zS7Hvtk_<=TnlbM&^EXzX{5N>DYV^CkBRZWD*I`;lBDXFh$M4QDrKwZiQz5$BneJj~ zvN^gNNr4i3xa@?0|9H6AUd!se`;f!BPd{dE$mtH=0!Td z6*lKK+)6H{AwUyD7opU{VX>`iit`g2LkvqM4AR7J7EOYSsqTpyVHcGDbmHHwk zXb@_~$i4Rd&DHrH>;L}>DH=f3oP|*g$fz?`=xeo1~QbvdFeM=3$sCz1N><>J0Z zhsvIhV6%UIFl>PDidXzQv3o{YLzfT{TGDlnlNGwp`3LNieA`5=n6E6*SP1nhx_?~X zW2aLnBUCciiJWgcUdb>-hcwXy8lUs|N?~ou=f@`Vw4~xrin-=V4w)H_h&LivGFD&N zJrL9eR3{U?6U|!q3cV6&2pAihBqsvhGH2aJ*gjEUq@AsPA<{1FJ4qQuKbwU`ndSeu zxh3j}akr!hBQbh}`qfqGB^^-9E9q(2^RyOUuS~@V`_&{z+ zTdf>aDC={+E+}hC)oeMT{oRq**`|bg?9Ev~bx7yAZDU_#UqHPvG%c({g{n#i3JfMT zd8Ib-bf7~e_^7x{~N4%2_|9*DN2Ovs@F z{@II$nBszBeE6Wryv~0`(G0^Gu9u<5ODV&4s5#VHfgd8s6y*F1(#uXx_rDq|`gs^t xlnuJcg_ZuU(fw`{VG>Cvp$fR@Z|Tio*6-r}Dcew4D7%yCu|;+Jgpio { + // 这里可以设置测试账号的登录状态 + // 实际项目中应该使用测试账号登录并保存状态 + + // 示例:登录流程 + // await page.goto('/login'); + // await page.fill('[name="username"]', 'test-user'); + // await page.fill('[name="password"]', 'test-password'); + // await page.click('button[type="submit"]'); + // await page.waitForURL('/purchaser'); + + // 保存登录状态 + // await page.context().storageState({ path: authFile }); +}); diff --git a/e2e/collaboration-workflow.spec.ts b/e2e/collaboration-workflow.spec.ts new file mode 100644 index 0000000..c6296dc --- /dev/null +++ b/e2e/collaboration-workflow.spec.ts @@ -0,0 +1,182 @@ +import { test, expect } from '@playwright/test'; + +/** + * 协作流程测试 + * 核心路径:采购商创建计划 → 纺织厂确认 → 入库 → 采购商看到库存 + * + * 注意:这些测试需要真实的数据库连接和测试账号 + * 在实际运行前需要配置测试环境 + */ + +test.describe('协作流程 - 采购商视角', () => { + test.beforeEach(async ({ page }) => { + // 登录采购商账号(需要配置测试账号) + await page.goto('/login'); + // await loginAsPurchaser(page); + }); + + test('采购商创建新计划', async ({ page }) => { + // 导航到新建计划页面 + await page.goto('/purchaser/plans/new'); + + // 验证页面加载 + await expect(page.locator('h1')).toContainText('新建纺织计划'); + + // 填写计划表单 + await page.fill('[name="productName"]', '测试产品'); + await page.fill('[name="color"]', '红色'); + await page.fill('[name="targetQuantity"]', '1000'); + + // 添加纱线配比 + await page.click('text=添加纱线'); + await page.fill('[name="yarnName"]', '测试纱线'); + await page.fill('[name="ratio"]', '100'); + + // 提交表单 + // await page.click('button[type="submit"]'); + + // 验证创建成功 + // await expect(page).toHaveURL(/.*purchaser\/plans/); + }); + + test('采购商查看计划列表', async ({ page }) => { + await page.goto('/purchaser/plans'); + + // 验证页面加载 + await expect(page.locator('h1')).toContainText('计划总览'); + + // 验证计划列表存在 + await expect(page.locator('[data-testid="plan-list"]')).toBeVisible(); + }); + + test('采购商查看库存', async ({ page }) => { + await page.goto('/purchaser/warehouse'); + + // 验证页面加载 + await expect(page.locator('h1')).toContainText('仓库管理'); + + // 验证库存列表 + await expect(page.locator('[data-testid="inventory-list"]')).toBeVisible(); + }); +}); + +test.describe('协作流程 - 纺织厂视角', () => { + test.beforeEach(async ({ page }) => { + // 登录纺织厂账号 + await page.goto('/login'); + // await loginAsTextileFactory(page); + }); + + test('纺织厂查看计划', async ({ page }) => { + await page.goto('/textile/plans'); + + // 验证页面加载 + await expect(page.locator('h1')).toContainText('计划总览'); + + // 验证计划列表 + await expect(page.locator('[data-testid="plan-list"]')).toBeVisible(); + }); + + test('纺织厂确认流程节点', async ({ page }) => { + await page.goto('/textile/plans'); + + // 找到待确认的计划 + const pendingPlan = page.locator('[data-testid="plan-card"]').first(); + await expect(pendingPlan).toBeVisible(); + + // 展开计划详情 + await pendingPlan.click(); + + // 点击确认按钮 + // await page.click('[data-testid="confirm-step-button"]'); + + // 验证确认成功 + // await expect(page.locator('[data-testid="step-completed"]')).toBeVisible(); + }); + + test('纺织厂坯布入库', async ({ page }) => { + await page.goto('/textile/plans'); + + // 找到进行中的计划 + const activePlan = page.locator('[data-testid="plan-card"]').first(); + await expect(activePlan).toBeVisible(); + + // 展开计划 + await activePlan.click(); + + // 填写入库信息 + // await page.fill('[data-testid="inbound-quantity"]', '100'); + // await page.fill('[data-testid="inbound-rolls"]', '10'); + + // 提交入库 + // await page.click('[data-testid="submit-inbound"]'); + + // 验证入库成功 + // await expect(page.locator('[data-testid="inbound-success"]')).toBeVisible(); + }); + + test('纺织厂查看坯布仓库', async ({ page }) => { + await page.goto('/textile/fabric-warehouse'); + + // 验证页面加载 + await expect(page.locator('h1')).toContainText('原坯布仓库'); + + // 验证库存列表 + await expect(page.locator('[data-testid="fabric-inventory"]')).toBeVisible(); + }); +}); + +test.describe('数据同步验证', () => { + test('入库后采购商实时看到库存更新', async ({ page, browser }) => { + // 这个测试需要两个浏览器上下文模拟两个角色 + const purchaserContext = await browser.newContext(); + const textileContext = await browser.newContext(); + + const purchaserPage = await purchaserContext.newPage(); + const textilePage = await textileContext.newPage(); + + try { + // 采购商查看初始库存 + await purchaserPage.goto('/purchaser/warehouse'); + // const initialInventory = await getInventoryCount(purchaserPage); + + // 纺织厂执行入库 + await textilePage.goto('/textile/plans'); + // await textilePage.click('[data-testid="inbound-button"]'); + // await textilePage.fill('[data-testid="quantity"]', '100'); + // await textilePage.click('[data-testid="submit"]'); + + // 等待实时同步 + await purchaserPage.waitForTimeout(2000); + + // 验证采购商看到更新后的库存 + // const updatedInventory = await getInventoryCount(purchaserPage); + // expect(updatedInventory).toBe(initialInventory + 100); + + } finally { + await purchaserContext.close(); + await textileContext.close(); + } + }); +}); + +test.describe('分享链接流程', () => { + test('通过分享链接导入计划', async ({ page }) => { + // 访问分享链接 + const shareToken = 'test-token'; + await page.goto(`/import?token=${shareToken}`); + + // 验证导入页面加载 + await expect(page.locator('h1')).toContainText('导入计划'); + + // 验证计划信息展示(不包含敏感信息) + await expect(page.locator('[data-testid="plan-code"]')).toBeVisible(); + await expect(page.locator('[data-testid="fabric-code"]')).toBeVisible(); + + // 确认导入 + // await page.click('[data-testid="confirm-import"]'); + + // 验证导入成功 + // await expect(page).toHaveURL(/.*textile\/plans/); + }); +}); diff --git a/e2e/core-workflow.spec.ts b/e2e/core-workflow.spec.ts new file mode 100644 index 0000000..a22b850 --- /dev/null +++ b/e2e/core-workflow.spec.ts @@ -0,0 +1,149 @@ +import { test, expect } from '@playwright/test'; + +test.describe('核心业务流程', () => { + test.beforeEach(async ({ page }) => { + // 每个测试前导航到登录页 + await page.goto('/login'); + }); + + test('采购商登录并查看仪表盘', async ({ page }) => { + // 验证登录页面加载 + await expect(page).toHaveURL(/.*login/); + await expect(page.locator('h1')).toContainText('登录'); + + // 由于需要真实账号,这里只验证页面结构 + await expect(page.locator('input[name="username"]')).toBeVisible(); + await expect(page.locator('input[name="password"]')).toBeVisible(); + await expect(page.locator('button[type="submit"]')).toBeVisible(); + }); + + test('角色选择页面', async ({ page }) => { + await page.goto('/role-select'); + + // 验证角色选择页面 + await expect(page.locator('h1')).toContainText('选择角色'); + await expect(page.locator('text=采购商/布行')).toBeVisible(); + await expect(page.locator('text=纺织厂')).toBeVisible(); + await expect(page.locator('text=水洗厂')).toBeVisible(); + }); + + test('注册页面表单验证', async ({ page }) => { + await page.goto('/register'); + + // 验证注册页面 + await expect(page.locator('h1')).toContainText('注册'); + + // 尝试提交空表单 + await page.click('button[type="submit"]'); + + // 验证表单验证(假设有必填验证) + await expect(page.locator('input[name="username"]')).toBeVisible(); + await expect(page.locator('input[name="password"]')).toBeVisible(); + await expect(page.locator('input[name="confirmPassword"]')).toBeVisible(); + }); + + test('页面导航和路由', async ({ page }) => { + // 测试路由导航 + await page.goto('/login'); + await expect(page).toHaveURL(/.*login/); + + // 测试 404 回退 + await page.goto('/non-existent-page'); + // 应该显示登录页或 404 + await expect(page.locator('body')).toBeVisible(); + }); + + test('响应式布局 - 移动端适配', async ({ page }) => { + // 设置移动端视口 + await page.setViewportSize({ width: 375, height: 667 }); + await page.goto('/login'); + + // 验证移动端布局 + await expect(page.locator('body')).toBeVisible(); + + // 检查是否有移动端特定的样式或元素 + const body = page.locator('body'); + await expect(body).toBeVisible(); + }); + + test('网络状态提示组件', async ({ page }) => { + await page.goto('/login'); + + // 模拟离线状态 + await page.context().setOffline(true); + + // 等待离线提示出现 + await page.waitForTimeout(500); + + // 恢复网络 + await page.context().setOffline(false); + }); +}); + +test.describe('页面加载性能', () => { + test('首页加载时间', async ({ page }) => { + const startTime = Date.now(); + await page.goto('/login'); + const loadTime = Date.now() - startTime; + + // 验证页面在合理时间内加载 + expect(loadTime).toBeLessThan(5000); + + // 验证关键元素渲染 + await expect(page.locator('h1')).toBeVisible(); + }); + + test('资源加载无错误', async ({ page }) => { + const errors: string[] = []; + + page.on('console', (msg) => { + if (msg.type() === 'error') { + errors.push(msg.text()); + } + }); + + page.on('pageerror', (error) => { + errors.push(error.message); + }); + + await page.goto('/login'); + await page.waitForLoadState('networkidle'); + + // 允许一些预期的错误(如 API 调用) + const unexpectedErrors = errors.filter(e => + !e.includes('supabase') && + !e.includes('api') && + !e.includes('favicon') + ); + + expect(unexpectedErrors).toHaveLength(0); + }); +}); + +test.describe('无障碍测试', () => { + test('登录页表单可访问性', async ({ page }) => { + await page.goto('/login'); + + // 检查表单元素是否有正确的标签 + const usernameInput = page.locator('input[name="username"]'); + const passwordInput = page.locator('input[name="password"]'); + + await expect(usernameInput).toHaveAttribute('placeholder'); + await expect(passwordInput).toHaveAttribute('placeholder'); + + // 检查按钮可点击 + const submitButton = page.locator('button[type="submit"]'); + await expect(submitButton).toBeEnabled(); + }); + + test('键盘导航', async ({ page }) => { + await page.goto('/login'); + + // 使用 Tab 键导航 + await page.keyboard.press('Tab'); + await expect(page.locator('input[name="username"]"]')).toBeFocused(); + + await page.keyboard.press('Tab'); + await expect(page.locator('input[name="password"]"]')).toBeFocused(); + }); +}); diff --git a/functions/data-consistency-check/index.ts b/functions/data-consistency-check/index.ts new file mode 100644 index 0000000..aabeac7 --- /dev/null +++ b/functions/data-consistency-check/index.ts @@ -0,0 +1,176 @@ +// 数据一致性校验与修复 Edge Function +// 定期检查并修复 completed_quantity 与 inventory_records 不一致的问题 + +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; + +Deno.serve(async (req) => { + // CORS 预检 + if (req.method === 'OPTIONS') { + return new Response('ok', { + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', + }, + }); + } + + const corsHeaders = { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }; + + try { + const body = await req.json(); + const { action, planId } = body; // action: 'check' | 'fix' | 'check-all' + + // 获取认证信息 + const authHeader = req.headers.get('Authorization') || ''; + + // 创建管理员客户端(绕过 RLS) + const supabaseAdmin = createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, + ); + + // 检查单个计划的数据一致性 + const checkPlanConsistency = async (pid: string) => { + // 获取计划信息 + const { data: plan } = await supabaseAdmin + .from('production_plans') + .select('id, plan_code, completed_quantity, target_quantity') + .eq('id', pid) + .maybeSingle(); + + if (!plan) return null; + + // 计算入库记录总和 + const { data: records } = await supabaseAdmin + .from('inventory_records') + .select('quantity') + .eq('plan_id', pid); + + const inventoryTotal = (records || []).reduce((sum, r) => sum + (r.quantity || 0), 0); + const completedQuantity = plan.completed_quantity || 0; + const diff = inventoryTotal - completedQuantity; + + return { + planId: pid, + planCode: plan.plan_code, + completedQuantity, + inventoryTotal, + diff, + isConsistent: diff === 0, + }; + }; + + // 检查所有计划的数据一致性 + const checkAllPlans = async () => { + const { data: plans } = await supabaseAdmin + .from('production_plans') + .select('id'); + + const results = []; + for (const plan of (plans || [])) { + const result = await checkPlanConsistency(plan.id); + if (result && !result.isConsistent) { + results.push(result); + } + } + return results; + }; + + // 修复不一致的数据 + const fixInconsistency = async (pid: string) => { + const check = await checkPlanConsistency(pid); + if (!check || check.isConsistent) { + return { fixed: false, message: '数据已一致,无需修复' }; + } + + // 以 inventory_records 为准修复 completed_quantity + const { data: records } = await supabaseAdmin + .from('inventory_records') + .select('quantity') + .eq('plan_id', pid); + + const inventoryTotal = (records || []).reduce((sum, r) => sum + (r.quantity || 0), 0); + + const { error } = await supabaseAdmin + .from('production_plans') + .update({ completed_quantity: inventoryTotal }) + .eq('id', pid); + + if (error) { + return { fixed: false, error: error.message }; + } + + return { + fixed: true, + planId: pid, + oldValue: check.completedQuantity, + newValue: inventoryTotal, + diff: check.diff, + }; + }; + + let result; + + switch (action) { + case 'check': + if (!planId) { + return new Response( + JSON.stringify({ error: 'check 操作需要提供 planId' }), + { status: 400, headers: corsHeaders } + ); + } + result = await checkPlanConsistency(planId); + break; + + case 'check-all': + result = await checkAllPlans(); + break; + + case 'fix': + if (!planId) { + return new Response( + JSON.stringify({ error: 'fix 操作需要提供 planId' }), + { status: 400, headers: corsHeaders } + ); + } + result = await fixInconsistency(planId); + break; + + case 'fix-all': + const inconsistentPlans = await checkAllPlans(); + const fixResults = []; + for (const plan of inconsistentPlans) { + const fixResult = await fixInconsistency(plan.planId); + fixResults.push(fixResult); + } + result = { + totalChecked: inconsistentPlans.length, + fixed: fixResults.filter(r => r.fixed).length, + details: fixResults, + }; + break; + + default: + return new Response( + JSON.stringify({ error: '无效的操作类型,支持: check, check-all, fix, fix-all' }), + { status: 400, headers: corsHeaders } + ); + } + + return new Response( + JSON.stringify({ success: true, data: result }), + { headers: corsHeaders } + ); + + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + return new Response( + JSON.stringify({ error: '服务器错误: ' + message }), + { status: 500, headers: corsHeaders } + ); + } +}); diff --git a/functions/fabric-warehouse-inbound/index.ts b/functions/fabric-warehouse-inbound/index.ts new file mode 100644 index 0000000..c2ae298 --- /dev/null +++ b/functions/fabric-warehouse-inbound/index.ts @@ -0,0 +1,148 @@ +// 坯布入库事务处理 Edge Function +// 确保 completed_quantity 和 inventory_records 数据一致性 + +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; + +Deno.serve(async (req) => { + // CORS 预检 + if (req.method === 'OPTIONS') { + return new Response('ok', { + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST, OPTIONS', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', + }, + }); + } + + const corsHeaders = { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }; + + try { + const body = await req.json(); + const { + planId, + quantity, + rolls, + warehouseId, + warehouseLocation, + operatorId, + pricePerMeter, + } = body; + + // 参数验证 + if (!planId || !quantity || quantity <= 0) { + return new Response( + JSON.stringify({ error: '缺少必要参数或数量无效' }), + { status: 400, headers: corsHeaders } + ); + } + + // 获取认证信息 + const authHeader = req.headers.get('Authorization') || ''; + + // 创建管理员客户端(绕过 RLS) + const supabaseAdmin = createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, + ); + + // 开始事务处理 + // 1. 获取当前计划信息 + const { data: plan, error: planError } = await supabaseAdmin + .from('production_plans') + .select('completed_quantity, target_quantity, purchaser_id') + .eq('id', planId) + .maybeSingle(); + + if (planError || !plan) { + return new Response( + JSON.stringify({ error: '获取计划信息失败: ' + (planError?.message || '计划不存在') }), + { status: 404, headers: corsHeaders } + ); + } + + // 2. 计算新的已完成数量 + const newCompletedQuantity = (plan.completed_quantity || 0) + quantity; + + // 3. 检查是否超过目标数量 + if (newCompletedQuantity > plan.target_quantity) { + return new Response( + JSON.stringify({ error: `入库数量超过剩余数量 (${plan.target_quantity - (plan.completed_quantity || 0)}米)` }), + { status: 400, headers: corsHeaders } + ); + } + + const now = new Date().toISOString(); + + // 4. 更新计划的 completed_quantity + const { error: updateError } = await supabaseAdmin + .from('production_plans') + .update({ completed_quantity: newCompletedQuantity }) + .eq('id', planId); + + if (updateError) { + return new Response( + JSON.stringify({ error: '更新计划数量失败: ' + updateError.message }), + { status: 500, headers: corsHeaders } + ); + } + + // 5. 创建入库记录 + const insertData: any = { + plan_id: planId, + quantity: quantity, + rolls: rolls || 0, + warehouse_location: warehouseLocation || '', + operator_id: operatorId, + price_per_meter: pricePerMeter || 0, + created_at: now, + }; + + if (warehouseId) { + insertData.warehouse_id = warehouseId; + } + + const { data: inventoryRecord, error: insertError } = await supabaseAdmin + .from('inventory_records') + .insert(insertData) + .select() + .single(); + + if (insertError) { + // 如果入库记录创建失败,需要回滚 completed_quantity + // 注意:这里简化处理,实际生产环境应使用数据库事务 + await supabaseAdmin + .from('production_plans') + .update({ completed_quantity: plan.completed_quantity }) + .eq('id', planId); + + return new Response( + JSON.stringify({ error: '创建入库记录失败: ' + insertError.message }), + { status: 500, headers: corsHeaders } + ); + } + + // 6. 返回成功结果 + return new Response( + JSON.stringify({ + success: true, + data: { + inventoryRecord, + completedQuantity: newCompletedQuantity, + progress: Math.round((newCompletedQuantity / plan.target_quantity) * 100), + }, + }), + { headers: corsHeaders } + ); + + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + return new Response( + JSON.stringify({ error: '服务器错误: ' + message }), + { status: 500, headers: corsHeaders } + ); + } +}); diff --git a/index.html b/index.html new file mode 100644 index 0000000..87abcfa --- /dev/null +++ b/index.html @@ -0,0 +1,56 @@ + + + + + + 织机 - 以AI为梭,织天地经纬 + + + + + +
+ + diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 0000000..c60ad1a --- /dev/null +++ b/jest.config.js @@ -0,0 +1,34 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'jsdom', + roots: ['/src'], + testMatch: ['**/__tests__/**/*.test.ts', '**/__tests__/**/*.test.tsx'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json'], + setupFilesAfterEnv: ['/src/__tests__/setup.ts'], + moduleNameMapper: { + '^@/(.*)$': '/src/$1', + }, + collectCoverageFrom: [ + 'src/**/*.{ts,tsx}', + '!src/**/*.d.ts', + '!src/__tests__/**', + '!src/index.tsx', + '!src/App.tsx', + ], + coverageThreshold: { + global: { + branches: 30, + functions: 30, + lines: 30, + statements: 30, + }, + }, + transform: { + '^.+\\.tsx?$': ['ts-jest', { + tsconfig: { + jsx: 'react-jsx', + esModuleInterop: true, + }, + }], + }, +}; diff --git a/migrations/20260523_052639_create_base_tables.sql b/migrations/20260523_052639_create_base_tables.sql new file mode 100644 index 0000000..7fea0d0 --- /dev/null +++ b/migrations/20260523_052639_create_base_tables.sql @@ -0,0 +1,46 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 自定义枚举类型 +CREATE TYPE public.app_role AS ENUM ('purchaser', 'textile', 'washing'); +CREATE TYPE public.factory_type AS ENUM ('textile', 'washing'); +CREATE TYPE public.plan_status AS ENUM ('pending', 'producing', 'completed'); +CREATE TYPE public.step_type AS ENUM ('confirm', 'yarn_purchase', 'dyeing', 'machine_start', 'fabric_warehouse'); +CREATE TYPE public.step_status AS ENUM ('pending', 'active', 'completed'); +CREATE TYPE public.warehouse_type AS ENUM ('raw_fabric', 'fabric', 'finished', 'yarn'); +CREATE TYPE public.payment_status AS ENUM ('pending', 'completed'); + +-- 公司表 +CREATE TABLE public.companies ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + name TEXT NOT NULL, + role public.app_role NOT NULL, + address TEXT, + contact_phone TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 用户配置表 +CREATE TABLE public.profiles ( + id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, + username TEXT UNIQUE NOT NULL, + phone TEXT, + company_id UUID REFERENCES public.companies(id) ON DELETE SET NULL, + is_master BOOLEAN NOT NULL DEFAULT true, + master_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 公司成员表 +CREATE TABLE public.company_members ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + company_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, + user_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + role TEXT NOT NULL DEFAULT 'member', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(company_id, user_id) +); + diff --git a/migrations/20260523_052709_create_plan_tables.sql b/migrations/20260523_052709_create_plan_tables.sql new file mode 100644 index 0000000..db3aba2 --- /dev/null +++ b/migrations/20260523_052709_create_plan_tables.sql @@ -0,0 +1,48 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 生产计划表 +CREATE TABLE public.production_plans ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_code TEXT UNIQUE NOT NULL, + product_name TEXT NOT NULL, + color TEXT NOT NULL, + fabric_code TEXT NOT NULL, + color_code TEXT NOT NULL DEFAULT '01', + remark TEXT, + purchaser_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, + target_quantity INTEGER NOT NULL DEFAULT 0, + completed_quantity INTEGER NOT NULL DEFAULT 0, + yarn_usage_per_meter DECIMAL DEFAULT 0, + production_price DECIMAL DEFAULT 0, + status public.plan_status NOT NULL DEFAULT 'pending', + start_time TIMESTAMPTZ, + created_by UUID REFERENCES auth.users(id) ON DELETE SET NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 计划与工厂关联表 +CREATE TABLE public.plan_factories ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES public.production_plans(id) ON DELETE CASCADE, + factory_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, + factory_type public.factory_type NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE(plan_id, factory_id) +); + +-- 纱线配比表 +CREATE TABLE public.yarn_ratios ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES public.production_plans(id) ON DELETE CASCADE, + yarn_name TEXT NOT NULL, + ratio DECIMAL NOT NULL DEFAULT 1, + amount_per_meter DECIMAL NOT NULL DEFAULT 0, + total_amount DECIMAL NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + diff --git a/migrations/20260523_052735_create_warehouse_tables.sql b/migrations/20260523_052735_create_warehouse_tables.sql new file mode 100644 index 0000000..f1f2e00 --- /dev/null +++ b/migrations/20260523_052735_create_warehouse_tables.sql @@ -0,0 +1,40 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 仓库表 +CREATE TABLE public.warehouses ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + company_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, + name TEXT NOT NULL, + type public.warehouse_type NOT NULL, + location TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 入库记录表 +CREATE TABLE public.inventory_records ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES public.production_plans(id) ON DELETE CASCADE, + warehouse_id UUID NOT NULL REFERENCES public.warehouses(id) ON DELETE CASCADE, + quantity DECIMAL NOT NULL DEFAULT 0, + warehouse_location TEXT, + operator_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 纱线库存表 +CREATE TABLE public.yarn_stock ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + company_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, + warehouse_id UUID REFERENCES public.warehouses(id) ON DELETE SET NULL, + name TEXT NOT NULL, + spec TEXT, + quantity DECIMAL NOT NULL DEFAULT 0, + min_stock DECIMAL NOT NULL DEFAULT 0, + unit TEXT NOT NULL DEFAULT 'kg', + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + diff --git a/migrations/20260523_052800_create_process_and_payment_tables.sql b/migrations/20260523_052800_create_process_and_payment_tables.sql new file mode 100644 index 0000000..50f5cf9 --- /dev/null +++ b/migrations/20260523_052800_create_process_and_payment_tables.sql @@ -0,0 +1,32 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 流程节点表 +CREATE TABLE public.plan_process_steps ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES public.production_plans(id) ON DELETE CASCADE, + step_type public.step_type NOT NULL, + status public.step_status NOT NULL DEFAULT 'pending', + timestamp TIMESTAMPTZ, + operator_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, + notes TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 结款记录表 +CREATE TABLE public.payments ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES public.production_plans(id) ON DELETE CASCADE, + from_company_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, + to_company_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, + amount DECIMAL NOT NULL DEFAULT 0, + quantity DECIMAL NOT NULL DEFAULT 0, + price_per_meter DECIMAL NOT NULL DEFAULT 0, + status public.payment_status NOT NULL DEFAULT 'pending', + paid_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + diff --git a/migrations/20260523_052842_enable_rls_all_tables.sql b/migrations/20260523_052842_enable_rls_all_tables.sql new file mode 100644 index 0000000..e57b672 --- /dev/null +++ b/migrations/20260523_052842_enable_rls_all_tables.sql @@ -0,0 +1,73 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 启用所有表的 RLS +ALTER TABLE public.companies ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.company_members ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.production_plans ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.plan_factories ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.yarn_ratios ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.warehouses ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.inventory_records ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.yarn_stock ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.plan_process_steps ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.payments ENABLE ROW LEVEL SECURITY; + +-- companies: 所有人可查看,登录用户可创建/修改自己公司的 +CREATE POLICY anon_select_companies ON public.companies FOR SELECT USING (true); +CREATE POLICY anon_insert_companies ON public.companies FOR INSERT WITH CHECK (true); +CREATE POLICY anon_update_companies ON public.companies FOR UPDATE USING (true); + +-- profiles: 登录用户可查看所有profile,只能修改自己的 +CREATE POLICY anon_select_profiles ON public.profiles FOR SELECT USING (true); +CREATE POLICY anon_insert_profiles ON public.profiles FOR INSERT WITH CHECK (true); +CREATE POLICY users_update_own_profile ON public.profiles FOR UPDATE USING (auth.uid() = id); + +-- company_members: 所有人可查看和创建 +CREATE POLICY anon_select_company_members ON public.company_members FOR SELECT USING (true); +CREATE POLICY anon_insert_company_members ON public.company_members FOR INSERT WITH CHECK (true); + +-- production_plans: 所有人可查看,登录用户可创建/修改 +CREATE POLICY anon_select_plans ON public.production_plans FOR SELECT USING (true); +CREATE POLICY anon_insert_plans ON public.production_plans FOR INSERT WITH CHECK (true); +CREATE POLICY anon_update_plans ON public.production_plans FOR UPDATE USING (true); +CREATE POLICY anon_delete_plans ON public.production_plans FOR DELETE USING (true); + +-- plan_factories: 所有人可查看和操作 +CREATE POLICY anon_select_plan_factories ON public.plan_factories FOR SELECT USING (true); +CREATE POLICY anon_insert_plan_factories ON public.plan_factories FOR INSERT WITH CHECK (true); +CREATE POLICY anon_delete_plan_factories ON public.plan_factories FOR DELETE USING (true); + +-- yarn_ratios: 所有人可查看和操作 +CREATE POLICY anon_select_yarn_ratios ON public.yarn_ratios FOR SELECT USING (true); +CREATE POLICY anon_insert_yarn_ratios ON public.yarn_ratios FOR INSERT WITH CHECK (true); +CREATE POLICY anon_delete_yarn_ratios ON public.yarn_ratios FOR DELETE USING (true); + +-- warehouses: 所有人可查看和操作 +CREATE POLICY anon_select_warehouses ON public.warehouses FOR SELECT USING (true); +CREATE POLICY anon_insert_warehouses ON public.warehouses FOR INSERT WITH CHECK (true); +CREATE POLICY anon_update_warehouses ON public.warehouses FOR UPDATE USING (true); + +-- inventory_records: 所有人可查看和操作 +CREATE POLICY anon_select_inventory ON public.inventory_records FOR SELECT USING (true); +CREATE POLICY anon_insert_inventory ON public.inventory_records FOR INSERT WITH CHECK (true); + +-- yarn_stock: 所有人可查看和操作 +CREATE POLICY anon_select_yarn_stock ON public.yarn_stock FOR SELECT USING (true); +CREATE POLICY anon_insert_yarn_stock ON public.yarn_stock FOR INSERT WITH CHECK (true); +CREATE POLICY anon_update_yarn_stock ON public.yarn_stock FOR UPDATE USING (true); + +-- plan_process_steps: 所有人可查看和操作 +CREATE POLICY anon_select_process_steps ON public.plan_process_steps FOR SELECT USING (true); +CREATE POLICY anon_insert_process_steps ON public.plan_process_steps FOR INSERT WITH CHECK (true); +CREATE POLICY anon_update_process_steps ON public.plan_process_steps FOR UPDATE USING (true); + +-- payments: 所有人可查看和操作 +CREATE POLICY anon_select_payments ON public.payments FOR SELECT USING (true); +CREATE POLICY anon_insert_payments ON public.payments FOR INSERT WITH CHECK (true); +CREATE POLICY anon_update_payments ON public.payments FOR UPDATE USING (true); + diff --git a/migrations/20260523_052936_create_test_users.sql b/migrations/20260523_052936_create_test_users.sql new file mode 100644 index 0000000..8b8c18b --- /dev/null +++ b/migrations/20260523_052936_create_test_users.sql @@ -0,0 +1,66 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +INSERT INTO auth.users ( + instance_id, id, aud, role, email, + encrypted_password, email_confirmed_at, + created_at, updated_at, + confirmation_token, recovery_token, + email_change_token_new, email_change, + raw_app_meta_data, raw_user_meta_data, + is_super_admin +) VALUES ( + '00000000-0000-0000-0000-000000000000', + 'a0000001-0000-0000-0000-000000000001'::uuid, 'authenticated', 'authenticated', + 'purchaser@meoo.local', + crypt('123456', gen_salt('bf')), + NOW(), NOW(), NOW(), + '', '', '', '', + '{"provider":"email","providers":["email"]}'::jsonb, + '{"username":"purchaser"}'::jsonb, + false +); + +INSERT INTO auth.users ( + instance_id, id, aud, role, email, + encrypted_password, email_confirmed_at, + created_at, updated_at, + confirmation_token, recovery_token, + email_change_token_new, email_change, + raw_app_meta_data, raw_user_meta_data, + is_super_admin +) VALUES ( + '00000000-0000-0000-0000-000000000000', + 'a0000002-0000-0000-0000-000000000002'::uuid, 'authenticated', 'authenticated', + 'textile@meoo.local', + crypt('123456', gen_salt('bf')), + NOW(), NOW(), NOW(), + '', '', '', '', + '{"provider":"email","providers":["email"]}'::jsonb, + '{"username":"textile"}'::jsonb, + false +); + +INSERT INTO auth.users ( + instance_id, id, aud, role, email, + encrypted_password, email_confirmed_at, + created_at, updated_at, + confirmation_token, recovery_token, + email_change_token_new, email_change, + raw_app_meta_data, raw_user_meta_data, + is_super_admin +) VALUES ( + '00000000-0000-0000-0000-000000000000', + 'a0000003-0000-0000-0000-000000000003'::uuid, 'authenticated', 'authenticated', + 'washing@meoo.local', + crypt('123456', gen_salt('bf')), + NOW(), NOW(), NOW(), + '', '', '', '', + '{"provider":"email","providers":["email"]}'::jsonb, + '{"username":"washing"}'::jsonb, + false +); + diff --git a/migrations/20260523_053030_seed_companies_and_profiles.sql b/migrations/20260523_053030_seed_companies_and_profiles.sql new file mode 100644 index 0000000..e9fb2dd --- /dev/null +++ b/migrations/20260523_053030_seed_companies_and_profiles.sql @@ -0,0 +1,37 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 创建公司 +INSERT INTO public.companies (id, name, role, address, contact_phone) VALUES + ('11111111-1111-1111-1111-111111111101', '示例布行', 'purchaser', '浙江省杭州市', '13800138000'), + ('11111111-1111-1111-1111-111111111102', '示例纺织厂', 'textile', '浙江省绍兴市', '13800138001'), + ('11111111-1111-1111-1111-111111111103', '示例水洗厂', 'washing', '广东省广州市', '13800138002'); + +-- 创建 profiles +INSERT INTO public.profiles (id, username, phone, company_id, is_master) VALUES + ('a0000001-0000-0000-0000-000000000001', 'purchaser', '13800138000', '11111111-1111-1111-1111-111111111101', true), + ('a0000002-0000-0000-0000-000000000002', 'textile', '13800138001', '11111111-1111-1111-1111-111111111102', true), + ('a0000003-0000-0000-0000-000000000003', 'washing', '13800138002', '11111111-1111-1111-1111-111111111103', true); + +-- 创建公司成员 +INSERT INTO public.company_members (company_id, user_id, role) VALUES + ('11111111-1111-1111-1111-111111111101', 'a0000001-0000-0000-0000-000000000001', 'master'), + ('11111111-1111-1111-1111-111111111102', 'a0000002-0000-0000-0000-000000000002', 'master'), + ('11111111-1111-1111-1111-111111111103', 'a0000003-0000-0000-0000-000000000003', 'master'); + +-- 创建仓库 +INSERT INTO public.warehouses (id, company_id, name, type, location) VALUES + ('22222222-2222-2222-2222-222222222201', '11111111-1111-1111-1111-111111111101', '原坯布仓库', 'raw_fabric', 'A区-01'), + ('22222222-2222-2222-2222-222222222202', '11111111-1111-1111-1111-111111111101', '坯布仓库', 'fabric', 'B区-01'), + ('22222222-2222-2222-2222-222222222203', '11111111-1111-1111-1111-111111111101', '成品仓库', 'finished', 'C区-01'), + ('22222222-2222-2222-2222-222222222204', '11111111-1111-1111-1111-111111111102', '原料纱仓库', 'yarn', '绍兴厂区'); + +-- 创建纱线库存 +INSERT INTO public.yarn_stock (company_id, warehouse_id, name, spec, quantity, min_stock, unit) VALUES + ('11111111-1111-1111-1111-111111111102', '22222222-2222-2222-2222-222222222204', '棉纱32S', '32支纯棉', 500, 100, 'kg'), + ('11111111-1111-1111-1111-111111111102', '22222222-2222-2222-2222-222222222204', '涤纶纱', '75D/36F', 30, 50, 'kg'), + ('11111111-1111-1111-1111-111111111102', '22222222-2222-2222-2222-222222222204', '氨纶', '40D', 200, 80, 'kg'); + diff --git a/migrations/20260523_053110_seed_plans_and_related.sql b/migrations/20260523_053110_seed_plans_and_related.sql new file mode 100644 index 0000000..23264c8 --- /dev/null +++ b/migrations/20260523_053110_seed_plans_and_related.sql @@ -0,0 +1,37 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 创建示例生产计划 +INSERT INTO public.production_plans (id, plan_code, product_name, color, fabric_code, color_code, remark, purchaser_id, target_quantity, completed_quantity, yarn_usage_per_meter, production_price, status, start_time, created_by) VALUES + ('33333333-3333-3333-3333-333333333301', 'PLAN-2026-001', '水洗棉花糖', '白色', 'SX', '01', '60*40', '11111111-1111-1111-1111-111111111101', 10000, 6500, 100, 10, 'producing', '2026-01-15T00:00:00Z', 'a0000001-0000-0000-0000-000000000001'), + ('33333333-3333-3333-3333-333333333302', 'PLAN-2026-002', '纯棉汗布', '黑色', 'CM', '02', '60双股', '11111111-1111-1111-1111-111111111101', 8000, 0, 120, 12, 'pending', NULL, 'a0000001-0000-0000-0000-000000000001'), + ('33333333-3333-3333-3333-333333333303', 'PLAN-2026-003', '磨毛布', '灰色', 'MM', '03', '40*40', '11111111-1111-1111-1111-111111111101', 5000, 5000, 80, 8, 'completed', '2026-01-01T00:00:00Z', 'a0000001-0000-0000-0000-000000000001'); + +-- 创建计划与工厂关联 +INSERT INTO public.plan_factories (plan_id, factory_id, factory_type) VALUES + ('33333333-3333-3333-3333-333333333301', '11111111-1111-1111-1111-111111111102', 'textile'), + ('33333333-3333-3333-3333-333333333302', '11111111-1111-1111-1111-111111111102', 'textile'), + ('33333333-3333-3333-3333-333333333303', '11111111-1111-1111-1111-111111111102', 'textile'); + +-- 创建纱线配比 +INSERT INTO public.yarn_ratios (plan_id, yarn_name, ratio, amount_per_meter, total_amount) VALUES + ('33333333-3333-3333-3333-333333333301', '棉纱32S', 1, 10, 100000), + ('33333333-3333-3333-3333-333333333301', '涤纶纱', 1, 10, 100000), + ('33333333-3333-3333-3333-333333333301', '氨纶', 8, 80, 800000); + +-- 创建流程节点 +INSERT INTO public.plan_process_steps (plan_id, step_type, status, timestamp, operator_id) VALUES + ('33333333-3333-3333-3333-333333333301', 'confirm', 'completed', '2026-01-15T09:30:00Z', 'a0000001-0000-0000-0000-000000000001'), + ('33333333-3333-3333-3333-333333333301', 'yarn_purchase', 'completed', '2026-01-16T14:20:00Z', 'a0000002-0000-0000-0000-000000000002'), + ('33333333-3333-3333-3333-333333333301', 'dyeing', 'completed', '2026-01-18T10:00:00Z', 'a0000002-0000-0000-0000-000000000002'), + ('33333333-3333-3333-3333-333333333301', 'machine_start', 'active', '2026-01-20T08:00:00Z', 'a0000002-0000-0000-0000-000000000002'), + ('33333333-3333-3333-3333-333333333301', 'fabric_warehouse', 'pending', NULL, NULL); + +-- 创建结款记录 +INSERT INTO public.payments (plan_id, from_company_id, to_company_id, amount, quantity, price_per_meter, status) VALUES + ('33333333-3333-3333-3333-333333333301', '11111111-1111-1111-1111-111111111101', '11111111-1111-1111-1111-111111111102', 65000, 6500, 10, 'pending'), + ('33333333-3333-3333-3333-333333333303', '11111111-1111-1111-1111-111111111101', '11111111-1111-1111-1111-111111111102', 50000, 5000, 10, 'pending'); + diff --git a/migrations/20260523_124703_add_company_id_to_inventory.sql b/migrations/20260523_124703_add_company_id_to_inventory.sql new file mode 100644 index 0000000..cccbfa0 --- /dev/null +++ b/migrations/20260523_124703_add_company_id_to_inventory.sql @@ -0,0 +1,14 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +ALTER TABLE product_inventory_records ADD COLUMN company_id UUID REFERENCES companies(id) ON DELETE CASCADE; + +-- 更新RLS策略 +CREATE POLICY company_select_product_inventory_records ON product_inventory_records FOR SELECT USING (true); +CREATE POLICY company_insert_product_inventory_records ON product_inventory_records FOR INSERT WITH CHECK (true); +CREATE POLICY company_update_product_inventory_records ON product_inventory_records FOR UPDATE USING (true); +CREATE POLICY company_delete_product_inventory_records ON product_inventory_records FOR DELETE USING (true); + diff --git a/migrations/20260523_124742_create_product_yarn_ratios_table.sql b/migrations/20260523_124742_create_product_yarn_ratios_table.sql new file mode 100644 index 0000000..ce16d24 --- /dev/null +++ b/migrations/20260523_124742_create_product_yarn_ratios_table.sql @@ -0,0 +1,24 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 创建产品纱线配比表 +CREATE TABLE IF NOT EXISTS product_yarn_ratios ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + product_id UUID NOT NULL REFERENCES products(id) ON DELETE CASCADE, + yarn_name TEXT NOT NULL, + ratio DECIMAL(5,2) NOT NULL DEFAULT 0, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 启用RLS +ALTER TABLE product_yarn_ratios ENABLE ROW LEVEL SECURITY; + +-- RLS策略 +CREATE POLICY company_select_product_yarn_ratios ON product_yarn_ratios FOR SELECT USING (true); +CREATE POLICY company_insert_product_yarn_ratios ON product_yarn_ratios FOR INSERT WITH CHECK (true); +CREATE POLICY company_update_product_yarn_ratios ON product_yarn_ratios FOR UPDATE USING (true); +CREATE POLICY company_delete_product_yarn_ratios ON product_yarn_ratios FOR DELETE USING (true); + diff --git a/migrations/20260523_131935_add_production_price_to_products.sql b/migrations/20260523_131935_add_production_price_to_products.sql new file mode 100644 index 0000000..d76c3bd --- /dev/null +++ b/migrations/20260523_131935_add_production_price_to_products.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +ALTER TABLE products ADD COLUMN IF NOT EXISTS production_price DECIMAL(10,2) DEFAULT 0; diff --git a/migrations/20260523_151243_add_rolls_to_inventory_records.sql b/migrations/20260523_151243_add_rolls_to_inventory_records.sql new file mode 100644 index 0000000..4f808d3 --- /dev/null +++ b/migrations/20260523_151243_add_rolls_to_inventory_records.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +ALTER TABLE inventory_records ADD COLUMN IF NOT EXISTS rolls INTEGER DEFAULT 0; diff --git a/migrations/20260523_162407_drop_old_rls_policies.sql b/migrations/20260523_162407_drop_old_rls_policies.sql new file mode 100644 index 0000000..5a6539f --- /dev/null +++ b/migrations/20260523_162407_drop_old_rls_policies.sql @@ -0,0 +1,40 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 删除现有宽松的 RLS 策略 +DROP POLICY IF EXISTS anon_select_companies ON public.companies; +DROP POLICY IF EXISTS anon_insert_companies ON public.companies; +DROP POLICY IF EXISTS anon_update_companies ON public.companies; +DROP POLICY IF EXISTS anon_select_profiles ON public.profiles; +DROP POLICY IF EXISTS anon_insert_profiles ON public.profiles; +DROP POLICY IF EXISTS users_update_own_profile ON public.profiles; +DROP POLICY IF EXISTS anon_select_company_members ON public.company_members; +DROP POLICY IF EXISTS anon_insert_company_members ON public.company_members; +DROP POLICY IF EXISTS anon_select_plans ON public.production_plans; +DROP POLICY IF EXISTS anon_insert_plans ON public.production_plans; +DROP POLICY IF EXISTS anon_update_plans ON public.production_plans; +DROP POLICY IF EXISTS anon_delete_plans ON public.production_plans; +DROP POLICY IF EXISTS anon_select_plan_factories ON public.plan_factories; +DROP POLICY IF EXISTS anon_insert_plan_factories ON public.plan_factories; +DROP POLICY IF EXISTS anon_delete_plan_factories ON public.plan_factories; +DROP POLICY IF EXISTS anon_select_yarn_ratios ON public.yarn_ratios; +DROP POLICY IF EXISTS anon_insert_yarn_ratios ON public.yarn_ratios; +DROP POLICY IF EXISTS anon_delete_yarn_ratios ON public.yarn_ratios; +DROP POLICY IF EXISTS anon_select_warehouses ON public.warehouses; +DROP POLICY IF EXISTS anon_insert_warehouses ON public.warehouses; +DROP POLICY IF EXISTS anon_update_warehouses ON public.warehouses; +DROP POLICY IF EXISTS anon_select_inventory ON public.inventory_records; +DROP POLICY IF EXISTS anon_insert_inventory ON public.inventory_records; +DROP POLICY IF EXISTS anon_select_yarn_stock ON public.yarn_stock; +DROP POLICY IF EXISTS anon_insert_yarn_stock ON public.yarn_stock; +DROP POLICY IF EXISTS anon_update_yarn_stock ON public.yarn_stock; +DROP POLICY IF EXISTS anon_select_process_steps ON public.plan_process_steps; +DROP POLICY IF EXISTS anon_insert_process_steps ON public.plan_process_steps; +DROP POLICY IF EXISTS anon_update_process_steps ON public.plan_process_steps; +DROP POLICY IF EXISTS anon_select_payments ON public.payments; +DROP POLICY IF EXISTS anon_insert_payments ON public.payments; +DROP POLICY IF EXISTS anon_update_payments ON public.payments; + diff --git a/migrations/20260523_162428_create_helper_function_1.sql b/migrations/20260523_162428_create_helper_function_1.sql new file mode 100644 index 0000000..194ac16 --- /dev/null +++ b/migrations/20260523_162428_create_helper_function_1.sql @@ -0,0 +1,23 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 创建辅助函数:获取当前用户的公司ID +CREATE OR REPLACE FUNCTION public.get_current_user_company_id() +RETURNS UUID +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +DECLARE + company_id UUID; +BEGIN + SELECT p.company_id INTO company_id + FROM public.profiles p + WHERE p.id = auth.uid(); + RETURN company_id; +END; +$$; + diff --git a/migrations/20260523_162436_create_helper_function_2.sql b/migrations/20260523_162436_create_helper_function_2.sql new file mode 100644 index 0000000..623cdc8 --- /dev/null +++ b/migrations/20260523_162436_create_helper_function_2.sql @@ -0,0 +1,32 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 创建辅助函数:获取用户主账号公司ID(用于子账号) +CREATE OR REPLACE FUNCTION public.get_user_master_company_id() +RETURNS UUID +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +DECLARE + user_company_id UUID; + user_master_id UUID; +BEGIN + SELECT company_id, master_id INTO user_company_id, user_master_id + FROM public.profiles + WHERE id = auth.uid(); + + -- 如果是子账号,返回主账号的公司ID + IF user_master_id IS NOT NULL THEN + SELECT company_id INTO user_company_id + FROM public.profiles + WHERE id = user_master_id; + END IF; + + RETURN user_company_id; +END; +$$; + diff --git a/migrations/20260523_162443_create_companies_rls.sql b/migrations/20260523_162443_create_companies_rls.sql new file mode 100644 index 0000000..b019766 --- /dev/null +++ b/migrations/20260523_162443_create_companies_rls.sql @@ -0,0 +1,22 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- companies 表 RLS 策略 +-- 用户只能查看、修改自己公司的数据 +CREATE POLICY users_select_own_company ON public.companies +FOR SELECT USING ( + id = public.get_user_master_company_id() +); + +CREATE POLICY users_update_own_company ON public.companies +FOR UPDATE USING ( + id = public.get_user_master_company_id() +); + +-- 允许创建公司(注册时需要) +CREATE POLICY users_insert_company ON public.companies +FOR INSERT WITH CHECK (true); + diff --git a/migrations/20260523_162456_create_profiles_rls.sql b/migrations/20260523_162456_create_profiles_rls.sql new file mode 100644 index 0000000..a9e0d7d --- /dev/null +++ b/migrations/20260523_162456_create_profiles_rls.sql @@ -0,0 +1,24 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- profiles 表 RLS 策略 +-- 用户可以查看自己公司的所有成员资料 +CREATE POLICY users_select_company_profiles ON public.profiles +FOR SELECT USING ( + company_id = public.get_user_master_company_id() + OR id = auth.uid() +); + +-- 用户可以修改自己的资料 +CREATE POLICY users_update_own_profile ON public.profiles +FOR UPDATE USING ( + id = auth.uid() +); + +-- 允许创建 profile(注册时需要) +CREATE POLICY users_insert_profile ON public.profiles +FOR INSERT WITH CHECK (true); + diff --git a/migrations/20260523_162514_create_production_plans_rls.sql b/migrations/20260523_162514_create_production_plans_rls.sql new file mode 100644 index 0000000..cefc616 --- /dev/null +++ b/migrations/20260523_162514_create_production_plans_rls.sql @@ -0,0 +1,31 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- production_plans 表 RLS 策略 +-- 采购商可以查看、修改、删除自己创建的计划 +CREATE POLICY purchaser_select_plans ON public.production_plans +FOR SELECT USING ( + purchaser_id = public.get_user_master_company_id() +); + +CREATE POLICY purchaser_insert_plans ON public.production_plans +FOR INSERT WITH CHECK ( + purchaser_id = public.get_user_master_company_id() +); + +CREATE POLICY purchaser_update_plans ON public.production_plans +FOR UPDATE USING ( + purchaser_id = public.get_user_master_company_id() +); + +CREATE POLICY purchaser_delete_plans ON public.production_plans +FOR DELETE USING ( + purchaser_id = public.get_user_master_company_id() +); + +-- 工厂可以查看关联的计划(通过 plan_factories 关联) +-- 这个策略需要配合 plan_factories 的查询 + diff --git a/migrations/20260523_162530_create_plan_factories_rls.sql b/migrations/20260523_162530_create_plan_factories_rls.sql new file mode 100644 index 0000000..e03cf4e --- /dev/null +++ b/migrations/20260523_162530_create_plan_factories_rls.sql @@ -0,0 +1,39 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- plan_factories 表 RLS 策略 +-- 工厂可以查看和管理关联到自己的计划 +CREATE POLICY factory_select_plan_factories ON public.plan_factories +FOR SELECT USING ( + factory_id = public.get_user_master_company_id() +); + +-- 采购商可以查看自己计划的工厂关联 +CREATE POLICY purchaser_select_plan_factories ON public.plan_factories +FOR SELECT USING ( + EXISTS ( + SELECT 1 FROM public.production_plans + WHERE id = plan_id AND purchaser_id = public.get_user_master_company_id() + ) +); + +-- 采购商可以创建和删除工厂关联 +CREATE POLICY purchaser_insert_plan_factories ON public.plan_factories +FOR INSERT WITH CHECK ( + EXISTS ( + SELECT 1 FROM public.production_plans + WHERE id = plan_id AND purchaser_id = public.get_user_master_company_id() + ) +); + +CREATE POLICY purchaser_delete_plan_factories ON public.plan_factories +FOR DELETE USING ( + EXISTS ( + SELECT 1 FROM public.production_plans + WHERE id = plan_id AND purchaser_id = public.get_user_master_company_id() + ) +); + diff --git a/migrations/20260523_162547_create_warehouse_inventory_rls.sql b/migrations/20260523_162547_create_warehouse_inventory_rls.sql new file mode 100644 index 0000000..e1291d4 --- /dev/null +++ b/migrations/20260523_162547_create_warehouse_inventory_rls.sql @@ -0,0 +1,45 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- warehouses 表 RLS 策略 +CREATE POLICY users_select_company_warehouses ON public.warehouses +FOR SELECT USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_insert_company_warehouses ON public.warehouses +FOR INSERT WITH CHECK ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_update_company_warehouses ON public.warehouses +FOR UPDATE USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_delete_company_warehouses ON public.warehouses +FOR DELETE USING ( + company_id = public.get_user_master_company_id() +); + +-- inventory_records 表 RLS 策略 +-- 通过 warehouse 关联到公司 +CREATE POLICY users_select_company_inventory ON public.inventory_records +FOR SELECT USING ( + EXISTS ( + SELECT 1 FROM public.warehouses + WHERE id = warehouse_id AND company_id = public.get_user_master_company_id() + ) +); + +CREATE POLICY users_insert_company_inventory ON public.inventory_records +FOR INSERT WITH CHECK ( + EXISTS ( + SELECT 1 FROM public.warehouses + WHERE id = warehouse_id AND company_id = public.get_user_master_company_id() + ) +); + diff --git a/migrations/20260523_162604_create_yarn_rls.sql b/migrations/20260523_162604_create_yarn_rls.sql new file mode 100644 index 0000000..def9ce9 --- /dev/null +++ b/migrations/20260523_162604_create_yarn_rls.sql @@ -0,0 +1,53 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- yarn_stock 表 RLS 策略 +CREATE POLICY users_select_company_yarn_stock ON public.yarn_stock +FOR SELECT USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_insert_company_yarn_stock ON public.yarn_stock +FOR INSERT WITH CHECK ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_update_company_yarn_stock ON public.yarn_stock +FOR UPDATE USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_delete_company_yarn_stock ON public.yarn_stock +FOR DELETE USING ( + company_id = public.get_user_master_company_id() +); + +-- yarn_ratios 表 RLS 策略 +-- 通过 plan 关联到采购商 +CREATE POLICY users_select_yarn_ratios ON public.yarn_ratios +FOR SELECT USING ( + EXISTS ( + SELECT 1 FROM public.production_plans + WHERE id = plan_id AND purchaser_id = public.get_user_master_company_id() + ) +); + +CREATE POLICY users_insert_yarn_ratios ON public.yarn_ratios +FOR INSERT WITH CHECK ( + EXISTS ( + SELECT 1 FROM public.production_plans + WHERE id = plan_id AND purchaser_id = public.get_user_master_company_id() + ) +); + +CREATE POLICY users_delete_yarn_ratios ON public.yarn_ratios +FOR DELETE USING ( + EXISTS ( + SELECT 1 FROM public.production_plans + WHERE id = plan_id AND purchaser_id = public.get_user_master_company_id() + ) +); + diff --git a/migrations/20260523_162626_create_payments_process_rls.sql b/migrations/20260523_162626_create_payments_process_rls.sql new file mode 100644 index 0000000..0e92041 --- /dev/null +++ b/migrations/20260523_162626_create_payments_process_rls.sql @@ -0,0 +1,77 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- payments 表 RLS 策略 +-- 付款方或收款方可以查看相关结款记录 +CREATE POLICY users_select_payments ON public.payments +FOR SELECT USING ( + from_company_id = public.get_user_master_company_id() + OR to_company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_insert_payments ON public.payments +FOR INSERT WITH CHECK ( + from_company_id = public.get_user_master_company_id() + OR to_company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_update_payments ON public.payments +FOR UPDATE USING ( + from_company_id = public.get_user_master_company_id() + OR to_company_id = public.get_user_master_company_id() +); + +-- plan_process_steps 表 RLS 策略 +-- 关联计划的采购商或工厂可以查看流程步骤 +CREATE POLICY users_select_process_steps ON public.plan_process_steps +FOR SELECT USING ( + EXISTS ( + SELECT 1 FROM public.production_plans p + JOIN public.plan_factories pf ON p.id = pf.plan_id + WHERE p.id = plan_id + AND (p.purchaser_id = public.get_user_master_company_id() + OR pf.factory_id = public.get_user_master_company_id()) + ) +); + +CREATE POLICY users_insert_process_steps ON public.plan_process_steps +FOR INSERT WITH CHECK ( + EXISTS ( + SELECT 1 FROM public.production_plans p + JOIN public.plan_factories pf ON p.id = pf.plan_id + WHERE p.id = plan_id + AND (p.purchaser_id = public.get_user_master_company_id() + OR pf.factory_id = public.get_user_master_company_id()) + ) +); + +CREATE POLICY users_update_process_steps ON public.plan_process_steps +FOR UPDATE USING ( + EXISTS ( + SELECT 1 FROM public.production_plans p + JOIN public.plan_factories pf ON p.id = pf.plan_id + WHERE p.id = plan_id + AND (p.purchaser_id = public.get_user_master_company_id() + OR pf.factory_id = public.get_user_master_company_id()) + ) +); + +-- company_members 表 RLS 策略 +CREATE POLICY users_select_company_members ON public.company_members +FOR SELECT USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_insert_company_members ON public.company_members +FOR INSERT WITH CHECK ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_delete_company_members ON public.company_members +FOR DELETE USING ( + company_id = public.get_user_master_company_id() +); + diff --git a/migrations/20260523_162653_drop_remaining_old_policies.sql b/migrations/20260523_162653_drop_remaining_old_policies.sql new file mode 100644 index 0000000..c52b067 --- /dev/null +++ b/migrations/20260523_162653_drop_remaining_old_policies.sql @@ -0,0 +1,28 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 删除 products 表上的旧宽松策略 +DROP POLICY IF EXISTS anon_select_products ON public.products; +DROP POLICY IF EXISTS anon_insert_products ON public.products; +DROP POLICY IF EXISTS anon_update_products ON public.products; +DROP POLICY IF EXISTS anon_delete_products ON public.products; + +-- 删除 product_inventory_records 表上的旧宽松策略 +DROP POLICY IF EXISTS anon_select_product_inventory_records ON public.product_inventory_records; +DROP POLICY IF EXISTS anon_insert_product_inventory_records ON public.product_inventory_records; +DROP POLICY IF EXISTS anon_update_product_inventory_records ON public.product_inventory_records; +DROP POLICY IF EXISTS anon_delete_product_inventory_records ON public.product_inventory_records; +DROP POLICY IF EXISTS company_select_product_inventory_records ON public.product_inventory_records; +DROP POLICY IF EXISTS company_insert_product_inventory_records ON public.product_inventory_records; +DROP POLICY IF EXISTS company_update_product_inventory_records ON public.product_inventory_records; +DROP POLICY IF EXISTS company_delete_product_inventory_records ON public.product_inventory_records; + +-- 删除 product_yarn_ratios 表上的旧宽松策略 +DROP POLICY IF EXISTS company_select_product_yarn_ratios ON public.product_yarn_ratios; +DROP POLICY IF EXISTS company_insert_product_yarn_ratios ON public.product_yarn_ratios; +DROP POLICY IF EXISTS company_update_product_yarn_ratios ON public.product_yarn_ratios; +DROP POLICY IF EXISTS company_delete_product_yarn_ratios ON public.product_yarn_ratios; + diff --git a/migrations/20260523_162700_fix_profiles_rls.sql b/migrations/20260523_162700_fix_profiles_rls.sql new file mode 100644 index 0000000..a9427f8 --- /dev/null +++ b/migrations/20260523_162700_fix_profiles_rls.sql @@ -0,0 +1,22 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 修复 profiles 策略中的 uid() 为 auth.uid() +DROP POLICY IF EXISTS users_select_company_profiles ON public.profiles; +DROP POLICY IF EXISTS users_update_own_profile ON public.profiles; + +-- 重新创建正确的策略 +CREATE POLICY users_select_company_profiles ON public.profiles +FOR SELECT USING ( + company_id = public.get_user_master_company_id() + OR id = auth.uid() +); + +CREATE POLICY users_update_own_profile ON public.profiles +FOR UPDATE USING ( + id = auth.uid() +); + diff --git a/migrations/20260523_162833_add_company_id_to_inventory_records.sql b/migrations/20260523_162833_add_company_id_to_inventory_records.sql new file mode 100644 index 0000000..0756040 --- /dev/null +++ b/migrations/20260523_162833_add_company_id_to_inventory_records.sql @@ -0,0 +1,12 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 为 inventory_records 表添加 company_id 字段 +ALTER TABLE public.inventory_records ADD COLUMN IF NOT EXISTS company_id UUID REFERENCES public.companies(id) ON DELETE CASCADE; + +-- 创建索引优化查询 +CREATE INDEX IF NOT EXISTS idx_inventory_records_company_id ON public.inventory_records(company_id); + diff --git a/migrations/20260523_162846_add_company_id_to_yarn_ratios.sql b/migrations/20260523_162846_add_company_id_to_yarn_ratios.sql new file mode 100644 index 0000000..e45f429 --- /dev/null +++ b/migrations/20260523_162846_add_company_id_to_yarn_ratios.sql @@ -0,0 +1,12 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 为 yarn_ratios 表添加 company_id 字段 +ALTER TABLE public.yarn_ratios ADD COLUMN IF NOT EXISTS company_id UUID REFERENCES public.companies(id) ON DELETE CASCADE; + +-- 创建索引优化查询 +CREATE INDEX IF NOT EXISTS idx_yarn_ratios_company_id ON public.yarn_ratios(company_id); + diff --git a/migrations/20260523_162859_add_company_id_to_plan_process_steps.sql b/migrations/20260523_162859_add_company_id_to_plan_process_steps.sql new file mode 100644 index 0000000..8fd18fe --- /dev/null +++ b/migrations/20260523_162859_add_company_id_to_plan_process_steps.sql @@ -0,0 +1,12 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 为 plan_process_steps 表添加 company_id 字段 +ALTER TABLE public.plan_process_steps ADD COLUMN IF NOT EXISTS company_id UUID REFERENCES public.companies(id) ON DELETE CASCADE; + +-- 创建索引优化查询 +CREATE INDEX IF NOT EXISTS idx_plan_process_steps_company_id ON public.plan_process_steps(company_id); + diff --git a/migrations/20260523_162913_update_inventory_records_rls.sql b/migrations/20260523_162913_update_inventory_records_rls.sql new file mode 100644 index 0000000..ec1f6d9 --- /dev/null +++ b/migrations/20260523_162913_update_inventory_records_rls.sql @@ -0,0 +1,31 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 删除旧的 RLS 策略 +DROP POLICY IF EXISTS users_select_company_inventory ON public.inventory_records; +DROP POLICY IF EXISTS users_insert_company_inventory ON public.inventory_records; + +-- 创建优化的 RLS 策略(直接使用 company_id) +CREATE POLICY users_select_company_inventory ON public.inventory_records +FOR SELECT USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_insert_company_inventory ON public.inventory_records +FOR INSERT WITH CHECK ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_update_company_inventory ON public.inventory_records +FOR UPDATE USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_delete_company_inventory ON public.inventory_records +FOR DELETE USING ( + company_id = public.get_user_master_company_id() +); + diff --git a/migrations/20260523_162926_update_yarn_ratios_rls.sql b/migrations/20260523_162926_update_yarn_ratios_rls.sql new file mode 100644 index 0000000..fd6ead5 --- /dev/null +++ b/migrations/20260523_162926_update_yarn_ratios_rls.sql @@ -0,0 +1,32 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 删除旧的 RLS 策略 +DROP POLICY IF EXISTS users_select_yarn_ratios ON public.yarn_ratios; +DROP POLICY IF EXISTS users_insert_yarn_ratios ON public.yarn_ratios; +DROP POLICY IF EXISTS users_delete_yarn_ratios ON public.yarn_ratios; + +-- 创建优化的 RLS 策略(直接使用 company_id) +CREATE POLICY users_select_yarn_ratios ON public.yarn_ratios +FOR SELECT USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_insert_yarn_ratios ON public.yarn_ratios +FOR INSERT WITH CHECK ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_update_yarn_ratios ON public.yarn_ratios +FOR UPDATE USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_delete_yarn_ratios ON public.yarn_ratios +FOR DELETE USING ( + company_id = public.get_user_master_company_id() +); + diff --git a/migrations/20260523_162941_update_plan_process_steps_rls.sql b/migrations/20260523_162941_update_plan_process_steps_rls.sql new file mode 100644 index 0000000..a9ec728 --- /dev/null +++ b/migrations/20260523_162941_update_plan_process_steps_rls.sql @@ -0,0 +1,32 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 删除旧的 RLS 策略 +DROP POLICY IF EXISTS users_select_process_steps ON public.plan_process_steps; +DROP POLICY IF EXISTS users_insert_process_steps ON public.plan_process_steps; +DROP POLICY IF EXISTS users_update_process_steps ON public.plan_process_steps; + +-- 创建优化的 RLS 策略(直接使用 company_id) +CREATE POLICY users_select_process_steps ON public.plan_process_steps +FOR SELECT USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_insert_process_steps ON public.plan_process_steps +FOR INSERT WITH CHECK ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_update_process_steps ON public.plan_process_steps +FOR UPDATE USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_delete_process_steps ON public.plan_process_steps +FOR DELETE USING ( + company_id = public.get_user_master_company_id() +); + diff --git a/migrations/20260523_163000_sync_company_id_data.sql b/migrations/20260523_163000_sync_company_id_data.sql new file mode 100644 index 0000000..7d64272 --- /dev/null +++ b/migrations/20260523_163000_sync_company_id_data.sql @@ -0,0 +1,27 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 更新 inventory_records 的 company_id(通过 warehouse 关联) +UPDATE public.inventory_records ir +SET company_id = w.company_id +FROM public.warehouses w +WHERE ir.warehouse_id = w.id +AND ir.company_id IS NULL; + +-- 更新 yarn_ratios 的 company_id(通过 production_plans 关联) +UPDATE public.yarn_ratios yr +SET company_id = pp.purchaser_id +FROM public.production_plans pp +WHERE yr.plan_id = pp.id +AND yr.company_id IS NULL; + +-- 更新 plan_process_steps 的 company_id(通过 production_plans 关联) +UPDATE public.plan_process_steps pps +SET company_id = pp.purchaser_id +FROM public.production_plans pp +WHERE pps.plan_id = pp.id +AND pps.company_id IS NULL; + diff --git a/migrations/20260523_163054_add_share_link_rls_policies.sql b/migrations/20260523_163054_add_share_link_rls_policies.sql new file mode 100644 index 0000000..46c61ee --- /dev/null +++ b/migrations/20260523_163054_add_share_link_rls_policies.sql @@ -0,0 +1,37 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 为分享链接场景添加特殊的 RLS 策略 +-- 允许工厂查看通过 plan_factories 关联的计划 + +-- production_plans:工厂可以查看关联的计划 +CREATE POLICY factory_select_plans_via_share ON public.production_plans +FOR SELECT USING ( + EXISTS ( + SELECT 1 FROM public.plan_factories + WHERE plan_id = production_plans.id + AND factory_id = public.get_user_master_company_id() + ) +); + +-- yarn_ratios:工厂可以查看关联计划的纱线配比 +CREATE POLICY factory_select_yarn_ratios_via_share ON public.yarn_ratios +FOR SELECT USING ( + EXISTS ( + SELECT 1 FROM public.plan_factories + WHERE plan_id = yarn_ratios.plan_id + AND factory_id = public.get_user_master_company_id() + ) +); + +-- companies:允许查看所有公司(用于显示采购商名称) +-- 删除旧的限制策略,创建新的开放查询策略 +DROP POLICY IF EXISTS users_select_own_company ON public.companies; + +-- 允许所有登录用户查看所有公司信息(用于分享链接显示采购商名称) +CREATE POLICY users_select_companies ON public.companies +FOR SELECT USING (true); + diff --git a/migrations/20260523_163116_fix_share_link_rls_v2.sql b/migrations/20260523_163116_fix_share_link_rls_v2.sql new file mode 100644 index 0000000..2aac096 --- /dev/null +++ b/migrations/20260523_163116_fix_share_link_rls_v2.sql @@ -0,0 +1,41 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 删除刚才创建的策略(有问题) +DROP POLICY IF EXISTS factory_select_plans_via_share ON public.production_plans; +DROP POLICY IF EXISTS factory_select_yarn_ratios_via_share ON public.yarn_ratios; +DROP POLICY IF EXISTS users_select_yarn_ratios ON public.yarn_ratios; + +-- 重新创建策略:允许所有登录用户查看计划(但只能看到分享链接中展示的非敏感字段) +-- 实际敏感信息控制由前端完成 +DROP POLICY IF EXISTS purchaser_select_plans ON public.production_plans; +CREATE POLICY users_select_plans ON public.production_plans +FOR SELECT USING (true); + +-- yarn_ratios:允许查看(配合计划查看) +DROP POLICY IF EXISTS users_select_yarn_ratios ON public.yarn_ratios; +DROP POLICY IF EXISTS users_insert_yarn_ratios ON public.yarn_ratios; +DROP POLICY IF EXISTS users_update_yarn_ratios ON public.yarn_ratios; +DROP POLICY IF EXISTS users_delete_yarn_ratios ON public.yarn_ratios; + +CREATE POLICY users_select_yarn_ratios ON public.yarn_ratios +FOR SELECT USING (true); + +CREATE POLICY users_insert_yarn_ratios ON public.yarn_ratios +FOR INSERT WITH CHECK ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_update_yarn_ratios ON public.yarn_ratios +FOR UPDATE USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_delete_yarn_ratios ON public.yarn_ratios +FOR DELETE USING ( + company_id = public.get_user_master_company_id() +); + diff --git a/migrations/20260523_163245_add_missing_rls_policies.sql b/migrations/20260523_163245_add_missing_rls_policies.sql new file mode 100644 index 0000000..7568a06 --- /dev/null +++ b/migrations/20260523_163245_add_missing_rls_policies.sql @@ -0,0 +1,52 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 为 products 表添加 RLS 策略 +-- 允许查看(用于仓库同步),但只允许公司自己管理 +CREATE POLICY users_select_products ON public.products +FOR SELECT USING (true); + +CREATE POLICY users_insert_products ON public.products +FOR INSERT WITH CHECK ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_update_products ON public.products +FOR UPDATE USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_delete_products ON public.products +FOR DELETE USING ( + company_id = public.get_user_master_company_id() +); + +-- 为 product_inventory_records 表添加 RLS 策略 +CREATE POLICY users_select_product_inventory ON public.product_inventory_records +FOR SELECT USING (true); + +CREATE POLICY users_insert_product_inventory ON public.product_inventory_records +FOR INSERT WITH CHECK (true); + +CREATE POLICY users_update_product_inventory ON public.product_inventory_records +FOR UPDATE USING (true); + +CREATE POLICY users_delete_product_inventory ON public.product_inventory_records +FOR DELETE USING (true); + +-- 为 product_yarn_ratios 表添加 RLS 策略 +CREATE POLICY users_select_product_yarn_ratios ON public.product_yarn_ratios +FOR SELECT USING (true); + +CREATE POLICY users_insert_product_yarn_ratios ON public.product_yarn_ratios +FOR INSERT WITH CHECK (true); + +CREATE POLICY users_update_product_yarn_ratios ON public.product_yarn_ratios +FOR UPDATE USING (true); + +CREATE POLICY users_delete_product_yarn_ratios ON public.product_yarn_ratios +FOR DELETE USING (true); + diff --git a/migrations/20260523_163255_fix_inventory_records_rls.sql b/migrations/20260523_163255_fix_inventory_records_rls.sql new file mode 100644 index 0000000..a56cafc --- /dev/null +++ b/migrations/20260523_163255_fix_inventory_records_rls.sql @@ -0,0 +1,32 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 修复 inventory_records 的 RLS 策略 +-- 删除旧的策略 +DROP POLICY IF EXISTS users_select_company_inventory ON public.inventory_records; +DROP POLICY IF EXISTS users_insert_company_inventory ON public.inventory_records; +DROP POLICY IF EXISTS users_update_company_inventory ON public.inventory_records; +DROP POLICY IF EXISTS users_delete_company_inventory ON public.inventory_records; + +-- 创建新的策略:允许查看(用于仓库同步),但只允许公司自己管理 +CREATE POLICY users_select_inventory ON public.inventory_records +FOR SELECT USING (true); + +CREATE POLICY users_insert_inventory ON public.inventory_records +FOR INSERT WITH CHECK ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_update_inventory ON public.inventory_records +FOR UPDATE USING ( + company_id = public.get_user_master_company_id() +); + +CREATE POLICY users_delete_inventory ON public.inventory_records +FOR DELETE USING ( + company_id = public.get_user_master_company_id() +); + diff --git a/migrations/20260523_163429_fix_plan_factories_rls.sql b/migrations/20260523_163429_fix_plan_factories_rls.sql new file mode 100644 index 0000000..93b5f98 --- /dev/null +++ b/migrations/20260523_163429_fix_plan_factories_rls.sql @@ -0,0 +1,34 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 修复 plan_factories 的 RLS 策略 +-- 删除旧的策略 +DROP POLICY IF EXISTS factory_select_plan_factories ON public.plan_factories; +DROP POLICY IF EXISTS purchaser_select_plan_factories ON public.plan_factories; +DROP POLICY IF EXISTS purchaser_insert_plan_factories ON public.plan_factories; +DROP POLICY IF EXISTS purchaser_delete_plan_factories ON public.plan_factories; + +-- 创建新的策略:允许查看(用于仓库同步和分享链接) +CREATE POLICY users_select_plan_factories ON public.plan_factories +FOR SELECT USING (true); + +-- 插入和删除保持严格限制 +CREATE POLICY users_insert_plan_factories ON public.plan_factories +FOR INSERT WITH CHECK ( + EXISTS ( + SELECT 1 FROM public.production_plans + WHERE id = plan_id AND purchaser_id = public.get_user_master_company_id() + ) +); + +CREATE POLICY users_delete_plan_factories ON public.plan_factories +FOR DELETE USING ( + EXISTS ( + SELECT 1 FROM public.production_plans + WHERE id = plan_id AND purchaser_id = public.get_user_master_company_id() + ) +); + diff --git a/migrations/20260523_163537_fix_plan_process_steps_rls.sql b/migrations/20260523_163537_fix_plan_process_steps_rls.sql new file mode 100644 index 0000000..0a5aca2 --- /dev/null +++ b/migrations/20260523_163537_fix_plan_process_steps_rls.sql @@ -0,0 +1,51 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 修复 plan_process_steps 的 RLS 策略 +-- 删除旧的策略 +DROP POLICY IF EXISTS users_select_process_steps ON public.plan_process_steps; +DROP POLICY IF EXISTS users_insert_process_steps ON public.plan_process_steps; +DROP POLICY IF EXISTS users_update_process_steps ON public.plan_process_steps; +DROP POLICY IF EXISTS users_delete_process_steps ON public.plan_process_steps; + +-- 创建新的策略:允许查看(用于流程跟踪) +CREATE POLICY users_select_process_steps ON public.plan_process_steps +FOR SELECT USING (true); + +-- 插入:允许关联计划的采购商或工厂 +CREATE POLICY users_insert_process_steps ON public.plan_process_steps +FOR INSERT WITH CHECK ( + EXISTS ( + SELECT 1 FROM public.production_plans p + LEFT JOIN public.plan_factories pf ON p.id = pf.plan_id + WHERE p.id = plan_process_steps.plan_id + AND (p.purchaser_id = public.get_user_master_company_id() + OR pf.factory_id = public.get_user_master_company_id()) + ) +); + +-- 更新:允许关联计划的采购商或工厂 +CREATE POLICY users_update_process_steps ON public.plan_process_steps +FOR UPDATE USING ( + EXISTS ( + SELECT 1 FROM public.production_plans p + LEFT JOIN public.plan_factories pf ON p.id = pf.plan_id + WHERE p.id = plan_process_steps.plan_id + AND (p.purchaser_id = public.get_user_master_company_id() + OR pf.factory_id = public.get_user_master_company_id()) + ) +); + +-- 删除:只允许采购商 +CREATE POLICY users_delete_process_steps ON public.plan_process_steps +FOR DELETE USING ( + EXISTS ( + SELECT 1 FROM public.production_plans + WHERE id = plan_process_steps.plan_id + AND purchaser_id = public.get_user_master_company_id() + ) +); + diff --git a/migrations/20260523_163644_fix_select_policies_security.sql b/migrations/20260523_163644_fix_select_policies_security.sql new file mode 100644 index 0000000..6c8900d --- /dev/null +++ b/migrations/20260523_163644_fix_select_policies_security.sql @@ -0,0 +1,50 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 修复高风险表的 SELECT 策略 + +-- 1. production_plans: 只允许采购商查看自己的计划,工厂查看关联的计划 +DROP POLICY IF EXISTS users_select_plans ON public.production_plans; + +CREATE POLICY users_select_plans ON public.production_plans +FOR SELECT USING ( + -- 采购商可以查看自己的计划 + purchaser_id = public.get_user_master_company_id() + OR + -- 工厂可以查看关联的计划 + EXISTS ( + SELECT 1 FROM public.plan_factories + WHERE plan_id = production_plans.id + AND factory_id = public.get_user_master_company_id() + ) +); + +-- 2. products: 只允许公司自己查看(产品管理) +DROP POLICY IF EXISTS users_select_products ON public.products; + +CREATE POLICY users_select_products ON public.products +FOR SELECT USING ( + company_id = public.get_user_master_company_id() +); + +-- 3. inventory_records: 只允许相关方查看 +DROP POLICY IF EXISTS users_select_inventory ON public.inventory_records; + +CREATE POLICY users_select_inventory ON public.inventory_records +FOR SELECT USING ( + -- 公司可以查看自己的入库记录 + company_id = public.get_user_master_company_id() + OR + -- 采购商可以查看关联计划的入库记录 + EXISTS ( + SELECT 1 FROM public.production_plans p + JOIN public.plan_factories pf ON p.id = pf.plan_id + WHERE p.id = inventory_records.plan_id + AND (p.purchaser_id = public.get_user_master_company_id() + OR pf.factory_id = public.get_user_master_company_id()) + ) +); + diff --git a/migrations/20260523_164422_remove_duplicate_factory_links.sql b/migrations/20260523_164422_remove_duplicate_factory_links.sql new file mode 100644 index 0000000..b5b0c09 --- /dev/null +++ b/migrations/20260523_164422_remove_duplicate_factory_links.sql @@ -0,0 +1,17 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 1. 删除 PLAN-2026-820523 的重复工厂关联,只保留示例纺织厂 +DELETE FROM plan_factories +WHERE plan_id = '7b0d76c2-0bf3-4a58-8607-4dfa850cf416' +AND factory_id = '2da8c512-6076-44e1-8f37-def2298b5847'; + +-- 2. 检查是否还有其他重复关联 +SELECT plan_id, factory_type, COUNT(*) +FROM plan_factories +GROUP BY plan_id, factory_type +HAVING COUNT(*) > 1; + diff --git a/migrations/20260523_164431_add_unique_constraint_plan_factories.sql b/migrations/20260523_164431_add_unique_constraint_plan_factories.sql new file mode 100644 index 0000000..6c30bdd --- /dev/null +++ b/migrations/20260523_164431_add_unique_constraint_plan_factories.sql @@ -0,0 +1,15 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 添加唯一约束:一个计划的同类型工厂只能关联一个 +-- 先删除可能存在的旧约束 +ALTER TABLE plan_factories DROP CONSTRAINT IF EXISTS unique_plan_factory_type; + +-- 添加新的唯一约束 +ALTER TABLE plan_factories +ADD CONSTRAINT unique_plan_factory_type +UNIQUE (plan_id, factory_type); + diff --git a/migrations/20260523_165555_create_audit_logs_table.sql b/migrations/20260523_165555_create_audit_logs_table.sql new file mode 100644 index 0000000..5a53540 --- /dev/null +++ b/migrations/20260523_165555_create_audit_logs_table.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +CREATE TABLE IF NOT EXISTS public.audit_logs (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, company_id UUID REFERENCES public.companies(id) ON DELETE SET NULL, action TEXT NOT NULL, resource_type TEXT NOT NULL, resource_id UUID, details JSONB, ip_address TEXT, user_agent TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()); CREATE INDEX idx_audit_logs_user_id ON public.audit_logs(user_id); CREATE INDEX idx_audit_logs_company_id ON public.audit_logs(company_id); CREATE INDEX idx_audit_logs_action ON public.audit_logs(action); CREATE INDEX idx_audit_logs_created_at ON public.audit_logs(created_at DESC); ALTER TABLE public.audit_logs ENABLE ROW LEVEL SECURITY; CREATE POLICY users_select_company_audit_logs ON public.audit_logs FOR SELECT USING (company_id = public.get_user_master_company_id()); CREATE POLICY users_insert_audit_logs ON public.audit_logs FOR INSERT WITH CHECK (true); diff --git a/migrations/20260523_165857_fix_inventory_insert_rls.sql b/migrations/20260523_165857_fix_inventory_insert_rls.sql new file mode 100644 index 0000000..dd42945 --- /dev/null +++ b/migrations/20260523_165857_fix_inventory_insert_rls.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +DROP POLICY IF EXISTS users_insert_inventory ON public.inventory_records; CREATE POLICY users_insert_inventory ON public.inventory_records FOR INSERT WITH CHECK (EXISTS (SELECT 1 FROM public.warehouses WHERE id = warehouse_id AND company_id = public.get_user_master_company_id())); diff --git a/migrations/20260523_165900_fix_payments_insert_rls.sql b/migrations/20260523_165900_fix_payments_insert_rls.sql new file mode 100644 index 0000000..70200ad --- /dev/null +++ b/migrations/20260523_165900_fix_payments_insert_rls.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +DROP POLICY IF EXISTS users_insert_payments ON public.payments; CREATE POLICY users_insert_payments ON public.payments FOR INSERT WITH CHECK (from_company_id = public.get_user_master_company_id() OR to_company_id = public.get_user_master_company_id()); diff --git a/migrations/20260523_165901_fix_yarn_ratios_delete_rls.sql b/migrations/20260523_165901_fix_yarn_ratios_delete_rls.sql new file mode 100644 index 0000000..4d164a6 --- /dev/null +++ b/migrations/20260523_165901_fix_yarn_ratios_delete_rls.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +DROP POLICY IF EXISTS users_delete_yarn_ratios ON public.yarn_ratios; CREATE POLICY users_delete_yarn_ratios ON public.yarn_ratios FOR DELETE USING (EXISTS (SELECT 1 FROM public.production_plans WHERE id = plan_id AND purchaser_id = public.get_user_master_company_id())); diff --git a/migrations/20260523_165913_fix_process_steps_select_rls.sql b/migrations/20260523_165913_fix_process_steps_select_rls.sql new file mode 100644 index 0000000..1ce8165 --- /dev/null +++ b/migrations/20260523_165913_fix_process_steps_select_rls.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +DROP POLICY IF EXISTS users_select_process_steps ON public.plan_process_steps; CREATE POLICY users_select_process_steps ON public.plan_process_steps FOR SELECT USING (EXISTS (SELECT 1 FROM public.production_plans p LEFT JOIN public.plan_factories pf ON p.id = pf.plan_id WHERE p.id = plan_id AND (p.purchaser_id = public.get_user_master_company_id() OR pf.factory_id = public.get_user_master_company_id()))); diff --git a/migrations/20260523_170000_create_audit_logs_table.sql b/migrations/20260523_170000_create_audit_logs_table.sql new file mode 100644 index 0000000..7479297 --- /dev/null +++ b/migrations/20260523_170000_create_audit_logs_table.sql @@ -0,0 +1,46 @@ +-- ============================================ +-- 创建审计日志表 +-- 用于记录敏感操作,便于安全审计 +-- ============================================ + +-- 创建审计日志表 +CREATE TABLE IF NOT EXISTS public.audit_logs ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, + company_id UUID REFERENCES public.companies(id) ON DELETE SET NULL, + action TEXT NOT NULL, -- 操作类型:login, logout, create, update, delete, export, etc. + resource_type TEXT NOT NULL, -- 资源类型:plan, factory, warehouse, payment, profile, etc. + resource_id UUID, -- 被操作资源的ID + details JSONB, -- 操作详情(JSON格式) + ip_address TEXT, -- IP地址 + user_agent TEXT, -- 用户代理 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 创建索引 +CREATE INDEX idx_audit_logs_user_id ON public.audit_logs(user_id); +CREATE INDEX idx_audit_logs_company_id ON public.audit_logs(company_id); +CREATE INDEX idx_audit_logs_action ON public.audit_logs(action); +CREATE INDEX idx_audit_logs_created_at ON public.audit_logs(created_at DESC); + +-- 启用 RLS +ALTER TABLE public.audit_logs ENABLE ROW LEVEL SECURITY; + +-- 审计日志策略:用户只能查看自己公司的日志 +CREATE POLICY users_select_company_audit_logs ON public.audit_logs +FOR SELECT USING ( + company_id = public.get_user_master_company_id() +); + +-- 允许插入日志(应用层写入) +CREATE POLICY users_insert_audit_logs ON public.audit_logs +FOR INSERT WITH CHECK (true); + +-- 禁止修改和删除审计日志(保证不可篡改) +-- 不提供 UPDATE 和 DELETE 策略 + +-- 注释 +COMMENT ON TABLE public.audit_logs IS '审计日志表,记录所有敏感操作'; +COMMENT ON COLUMN public.audit_logs.action IS '操作类型:login, logout, create, update, delete, export'; +COMMENT ON COLUMN public.audit_logs.resource_type IS '被操作的资源类型'; +COMMENT ON COLUMN public.audit_logs.details IS '操作详情,JSON格式存储变更前后数据'; diff --git a/migrations/20260523_170036_fix_yarn_ratios_select_rls.sql b/migrations/20260523_170036_fix_yarn_ratios_select_rls.sql new file mode 100644 index 0000000..4068f54 --- /dev/null +++ b/migrations/20260523_170036_fix_yarn_ratios_select_rls.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +DROP POLICY IF EXISTS users_select_yarn_ratios ON public.yarn_ratios; CREATE POLICY users_select_yarn_ratios ON public.yarn_ratios FOR SELECT USING (EXISTS (SELECT 1 FROM public.production_plans WHERE id = plan_id AND (purchaser_id = public.get_user_master_company_id() OR EXISTS (SELECT 1 FROM public.plan_factories WHERE plan_id = production_plans.id AND factory_id = public.get_user_master_company_id())))); diff --git a/migrations/20260523_191341_create_notifications_table.sql b/migrations/20260523_191341_create_notifications_table.sql new file mode 100644 index 0000000..5a29f8a --- /dev/null +++ b/migrations/20260523_191341_create_notifications_table.sql @@ -0,0 +1,19 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +CREATE TABLE IF NOT EXISTS notifications ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + recipient_id UUID NOT NULL REFERENCES auth.users(id) ON DELETE CASCADE, + sender_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, + sender_company_id UUID REFERENCES companies(id) ON DELETE SET NULL, + plan_id UUID REFERENCES production_plans(id) ON DELETE CASCADE, + type TEXT NOT NULL CHECK (type IN ('step_completed', 'plan_created', 'plan_updated', 'inventory_in', 'payment_pending', 'system')), + title TEXT NOT NULL, + content TEXT NOT NULL, + is_read BOOLEAN DEFAULT false, + created_at TIMESTAMPTZ DEFAULT NOW(), + read_at TIMESTAMPTZ +); diff --git a/migrations/20260523_191350_add_notifications_rls.sql b/migrations/20260523_191350_add_notifications_rls.sql new file mode 100644 index 0000000..6b9f427 --- /dev/null +++ b/migrations/20260523_191350_add_notifications_rls.sql @@ -0,0 +1,20 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +ALTER TABLE notifications ENABLE ROW LEVEL SECURITY; + +-- 用户只能查看自己的通知 +CREATE POLICY users_select_own_notifications ON notifications + FOR SELECT USING (recipient_id = auth.uid()); + +-- 用户只能标记自己的通知为已读 +CREATE POLICY users_update_own_notifications ON notifications + FOR UPDATE USING (recipient_id = auth.uid()); + +-- 系统可以插入通知(通过 service role) +CREATE POLICY service_insert_notifications ON notifications + FOR INSERT WITH CHECK (true); + diff --git a/migrations/20260524_001_add_rolls_to_inventory_records.sql b/migrations/20260524_001_add_rolls_to_inventory_records.sql new file mode 100644 index 0000000..663d8f0 --- /dev/null +++ b/migrations/20260524_001_add_rolls_to_inventory_records.sql @@ -0,0 +1,5 @@ +-- ============================================ +-- 为 inventory_records 表添加匹数字段 +-- ============================================ + +ALTER TABLE inventory_records ADD COLUMN rolls INTEGER DEFAULT 0; diff --git a/migrations/20260524_002_create_accounts_payable.sql b/migrations/20260524_002_create_accounts_payable.sql new file mode 100644 index 0000000..c59e548 --- /dev/null +++ b/migrations/20260524_002_create_accounts_payable.sql @@ -0,0 +1,53 @@ +-- ============================================ +-- 应付账款功能 - 创建应付账款表 +-- ============================================ + +-- 应付账款表 +CREATE TABLE public.accounts_payable ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES public.production_plans(id) ON DELETE CASCADE, + purchaser_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, + textile_factory_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, + -- 金额信息 + total_amount DECIMAL NOT NULL DEFAULT 0, + paid_amount DECIMAL NOT NULL DEFAULT 0, + unpaid_amount DECIMAL NOT NULL DEFAULT 0, + -- 数量信息 + total_quantity DECIMAL NOT NULL DEFAULT 0, + paid_quantity DECIMAL NOT NULL DEFAULT 0, + unpaid_quantity DECIMAL NOT NULL DEFAULT 0, + -- 单价(元/米) + price_per_meter DECIMAL NOT NULL DEFAULT 0, + -- 状态 + status TEXT NOT NULL DEFAULT 'unpaid', + -- 时间 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 应付账款明细表(记录每次入库产生的应付记录) +CREATE TABLE public.accounts_payable_items ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + accounts_payable_id UUID NOT NULL REFERENCES public.accounts_payable(id) ON DELETE CASCADE, + inventory_record_id UUID REFERENCES public.inventory_records(id) ON DELETE SET NULL, + -- 入库信息 + quantity DECIMAL NOT NULL DEFAULT 0, + rolls INTEGER DEFAULT 0, + price_per_meter DECIMAL NOT NULL DEFAULT 0, + amount DECIMAL NOT NULL DEFAULT 0, + -- 状态 + status TEXT NOT NULL DEFAULT 'unpaid', + -- 关联的付款记录 + payment_id UUID REFERENCES public.payments(id) ON DELETE SET NULL, + -- 时间 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + paid_at TIMESTAMPTZ +); + +-- 添加索引 +CREATE INDEX idx_accounts_payable_plan ON public.accounts_payable(plan_id); +CREATE INDEX idx_accounts_payable_purchaser ON public.accounts_payable(purchaser_id); +CREATE INDEX idx_accounts_payable_factory ON public.accounts_payable(textile_factory_id); +CREATE INDEX idx_accounts_payable_status ON public.accounts_payable(status); +CREATE INDEX idx_accounts_payable_items_ap ON public.accounts_payable_items(accounts_payable_id); +CREATE INDEX idx_accounts_payable_items_status ON public.accounts_payable_items(status); diff --git a/migrations/20260524_003_add_price_to_inventory.sql b/migrations/20260524_003_add_price_to_inventory.sql new file mode 100644 index 0000000..d32da88 --- /dev/null +++ b/migrations/20260524_003_add_price_to_inventory.sql @@ -0,0 +1,14 @@ +-- ============================================ +-- 入库记录添加单价字段 +-- ============================================ + +-- 为入库记录表添加单价字段 +ALTER TABLE public.inventory_records +ADD COLUMN IF NOT EXISTS price_per_meter DECIMAL DEFAULT 0; + +-- 添加备注字段记录价格变动原因 +ALTER TABLE public.inventory_records +ADD COLUMN IF NOT EXISTS price_note TEXT; + +-- 添加索引 +CREATE INDEX IF NOT EXISTS idx_inventory_records_price ON public.inventory_records(price_per_meter); diff --git a/migrations/20260524_004_add_accounts_payable_rls.sql b/migrations/20260524_004_add_accounts_payable_rls.sql new file mode 100644 index 0000000..97504fd --- /dev/null +++ b/migrations/20260524_004_add_accounts_payable_rls.sql @@ -0,0 +1,65 @@ +-- ============================================ +-- 应付账款表 RLS 策略 +-- ============================================ + +-- 启用 RLS +ALTER TABLE public.accounts_payable ENABLE ROW LEVEL SECURITY; +ALTER TABLE public.accounts_payable_items ENABLE ROW LEVEL SECURITY; + +-- 应付账款表策略 +-- 采购商可以查看自己的应付账款 +CREATE POLICY purchaser_select_ap ON public.accounts_payable + FOR SELECT USING (purchaser_id = get_user_master_company_id()); + +-- 纺织厂可以查看与自己相关的应付账款 +CREATE POLICY textile_select_ap ON public.accounts_payable + FOR SELECT USING (textile_factory_id = get_user_master_company_id()); + +-- 采购商可以创建应付账款 +CREATE POLICY purchaser_insert_ap ON public.accounts_payable + FOR INSERT WITH CHECK (purchaser_id = get_user_master_company_id()); + +-- 采购商可以更新自己的应付账款 +CREATE POLICY purchaser_update_ap ON public.accounts_payable + FOR UPDATE USING (purchaser_id = get_user_master_company_id()); + +-- 应付账款明细表策略 +-- 采购商可以查看自己的应付账款明细 +CREATE POLICY purchaser_select_ap_items ON public.accounts_payable_items + FOR SELECT USING ( + EXISTS ( + SELECT 1 FROM public.accounts_payable ap + WHERE ap.id = accounts_payable_items.accounts_payable_id + AND ap.purchaser_id = get_user_master_company_id() + ) + ); + +-- 纺织厂可以查看与自己相关的应付账款明细 +CREATE POLICY textile_select_ap_items ON public.accounts_payable_items + FOR SELECT USING ( + EXISTS ( + SELECT 1 FROM public.accounts_payable ap + WHERE ap.id = accounts_payable_items.accounts_payable_id + AND ap.textile_factory_id = get_user_master_company_id() + ) + ); + +-- 采购商可以创建应付账款明细 +CREATE POLICY purchaser_insert_ap_items ON public.accounts_payable_items + FOR INSERT WITH CHECK ( + EXISTS ( + SELECT 1 FROM public.accounts_payable ap + WHERE ap.id = accounts_payable_items.accounts_payable_id + AND ap.purchaser_id = get_user_master_company_id() + ) + ); + +-- 采购商可以更新自己的应付账款明细 +CREATE POLICY purchaser_update_ap_items ON public.accounts_payable_items + FOR UPDATE USING ( + EXISTS ( + SELECT 1 FROM public.accounts_payable ap + WHERE ap.id = accounts_payable_items.accounts_payable_id + AND ap.purchaser_id = get_user_master_company_id() + ) + ); diff --git a/migrations/20260524_005_create_price_history.sql b/migrations/20260524_005_create_price_history.sql new file mode 100644 index 0000000..05065d2 --- /dev/null +++ b/migrations/20260524_005_create_price_history.sql @@ -0,0 +1,58 @@ +-- ============================================ +-- 生产采购价变更记录表 +-- ============================================ + +CREATE TABLE public.production_price_history ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_id UUID NOT NULL REFERENCES public.production_plans(id) ON DELETE CASCADE, + -- 变更信息 + old_price DECIMAL NOT NULL DEFAULT 0, + new_price DECIMAL NOT NULL DEFAULT 0, + change_amount DECIMAL NOT NULL DEFAULT 0, + -- 操作人 + operator_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, + operator_name TEXT, + -- 变更原因/备注 + change_reason TEXT, + -- 时间 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 添加索引 +CREATE INDEX idx_price_history_plan ON public.production_price_history(plan_id); +CREATE INDEX idx_price_history_created ON public.production_price_history(created_at); + +-- 启用 RLS +ALTER TABLE public.production_price_history ENABLE ROW LEVEL SECURITY; + +-- RLS 策略 +-- 采购商可以查看自己计划的价格变更记录 +CREATE POLICY purchaser_select_price_history ON public.production_price_history + FOR SELECT USING ( + EXISTS ( + SELECT 1 FROM public.production_plans p + WHERE p.id = production_price_history.plan_id + AND p.purchaser_id = get_user_master_company_id() + ) + ); + +-- 纺织厂可以查看关联计划的价格变更记录 +CREATE POLICY textile_select_price_history ON public.production_price_history + FOR SELECT USING ( + EXISTS ( + SELECT 1 FROM public.production_plans p + JOIN public.plan_factories pf ON pf.plan_id = p.id + WHERE p.id = production_price_history.plan_id + AND pf.factory_id = get_user_master_company_id() + ) + ); + +-- 采购商可以创建价格变更记录 +CREATE POLICY purchaser_insert_price_history ON public.production_price_history + FOR INSERT WITH CHECK ( + EXISTS ( + SELECT 1 FROM public.production_plans p + WHERE p.id = production_price_history.plan_id + AND p.purchaser_id = get_user_master_company_id() + ) + ); diff --git a/migrations/20260524_006_create_product_price_history.sql b/migrations/20260524_006_create_product_price_history.sql new file mode 100644 index 0000000..e587689 --- /dev/null +++ b/migrations/20260524_006_create_product_price_history.sql @@ -0,0 +1,37 @@ +-- ============================================ +-- 产品价格变更历史表 +-- ============================================ + +CREATE TABLE public.product_price_history ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + product_id UUID NOT NULL REFERENCES public.products(id) ON DELETE CASCADE, + company_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, + -- 变更信息 + old_price DECIMAL NOT NULL DEFAULT 0, + new_price DECIMAL NOT NULL DEFAULT 0, + change_amount DECIMAL NOT NULL DEFAULT 0, + -- 操作人 + operator_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, + operator_name TEXT, + -- 变更原因/备注 + change_reason TEXT, + -- 时间 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 添加索引 +CREATE INDEX idx_product_price_history_product ON public.product_price_history(product_id); +CREATE INDEX idx_product_price_history_company ON public.product_price_history(company_id); +CREATE INDEX idx_product_price_history_created ON public.product_price_history(created_at); + +-- 启用 RLS +ALTER TABLE public.product_price_history ENABLE ROW LEVEL SECURITY; + +-- RLS 策略 +-- 公司成员可以查看自己公司的产品价格变更记录 +CREATE POLICY company_select_price_history ON public.product_price_history + FOR SELECT USING (company_id = get_user_master_company_id()); + +-- 公司成员可以创建价格变更记录 +CREATE POLICY company_insert_price_history ON public.product_price_history + FOR INSERT WITH CHECK (company_id = get_user_master_company_id()); diff --git a/migrations/20260524_054617_create_accounts_payable.sql b/migrations/20260524_054617_create_accounts_payable.sql new file mode 100644 index 0000000..af590dc --- /dev/null +++ b/migrations/20260524_054617_create_accounts_payable.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +CREATE TABLE public.accounts_payable (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), plan_id UUID NOT NULL REFERENCES public.production_plans(id) ON DELETE CASCADE, purchaser_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, textile_factory_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, total_amount DECIMAL NOT NULL DEFAULT 0, paid_amount DECIMAL NOT NULL DEFAULT 0, unpaid_amount DECIMAL NOT NULL DEFAULT 0, total_quantity DECIMAL NOT NULL DEFAULT 0, paid_quantity DECIMAL NOT NULL DEFAULT 0, unpaid_quantity DECIMAL NOT NULL DEFAULT 0, price_per_meter DECIMAL NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'unpaid', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()); CREATE TABLE public.accounts_payable_items (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), accounts_payable_id UUID NOT NULL REFERENCES public.accounts_payable(id) ON DELETE CASCADE, inventory_record_id UUID REFERENCES public.inventory_records(id) ON DELETE SET NULL, quantity DECIMAL NOT NULL DEFAULT 0, rolls INTEGER DEFAULT 0, price_per_meter DECIMAL NOT NULL DEFAULT 0, amount DECIMAL NOT NULL DEFAULT 0, status TEXT NOT NULL DEFAULT 'unpaid', payment_id UUID REFERENCES public.payments(id) ON DELETE SET NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), paid_at TIMESTAMPTZ); CREATE INDEX idx_accounts_payable_plan ON public.accounts_payable(plan_id); CREATE INDEX idx_accounts_payable_purchaser ON public.accounts_payable(purchaser_id); CREATE INDEX idx_accounts_payable_factory ON public.accounts_payable(textile_factory_id); CREATE INDEX idx_accounts_payable_status ON public.accounts_payable(status); CREATE INDEX idx_accounts_payable_items_ap ON public.accounts_payable_items(accounts_payable_id); CREATE INDEX idx_accounts_payable_items_status ON public.accounts_payable_items(status); diff --git a/migrations/20260524_054623_add_price_to_inventory.sql b/migrations/20260524_054623_add_price_to_inventory.sql new file mode 100644 index 0000000..58595f3 --- /dev/null +++ b/migrations/20260524_054623_add_price_to_inventory.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +ALTER TABLE public.inventory_records ADD COLUMN IF NOT EXISTS price_per_meter DECIMAL DEFAULT 0; ALTER TABLE public.inventory_records ADD COLUMN IF NOT EXISTS price_note TEXT; CREATE INDEX IF NOT EXISTS idx_inventory_records_price ON public.inventory_records(price_per_meter); diff --git a/migrations/20260524_054637_add_accounts_payable_rls.sql b/migrations/20260524_054637_add_accounts_payable_rls.sql new file mode 100644 index 0000000..78e51c8 --- /dev/null +++ b/migrations/20260524_054637_add_accounts_payable_rls.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +ALTER TABLE public.accounts_payable ENABLE ROW LEVEL SECURITY; ALTER TABLE public.accounts_payable_items ENABLE ROW LEVEL SECURITY; CREATE POLICY purchaser_select_ap ON public.accounts_payable FOR SELECT USING (purchaser_id = get_user_master_company_id()); CREATE POLICY textile_select_ap ON public.accounts_payable FOR SELECT USING (textile_factory_id = get_user_master_company_id()); CREATE POLICY purchaser_insert_ap ON public.accounts_payable FOR INSERT WITH CHECK (purchaser_id = get_user_master_company_id()); CREATE POLICY purchaser_update_ap ON public.accounts_payable FOR UPDATE USING (purchaser_id = get_user_master_company_id()); CREATE POLICY purchaser_select_ap_items ON public.accounts_payable_items FOR SELECT USING (EXISTS (SELECT 1 FROM public.accounts_payable ap WHERE ap.id = accounts_payable_items.accounts_payable_id AND ap.purchaser_id = get_user_master_company_id())); CREATE POLICY textile_select_ap_items ON public.accounts_payable_items FOR SELECT USING (EXISTS (SELECT 1 FROM public.accounts_payable ap WHERE ap.id = accounts_payable_items.accounts_payable_id AND ap.textile_factory_id = get_user_master_company_id())); CREATE POLICY purchaser_insert_ap_items ON public.accounts_payable_items FOR INSERT WITH CHECK (EXISTS (SELECT 1 FROM public.accounts_payable ap WHERE ap.id = accounts_payable_items.accounts_payable_id AND ap.purchaser_id = get_user_master_company_id())); CREATE POLICY purchaser_update_ap_items ON public.accounts_payable_items FOR UPDATE USING (EXISTS (SELECT 1 FROM public.accounts_payable ap WHERE ap.id = accounts_payable_items.accounts_payable_id AND ap.purchaser_id = get_user_master_company_id())); diff --git a/migrations/20260524_055450_create_price_history.sql b/migrations/20260524_055450_create_price_history.sql new file mode 100644 index 0000000..1a0b344 --- /dev/null +++ b/migrations/20260524_055450_create_price_history.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +CREATE TABLE public.production_price_history (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), plan_id UUID NOT NULL REFERENCES public.production_plans(id) ON DELETE CASCADE, old_price DECIMAL NOT NULL DEFAULT 0, new_price DECIMAL NOT NULL DEFAULT 0, change_amount DECIMAL NOT NULL DEFAULT 0, operator_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, operator_name TEXT, change_reason TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()); CREATE INDEX idx_price_history_plan ON public.production_price_history(plan_id); CREATE INDEX idx_price_history_created ON public.production_price_history(created_at); ALTER TABLE public.production_price_history ENABLE ROW LEVEL SECURITY; CREATE POLICY purchaser_select_price_history ON public.production_price_history FOR SELECT USING (EXISTS (SELECT 1 FROM public.production_plans p WHERE p.id = production_price_history.plan_id AND p.purchaser_id = get_user_master_company_id())); CREATE POLICY textile_select_price_history ON public.production_price_history FOR SELECT USING (EXISTS (SELECT 1 FROM public.production_plans p JOIN public.plan_factories pf ON pf.plan_id = p.id WHERE p.id = production_price_history.plan_id AND pf.factory_id = get_user_master_company_id())); CREATE POLICY purchaser_insert_price_history ON public.production_price_history FOR INSERT WITH CHECK (EXISTS (SELECT 1 FROM public.production_plans p WHERE p.id = production_price_history.plan_id AND p.purchaser_id = get_user_master_company_id())); diff --git a/migrations/20260524_055840_create_product_price_history.sql b/migrations/20260524_055840_create_product_price_history.sql new file mode 100644 index 0000000..c45bc08 --- /dev/null +++ b/migrations/20260524_055840_create_product_price_history.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +CREATE TABLE public.product_price_history (id UUID PRIMARY KEY DEFAULT gen_random_uuid(), product_id UUID NOT NULL REFERENCES public.products(id) ON DELETE CASCADE, company_id UUID NOT NULL REFERENCES public.companies(id) ON DELETE CASCADE, old_price DECIMAL NOT NULL DEFAULT 0, new_price DECIMAL NOT NULL DEFAULT 0, change_amount DECIMAL NOT NULL DEFAULT 0, operator_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, operator_name TEXT, change_reason TEXT, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()); CREATE INDEX idx_product_price_history_product ON public.product_price_history(product_id); CREATE INDEX idx_product_price_history_company ON public.product_price_history(company_id); CREATE INDEX idx_product_price_history_created ON public.product_price_history(created_at); ALTER TABLE public.product_price_history ENABLE ROW LEVEL SECURITY; CREATE POLICY company_select_price_history ON public.product_price_history FOR SELECT USING (company_id = get_user_master_company_id()); CREATE POLICY company_insert_price_history ON public.product_price_history FOR INSERT WITH CHECK (company_id = get_user_master_company_id()); diff --git a/migrations/20260524_100730_add_display_name_to_profiles.sql b/migrations/20260524_100730_add_display_name_to_profiles.sql new file mode 100644 index 0000000..8252e78 --- /dev/null +++ b/migrations/20260524_100730_add_display_name_to_profiles.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +ALTER TABLE profiles ADD COLUMN display_name TEXT; diff --git a/migrations/20260524_144737_create_washing_plans_table.sql b/migrations/20260524_144737_create_washing_plans_table.sql new file mode 100644 index 0000000..bf1b8bf --- /dev/null +++ b/migrations/20260524_144737_create_washing_plans_table.sql @@ -0,0 +1,66 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 创建水洗计划表 +CREATE TABLE washing_plans ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + plan_code TEXT UNIQUE NOT NULL, + company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE, + product_id UUID NOT NULL REFERENCES products(id) ON DELETE CASCADE, + washing_factory_id UUID REFERENCES companies(id) ON DELETE SET NULL, + + -- 计划信息 + planned_meters DECIMAL(12, 2) NOT NULL DEFAULT 0, + washing_price DECIMAL(10, 2) NOT NULL DEFAULT 0, + estimated_shrinkage_rate DECIMAL(5, 2) NOT NULL DEFAULT 0, + + -- 自动计算字段 + estimated_washed_meters DECIMAL(12, 2) GENERATED ALWAYS AS (planned_meters * (1 - estimated_shrinkage_rate / 100)) STORED, + estimated_total_cost DECIMAL(12, 2) GENERATED ALWAYS AS (planned_meters * washing_price) STORED, + + -- 实际完成数据 + actual_shrinkage_rate DECIMAL(5, 2), + actual_washed_meters DECIMAL(12, 2), + actual_total_cost DECIMAL(12, 2), + washing_date DATE, + + -- 状态 + status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'processing', 'completed')), + + -- 备注 + notes TEXT, + + -- 元数据 + created_by UUID REFERENCES profiles(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 创建索引 +CREATE INDEX idx_washing_plans_company ON washing_plans(company_id); +CREATE INDEX idx_washing_plans_product ON washing_plans(product_id); +CREATE INDEX idx_washing_plans_factory ON washing_plans(washing_factory_id); +CREATE INDEX idx_washing_plans_status ON washing_plans(status); + +-- 启用 RLS +ALTER TABLE washing_plans ENABLE ROW LEVEL SECURITY; + +-- RLS 策略:公司成员可查看本公司水洗计划 +CREATE POLICY company_select_washing_plans ON washing_plans + FOR SELECT USING (company_id = get_user_master_company_id()); + +-- RLS 策略:公司成员可创建本公司水洗计划 +CREATE POLICY company_insert_washing_plans ON washing_plans + FOR INSERT WITH CHECK (company_id = get_user_master_company_id()); + +-- RLS 策略:公司成员可更新本公司水洗计划 +CREATE POLICY company_update_washing_plans ON washing_plans + FOR UPDATE USING (company_id = get_user_master_company_id()); + +-- RLS 策略:公司成员可删除本公司水洗计划 +CREATE POLICY company_delete_washing_plans ON washing_plans + FOR DELETE USING (company_id = get_user_master_company_id()); + diff --git a/migrations/20260524_153210_create_product_outbound_records_table.sql b/migrations/20260524_153210_create_product_outbound_records_table.sql new file mode 100644 index 0000000..c63b822 --- /dev/null +++ b/migrations/20260524_153210_create_product_outbound_records_table.sql @@ -0,0 +1,41 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +CREATE TABLE public.product_outbound_records ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + product_id UUID NOT NULL REFERENCES public.products(id) ON DELETE CASCADE, + company_id UUID REFERENCES public.companies(id) ON DELETE CASCADE, + batch_no TEXT NOT NULL DEFAULT '', + rolls INTEGER NOT NULL DEFAULT 0, + meters NUMERIC NOT NULL DEFAULT 0, + notes TEXT, + operator_id UUID REFERENCES auth.users(id) ON DELETE SET NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + -- 关联水洗计划(如果是自动出库) + washing_plan_id UUID, + -- 出库类型: manual(手动), auto_washing(水洗计划自动出库) + outbound_type TEXT NOT NULL DEFAULT 'manual' +); + +-- 创建索引 +CREATE INDEX idx_product_outbound_product_id ON public.product_outbound_records(product_id); +CREATE INDEX idx_product_outbound_company_id ON public.product_outbound_records(company_id); +CREATE INDEX idx_product_outbound_created_at ON public.product_outbound_records(created_at DESC); +CREATE INDEX idx_product_outbound_washing_plan ON public.product_outbound_records(washing_plan_id); + +-- 启用 RLS +ALTER TABLE public.product_outbound_records ENABLE ROW LEVEL SECURITY; + +-- RLS 策略 +CREATE POLICY company_select_outbound ON public.product_outbound_records + FOR SELECT USING (company_id = get_user_master_company_id()); + +CREATE POLICY company_insert_outbound ON public.product_outbound_records + FOR INSERT WITH CHECK (company_id = get_user_master_company_id()); + +CREATE POLICY company_delete_outbound ON public.product_outbound_records + FOR DELETE USING (company_id = get_user_master_company_id()); + diff --git a/migrations/20260524_155028_fix_plan_factories_insert_policy.sql b/migrations/20260524_155028_fix_plan_factories_insert_policy.sql new file mode 100644 index 0000000..62836d8 --- /dev/null +++ b/migrations/20260524_155028_fix_plan_factories_insert_policy.sql @@ -0,0 +1,23 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 修改 plan_factories 的 INSERT 策略,允许创建不关联具体计划的工厂记录 +DROP POLICY IF EXISTS users_insert_plan_factories ON plan_factories; + +CREATE POLICY users_insert_plan_factories ON plan_factories + FOR INSERT + WITH CHECK ( + -- 允许创建关联到当前用户计划的记录 + EXISTS ( + SELECT 1 FROM production_plans + WHERE production_plans.id = plan_factories.plan_id + AND production_plans.purchaser_id = get_user_master_company_id() + ) + OR + -- 允许创建虚拟 plan_id (全零UUID) 的记录,用于工厂管理 + plan_factories.plan_id = '00000000-0000-0000-0000-000000000000'::uuid + ); + diff --git a/migrations/20260524_155840_create_finished_products_table.sql b/migrations/20260524_155840_create_finished_products_table.sql new file mode 100644 index 0000000..59c89cb --- /dev/null +++ b/migrations/20260524_155840_create_finished_products_table.sql @@ -0,0 +1,77 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 创建成品仓库表 +CREATE TABLE finished_products ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE, + + -- 关联信息 + washing_plan_id UUID REFERENCES washing_plans(id) ON DELETE SET NULL, + product_id UUID REFERENCES products(id) ON DELETE SET NULL, + + -- 产品信息(从水洗计划继承) + product_name TEXT NOT NULL, + color TEXT NOT NULL, + fabric_code TEXT NOT NULL, + color_code TEXT, + weight DECIMAL(8, 2), + + -- 水洗后数据 + washed_meters DECIMAL(12, 2) NOT NULL DEFAULT 0, + shrinkage_rate DECIMAL(5, 2) NOT NULL DEFAULT 0, + rolls INTEGER DEFAULT 0, + + -- 库存数据 + stock_meters DECIMAL(12, 2) NOT NULL DEFAULT 0, + stock_rolls INTEGER NOT NULL DEFAULT 0, + + -- 仓库位置 + warehouse_id UUID REFERENCES warehouses(id) ON DELETE SET NULL, + warehouse_location TEXT, + + -- 价格信息 + washing_cost DECIMAL(10, 2) DEFAULT 0, + unit_cost DECIMAL(10, 2) DEFAULT 0, + + -- 状态 + status TEXT NOT NULL DEFAULT 'in_stock' CHECK (status IN ('in_stock', 'partial_out', 'sold_out')), + + -- 备注 + notes TEXT, + + -- 元数据 + created_by UUID REFERENCES profiles(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 创建索引 +CREATE INDEX idx_finished_products_company ON finished_products(company_id); +CREATE INDEX idx_finished_products_washing_plan ON finished_products(washing_plan_id); +CREATE INDEX idx_finished_products_product ON finished_products(product_id); +CREATE INDEX idx_finished_products_status ON finished_products(status); +CREATE INDEX idx_finished_products_fabric_code ON finished_products(fabric_code); + +-- 启用 RLS +ALTER TABLE finished_products ENABLE ROW LEVEL SECURITY; + +-- RLS 策略:公司成员可查看本公司成品 +CREATE POLICY company_select_finished_products ON finished_products + FOR SELECT USING (company_id = get_user_master_company_id()); + +-- RLS 策略:公司成员可创建本公司成品 +CREATE POLICY company_insert_finished_products ON finished_products + FOR INSERT WITH CHECK (company_id = get_user_master_company_id()); + +-- RLS 策略:公司成员可更新本公司成品 +CREATE POLICY company_update_finished_products ON finished_products + FOR UPDATE USING (company_id = get_user_master_company_id()); + +-- RLS 策略:公司成员可删除本公司成品 +CREATE POLICY company_delete_finished_products ON finished_products + FOR DELETE USING (company_id = get_user_master_company_id()); + diff --git a/migrations/20260524_155925_create_washing_plan_completions_table.sql b/migrations/20260524_155925_create_washing_plan_completions_table.sql new file mode 100644 index 0000000..cc56ece --- /dev/null +++ b/migrations/20260524_155925_create_washing_plan_completions_table.sql @@ -0,0 +1,63 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 创建水洗计划完成记录表 +CREATE TABLE washing_plan_completions ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE, + + -- 关联信息 + washing_plan_id UUID NOT NULL REFERENCES washing_plans(id) ON DELETE CASCADE, + + -- 完成数据 + actual_washed_meters DECIMAL(12, 2) NOT NULL, + actual_shrinkage_rate DECIMAL(5, 2) NOT NULL, + rolls INTEGER NOT NULL DEFAULT 0, + washing_date DATE NOT NULL, + + -- 成本数据 + actual_washing_cost DECIMAL(10, 2), + unit_cost DECIMAL(10, 2), + + -- 仓库信息 + warehouse_id UUID REFERENCES warehouses(id) ON DELETE SET NULL, + warehouse_location TEXT, + + -- 自动导入成品仓库标记 + auto_imported BOOLEAN DEFAULT FALSE, + finished_product_id UUID REFERENCES finished_products(id) ON DELETE SET NULL, + + -- 备注 + notes TEXT, + + -- 元数据 + completed_by UUID REFERENCES profiles(id), + completed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 创建索引 +CREATE INDEX idx_washing_completions_company ON washing_plan_completions(company_id); +CREATE INDEX idx_washing_completions_plan ON washing_plan_completions(washing_plan_id); +CREATE INDEX idx_washing_completions_finished_product ON washing_plan_completions(finished_product_id); +CREATE INDEX idx_washing_completions_date ON washing_plan_completions(washing_date); + +-- 启用 RLS +ALTER TABLE washing_plan_completions ENABLE ROW LEVEL SECURITY; + +-- RLS 策略 +CREATE POLICY company_select_washing_completions ON washing_plan_completions + FOR SELECT USING (company_id = get_user_master_company_id()); + +CREATE POLICY company_insert_washing_completions ON washing_plan_completions + FOR INSERT WITH CHECK (company_id = get_user_master_company_id()); + +CREATE POLICY company_update_washing_completions ON washing_plan_completions + FOR UPDATE USING (company_id = get_user_master_company_id()); + +CREATE POLICY company_delete_washing_completions ON washing_plan_completions + FOR DELETE USING (company_id = get_user_master_company_id()); + diff --git a/migrations/20260524_155928_create_finished_product_inventory_records_table.sql b/migrations/20260524_155928_create_finished_product_inventory_records_table.sql new file mode 100644 index 0000000..d981249 --- /dev/null +++ b/migrations/20260524_155928_create_finished_product_inventory_records_table.sql @@ -0,0 +1,61 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 创建成品出入库记录表 +CREATE TABLE finished_product_inventory_records ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE, + + -- 关联信息 + finished_product_id UUID NOT NULL REFERENCES finished_products(id) ON DELETE CASCADE, + + -- 记录类型 + record_type TEXT NOT NULL CHECK (record_type IN ('inbound', 'outbound')), + + -- 数量信息 + rolls INTEGER NOT NULL DEFAULT 0, + meters DECIMAL(12, 2) NOT NULL DEFAULT 0, + + -- 关联单据 + washing_completion_id UUID REFERENCES washing_plan_completions(id) ON DELETE SET NULL, + related_order_id UUID, + related_order_type TEXT, + + -- 仓库信息 + warehouse_id UUID REFERENCES warehouses(id) ON DELETE SET NULL, + warehouse_location TEXT, + + -- 备注 + notes TEXT, + batch_no TEXT, + + -- 元数据 + operator_id UUID REFERENCES profiles(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 创建索引 +CREATE INDEX idx_finished_inventory_company ON finished_product_inventory_records(company_id); +CREATE INDEX idx_finished_inventory_product ON finished_product_inventory_records(finished_product_id); +CREATE INDEX idx_finished_inventory_type ON finished_product_inventory_records(record_type); +CREATE INDEX idx_finished_inventory_created ON finished_product_inventory_records(created_at); + +-- 启用 RLS +ALTER TABLE finished_product_inventory_records ENABLE ROW LEVEL SECURITY; + +-- RLS 策略 +CREATE POLICY company_select_finished_inventory ON finished_product_inventory_records + FOR SELECT USING (company_id = get_user_master_company_id()); + +CREATE POLICY company_insert_finished_inventory ON finished_product_inventory_records + FOR INSERT WITH CHECK (company_id = get_user_master_company_id()); + +CREATE POLICY company_update_finished_inventory ON finished_product_inventory_records + FOR UPDATE USING (company_id = get_user_master_company_id()); + +CREATE POLICY company_delete_finished_inventory ON finished_product_inventory_records + FOR DELETE USING (company_id = get_user_master_company_id()); + diff --git a/migrations/20260524_160012_create_auto_import_function_v2.sql b/migrations/20260524_160012_create_auto_import_function_v2.sql new file mode 100644 index 0000000..a7ab08a --- /dev/null +++ b/migrations/20260524_160012_create_auto_import_function_v2.sql @@ -0,0 +1,64 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +CREATE OR REPLACE FUNCTION auto_import_to_finished_products() +RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ +DECLARE + v_washing_plan RECORD; + v_product RECORD; + v_warehouse RECORD; + v_finished_product_id UUID; +BEGIN + SELECT wp.*, p.product_name, p.color, p.fabric_code, p.color_code, p.weight + FROM washing_plans wp + JOIN products p ON wp.product_id = p.id + WHERE wp.id = NEW.washing_plan_id + INTO v_washing_plan; + + IF v_washing_plan IS NULL THEN + RAISE EXCEPTION 'Washing plan not found'; + END IF; + + SELECT * FROM warehouses WHERE id = NEW.warehouse_id INTO v_warehouse; + + INSERT INTO finished_products ( + company_id, washing_plan_id, product_id, product_name, color, fabric_code, color_code, weight, + washed_meters, shrinkage_rate, rolls, stock_meters, stock_rolls, warehouse_id, warehouse_location, + washing_cost, unit_cost, status, notes, created_by + ) VALUES ( + NEW.company_id, NEW.washing_plan_id, v_washing_plan.product_id, v_washing_plan.product_name, + v_washing_plan.color, v_washing_plan.fabric_code, v_washing_plan.color_code, v_washing_plan.weight, + NEW.actual_washed_meters, NEW.actual_shrinkage_rate, NEW.rolls, NEW.actual_washed_meters, NEW.rolls, + NEW.warehouse_id, NEW.warehouse_location, NEW.actual_washing_cost, NEW.unit_cost, 'in_stock', + NEW.notes, NEW.completed_by + ) RETURNING id INTO v_finished_product_id; + + INSERT INTO finished_product_inventory_records ( + company_id, finished_product_id, record_type, rolls, meters, washing_completion_id, + warehouse_id, warehouse_location, notes, batch_no, operator_id + ) VALUES ( + NEW.company_id, v_finished_product_id, 'inbound', NEW.rolls, NEW.actual_washed_meters, + NEW.id, NEW.warehouse_id, NEW.warehouse_location, '水洗完成自动入库', + 'WASH-' || TO_CHAR(NOW(), 'YYYYMMDD') || '-' || SUBSTRING(v_finished_product_id::text, 1, 8), + NEW.completed_by + ); + + UPDATE washing_plans SET + actual_washed_meters = NEW.actual_washed_meters, + actual_shrinkage_rate = NEW.actual_shrinkage_rate, + actual_total_cost = NEW.actual_washing_cost, + status = 'completed', + washing_date = NEW.washing_date, + updated_at = NOW() + WHERE id = NEW.washing_plan_id; + + NEW.finished_product_id := v_finished_product_id; + NEW.auto_imported := TRUE; + + RETURN NEW; +END; +$$; + diff --git a/migrations/20260524_160018_create_auto_import_trigger.sql b/migrations/20260524_160018_create_auto_import_trigger.sql new file mode 100644 index 0000000..d5c56c3 --- /dev/null +++ b/migrations/20260524_160018_create_auto_import_trigger.sql @@ -0,0 +1,12 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +DROP TRIGGER IF EXISTS trigger_auto_import_finished_product ON washing_plan_completions; +CREATE TRIGGER trigger_auto_import_finished_product + BEFORE INSERT ON washing_plan_completions + FOR EACH ROW + EXECUTE FUNCTION auto_import_to_finished_products(); + diff --git a/migrations/20260524_163600_create_washing_process_steps.sql b/migrations/20260524_163600_create_washing_process_steps.sql new file mode 100644 index 0000000..35d044a --- /dev/null +++ b/migrations/20260524_163600_create_washing_process_steps.sql @@ -0,0 +1,54 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 创建水洗流程步骤表 +CREATE TABLE washing_process_steps ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + washing_plan_id UUID NOT NULL REFERENCES washing_plans(id) ON DELETE CASCADE, + company_id UUID NOT NULL REFERENCES companies(id) ON DELETE CASCADE, + + -- 步骤类型: confirm(确认计划), start_washing(开始水洗), complete(拆布打匹出库) + step_type TEXT NOT NULL CHECK (step_type IN ('confirm', 'start_washing', 'complete')), + + -- 状态: pending(待处理), active(进行中), completed(已完成) + status TEXT NOT NULL DEFAULT 'pending' CHECK (status IN ('pending', 'active', 'completed')), + + -- 时间戳 + timestamp TIMESTAMPTZ, + + -- 操作人 + operator_id UUID REFERENCES profiles(id), + + -- 备注 + notes TEXT, + + -- 元数据 + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +-- 创建索引 +CREATE INDEX idx_washing_steps_plan ON washing_process_steps(washing_plan_id); +CREATE INDEX idx_washing_steps_company ON washing_process_steps(company_id); +CREATE INDEX idx_washing_steps_type ON washing_process_steps(step_type); +CREATE INDEX idx_washing_steps_status ON washing_process_steps(status); + +-- 启用 RLS +ALTER TABLE washing_process_steps ENABLE ROW LEVEL SECURITY; + +-- RLS 策略 +CREATE POLICY company_select_washing_steps ON washing_process_steps + FOR SELECT USING (company_id = get_user_master_company_id()); + +CREATE POLICY company_insert_washing_steps ON washing_process_steps + FOR INSERT WITH CHECK (company_id = get_user_master_company_id()); + +CREATE POLICY company_update_washing_steps ON washing_process_steps + FOR UPDATE USING (company_id = get_user_master_company_id()); + +CREATE POLICY company_delete_washing_steps ON washing_process_steps + FOR DELETE USING (company_id = get_user_master_company_id()); + diff --git a/migrations/20260524_165641_fix_washing_plans_rls.sql b/migrations/20260524_165641_fix_washing_plans_rls.sql new file mode 100644 index 0000000..ab56924 --- /dev/null +++ b/migrations/20260524_165641_fix_washing_plans_rls.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +DROP POLICY IF EXISTS company_select_washing_plans ON washing_plans; CREATE POLICY company_select_washing_plans ON washing_plans FOR SELECT USING (company_id = get_user_master_company_id() OR washing_factory_id = get_user_master_company_id()); DROP POLICY IF EXISTS company_update_washing_plans ON washing_plans; CREATE POLICY company_update_washing_plans ON washing_plans FOR UPDATE USING (company_id = get_user_master_company_id() OR washing_factory_id = get_user_master_company_id()); diff --git a/migrations/20260524_165658_fix_auto_import_function.sql b/migrations/20260524_165658_fix_auto_import_function.sql new file mode 100644 index 0000000..ae577f5 --- /dev/null +++ b/migrations/20260524_165658_fix_auto_import_function.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +CREATE OR REPLACE FUNCTION auto_import_to_finished_products() RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ DECLARE v_washing_plan RECORD; v_product RECORD; v_warehouse RECORD; v_finished_product_id UUID; v_purchaser_company_id UUID; BEGIN SELECT wp.*, p.product_name, p.color, p.fabric_code, p.color_code, p.weight, wp.company_id as purchaser_id FROM washing_plans wp JOIN products p ON wp.product_id = p.id WHERE wp.id = NEW.washing_plan_id INTO v_washing_plan; IF v_washing_plan IS NULL THEN RAISE EXCEPTION 'Washing plan not found'; END IF; v_purchaser_company_id := v_washing_plan.purchaser_id; SELECT * FROM warehouses WHERE id = NEW.warehouse_id INTO v_warehouse; INSERT INTO finished_products (company_id, washing_plan_id, product_id, product_name, color, fabric_code, color_code, weight, washed_meters, shrinkage_rate, rolls, stock_meters, stock_rolls, warehouse_id, warehouse_location, washing_cost, unit_cost, status, notes, created_by) VALUES (v_purchaser_company_id, NEW.washing_plan_id, v_washing_plan.product_id, v_washing_plan.product_name, v_washing_plan.color, v_washing_plan.fabric_code, v_washing_plan.color_code, v_washing_plan.weight, NEW.actual_washed_meters, NEW.actual_shrinkage_rate, NEW.rolls, NEW.actual_washed_meters, NEW.rolls, NEW.warehouse_id, NEW.warehouse_location, NEW.actual_washing_cost, NEW.unit_cost, 'in_stock', NEW.notes, NEW.completed_by) RETURNING id INTO v_finished_product_id; INSERT INTO finished_product_inventory_records (company_id, finished_product_id, record_type, rolls, meters, washing_completion_id, warehouse_id, warehouse_location, notes, batch_no, operator_id) VALUES (v_purchaser_company_id, v_finished_product_id, 'inbound', NEW.rolls, NEW.actual_washed_meters, NEW.id, NEW.warehouse_id, NEW.warehouse_location, '水洗完成自动入库', 'WASH-' || TO_CHAR(NOW(), 'YYYYMMDD') || '-' || SUBSTRING(v_finished_product_id::text, 1, 8), NEW.completed_by); UPDATE washing_plans SET actual_washed_meters = NEW.actual_washed_meters, actual_shrinkage_rate = NEW.actual_shrinkage_rate, actual_total_cost = NEW.actual_washing_cost, status = 'completed', washing_date = NEW.washing_date, updated_at = NOW() WHERE id = NEW.washing_plan_id; NEW.finished_product_id := v_finished_product_id; NEW.auto_imported := TRUE; RETURN NEW; END; $$; diff --git a/migrations/20260524_165708_fix_finished_products_rls.sql b/migrations/20260524_165708_fix_finished_products_rls.sql new file mode 100644 index 0000000..e081191 --- /dev/null +++ b/migrations/20260524_165708_fix_finished_products_rls.sql @@ -0,0 +1,6 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + +DROP POLICY IF EXISTS company_select_finished_products ON finished_products; CREATE POLICY company_select_finished_products ON finished_products FOR SELECT USING (company_id = get_user_master_company_id() OR EXISTS (SELECT 1 FROM washing_plans wp WHERE wp.id = finished_products.washing_plan_id AND wp.company_id = get_user_master_company_id())); DROP POLICY IF EXISTS company_update_finished_products ON finished_products; CREATE POLICY company_update_finished_products ON finished_products FOR UPDATE USING (company_id = get_user_master_company_id()); DROP POLICY IF EXISTS company_delete_finished_products ON finished_products; CREATE POLICY company_delete_finished_products ON finished_products FOR DELETE USING (company_id = get_user_master_company_id()); DROP POLICY IF EXISTS company_select_finished_inventory ON finished_product_inventory_records; CREATE POLICY company_select_finished_inventory ON finished_product_inventory_records FOR SELECT USING (company_id = get_user_master_company_id() OR EXISTS (SELECT 1 FROM finished_products fp WHERE fp.id = finished_product_inventory_records.finished_product_id AND EXISTS (SELECT 1 FROM washing_plans wp WHERE wp.id = fp.washing_plan_id AND wp.company_id = get_user_master_company_id()))); diff --git a/migrations/20260524_170000_fix_washing_plans_rls.sql b/migrations/20260524_170000_fix_washing_plans_rls.sql new file mode 100644 index 0000000..2c19a9f --- /dev/null +++ b/migrations/20260524_170000_fix_washing_plans_rls.sql @@ -0,0 +1,25 @@ +-- ============================================ +-- 修复水洗计划表RLS策略,让水洗厂能访问关联计划 +-- ============================================ + +-- 删除旧的SELECT策略 +DROP POLICY IF EXISTS company_select_washing_plans ON washing_plans; + +-- 创建新的SELECT策略:允许创建公司或关联的水洗厂查看 +CREATE POLICY company_select_washing_plans ON washing_plans + FOR SELECT USING ( + company_id = get_user_master_company_id() + OR washing_factory_id = get_user_master_company_id() + ); + +-- 更新策略:只允许创建公司更新 +DROP POLICY IF EXISTS company_update_washing_plans ON washing_plans; +CREATE POLICY company_update_washing_plans ON washing_plans + FOR UPDATE USING ( + company_id = get_user_master_company_id() + OR washing_factory_id = get_user_master_company_id() + ); + +-- 添加注释说明 +COMMENT ON POLICY company_select_washing_plans ON washing_plans IS '允许采购商(创建公司)或关联的水洗厂查看计划'; +COMMENT ON POLICY company_update_washing_plans ON washing_plans IS '允许采购商(创建公司)或关联的水洗厂更新计划'; diff --git a/migrations/20260524_170001_fix_auto_import_function.sql b/migrations/20260524_170001_fix_auto_import_function.sql new file mode 100644 index 0000000..7cfad80 --- /dev/null +++ b/migrations/20260524_170001_fix_auto_import_function.sql @@ -0,0 +1,96 @@ +-- ============================================ +-- 修复自动导入函数,让成品归属到采购商公司 +-- ============================================ + +CREATE OR REPLACE FUNCTION auto_import_to_finished_products() +RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public AS $$ +DECLARE + v_washing_plan RECORD; + v_product RECORD; + v_warehouse RECORD; + v_finished_product_id UUID; + v_purchaser_company_id UUID; -- 采购商公司ID(计划创建公司) +BEGIN + -- 获取水洗计划信息,同时获取采购商公司ID + SELECT wp.*, p.product_name, p.color, p.fabric_code, p.color_code, p.weight, + wp.company_id as purchaser_id -- 计划创建公司就是采购商 + FROM washing_plans wp + JOIN products p ON wp.product_id = p.id + WHERE wp.id = NEW.washing_plan_id + INTO v_washing_plan; + + IF v_washing_plan IS NULL THEN + RAISE EXCEPTION 'Washing plan not found'; + END IF; + + -- 采购商公司ID + v_purchaser_company_id := v_washing_plan.purchaser_id; + + SELECT * FROM warehouses WHERE id = NEW.warehouse_id INTO v_warehouse; + + -- 插入成品到采购商的成品仓库(使用采购商company_id,而非水洗厂) + INSERT INTO finished_products ( + company_id, washing_plan_id, product_id, product_name, color, fabric_code, color_code, weight, + washed_meters, shrinkage_rate, rolls, stock_meters, stock_rolls, warehouse_id, warehouse_location, + washing_cost, unit_cost, status, notes, created_by + ) VALUES ( + v_purchaser_company_id, -- 关键修复:归属到采购商公司 + NEW.washing_plan_id, + v_washing_plan.product_id, + v_washing_plan.product_name, + v_washing_plan.color, + v_washing_plan.fabric_code, + v_washing_plan.color_code, + v_washing_plan.weight, + NEW.actual_washed_meters, + NEW.actual_shrinkage_rate, + NEW.rolls, + NEW.actual_washed_meters, + NEW.rolls, + NEW.warehouse_id, + NEW.warehouse_location, + NEW.actual_washing_cost, + NEW.unit_cost, + 'in_stock', + NEW.notes, + NEW.completed_by + ) RETURNING id INTO v_finished_product_id; + + -- 插入入库记录(归属到采购商公司) + INSERT INTO finished_product_inventory_records ( + company_id, finished_product_id, record_type, rolls, meters, washing_completion_id, + warehouse_id, warehouse_location, notes, batch_no, operator_id + ) VALUES ( + v_purchaser_company_id, -- 关键修复:归属到采购商公司 + v_finished_product_id, + 'inbound', + NEW.rolls, + NEW.actual_washed_meters, + NEW.id, + NEW.warehouse_id, + NEW.warehouse_location, + '水洗完成自动入库', + 'WASH-' || TO_CHAR(NOW(), 'YYYYMMDD') || '-' || SUBSTRING(v_finished_product_id::text, 1, 8), + NEW.completed_by + ); + + -- 更新水洗计划状态 + UPDATE washing_plans SET + actual_washed_meters = NEW.actual_washed_meters, + actual_shrinkage_rate = NEW.actual_shrinkage_rate, + actual_total_cost = NEW.actual_washing_cost, + status = 'completed', + washing_date = NEW.washing_date, + updated_at = NOW() + WHERE id = NEW.washing_plan_id; + + -- 设置完成记录的关联信息 + NEW.finished_product_id := v_finished_product_id; + NEW.auto_imported := TRUE; + + RETURN NEW; +END; +$$; + +-- 添加函数注释 +COMMENT ON FUNCTION auto_import_to_finished_products() IS '水洗完成时自动导入成品到采购商的成品仓库'; diff --git a/migrations/20260524_170002_fix_finished_products_rls.sql b/migrations/20260524_170002_fix_finished_products_rls.sql new file mode 100644 index 0000000..112dd7a --- /dev/null +++ b/migrations/20260524_170002_fix_finished_products_rls.sql @@ -0,0 +1,49 @@ +-- ============================================ +-- 修复成品表RLS策略,让采购商能查看关联计划的成品 +-- ============================================ + +-- 删除旧的SELECT策略 +DROP POLICY IF EXISTS company_select_finished_products ON finished_products; + +-- 创建新的SELECT策略:允许成品所属公司或通过水洗计划关联的采购商查看 +CREATE POLICY company_select_finished_products ON finished_products + FOR SELECT USING ( + company_id = get_user_master_company_id() + OR EXISTS ( + SELECT 1 FROM washing_plans wp + WHERE wp.id = finished_products.washing_plan_id + AND wp.company_id = get_user_master_company_id() + ) + ); + +-- 更新策略:只允许成品所属公司更新 +DROP POLICY IF EXISTS company_update_finished_products ON finished_products; +CREATE POLICY company_update_finished_products ON finished_products + FOR UPDATE USING (company_id = get_user_master_company_id()); + +-- 删除策略:只允许成品所属公司删除 +DROP POLICY IF EXISTS company_delete_finished_products ON finished_products; +CREATE POLICY company_delete_finished_products ON finished_products + FOR DELETE USING (company_id = get_user_master_company_id()); + +-- 添加注释说明 +COMMENT ON POLICY company_select_finished_products ON finished_products IS '允许成品所属公司或通过水洗计划关联的采购商查看成品'; + +-- 同时修复成品出入库记录的RLS策略 +DROP POLICY IF EXISTS company_select_finished_inventory ON finished_product_inventory_records; + +CREATE POLICY company_select_finished_inventory ON finished_product_inventory_records + FOR SELECT USING ( + company_id = get_user_master_company_id() + OR EXISTS ( + SELECT 1 FROM finished_products fp + WHERE fp.id = finished_product_inventory_records.finished_product_id + AND EXISTS ( + SELECT 1 FROM washing_plans wp + WHERE wp.id = fp.washing_plan_id + AND wp.company_id = get_user_master_company_id() + ) + ) + ); + +COMMENT ON POLICY company_select_finished_inventory ON finished_product_inventory_records IS '允许成品所属公司或关联的采购商查看出入库记录'; diff --git a/migrations/20260525_060503_add_product_image_support.sql b/migrations/20260525_060503_add_product_image_support.sql new file mode 100644 index 0000000..366a23c --- /dev/null +++ b/migrations/20260525_060503_add_product_image_support.sql @@ -0,0 +1,14 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 1. 为 products 表添加图片字段 +ALTER TABLE products ADD COLUMN IF NOT EXISTS image_url TEXT; + +-- 2. 创建产品图片存储桶 +INSERT INTO storage.buckets (id, name, public) +VALUES ('product_images', 'product_images', true) +ON CONFLICT (id) DO NOTHING; + diff --git a/migrations/20260525_060536_add_product_images_rls.sql b/migrations/20260525_060536_add_product_images_rls.sql new file mode 100644 index 0000000..2d758ae --- /dev/null +++ b/migrations/20260525_060536_add_product_images_rls.sql @@ -0,0 +1,28 @@ +-- ============================================ +-- 此文件由 Meoo Cloud 自动生成,请勿修改 +-- This file is auto-generated by Meoo Cloud, DO NOT MODIFY +-- ============================================ + + +-- 为 product_images 存储桶添加 RLS 策略 +CREATE POLICY anon_select_product_images ON storage.objects +FOR SELECT USING (bucket_id = 'product_images'); + +CREATE POLICY users_insert_product_images ON storage.objects +FOR INSERT WITH CHECK ( + bucket_id = 'product_images' AND + auth.role() = 'authenticated' +); + +CREATE POLICY users_update_product_images ON storage.objects +FOR UPDATE WITH CHECK ( + bucket_id = 'product_images' AND + auth.role() = 'authenticated' +); + +CREATE POLICY users_delete_product_images ON storage.objects +FOR DELETE USING ( + bucket_id = 'product_images' AND + auth.role() = 'authenticated' +); + diff --git a/outputs/demo-animation-guide.md b/outputs/demo-animation-guide.md new file mode 100644 index 0000000..f9f784f --- /dev/null +++ b/outputs/demo-animation-guide.md @@ -0,0 +1,109 @@ +# 纺织行业采购计划管理系统 - 演示动画制作指南 + +## 演示动画脚本 (15-20秒) + +### 场景1: 采购商创建计划 (0-4秒) +- **画面**: 采购商工作台界面 +- **动作**: 点击"新建计划"按钮,填写表单 +- **关键信息展示**: + - 成品名称: 水洗棉花糖 + - 颜色: 灰色 + - 坯布码: SX1 + - 计划产量: 10000米 +- **文字说明**: "采购商创建生产计划" + +### 场景2: 生成分享链接 (4-7秒) +- **画面**: 计划详情页,点击分享按钮 +- **动作**: 系统生成加密链接 +- **关键信息展示**: + - 原始数据: `{ planId: "xxx", factoryType: "textile" }` + - 加密后: `/#/import?token=62eyJw...` +- **文字说明**: "敏感信息加密,仅传输计划ID" + +### 场景3: 纺织厂导入计划 (7-10秒) +- **画面**: 纺织厂导入页面 +- **动作**: 粘贴链接,点击导入 +- **关键信息展示**: + - ✅ 可见: 计划编号、坯布码、产量、纱用量 + - ❌ 隐藏: 成品名称、颜色、色号 +- **文字说明**: "纺织厂只能看到生产相关信息" + +### 场景4: 生产流程确认 (10-13秒) +- **画面**: 纺织厂计划详情,流程节点 +- **动作**: 依次确认: 采纱 → 染纱 → 上机 → 坯布入库 +- **关键信息展示**: + - 每个节点点击确认 + - 进度条更新 +- **文字说明**: "实时跟踪生产进度" + +### 场景5: 入库同步与通知 (13-17秒) +- **画面**: 分屏显示 + - 左侧: 纺织厂入库操作 (49匹/4400米) + - 右侧: 采购商仓库实时同步显示 +- **动作**: + - 纺织厂点击"确认入库" + - 数据实时同步到采购商仓库 + - 通知弹窗: "坯布入库完成" +- **文字说明**: "数据双向同步,实时通知" + +### 场景6: 系统特性总结 (17-20秒) +- **画面**: 系统Logo + 特性列表 +- **文字展示**: + - "多角色协作" + - "敏感信息保护" + - "实时数据同步" + - "智能通知提醒" +- **结束语**: "协作管理demo样机" + +--- + +## 技术实现建议 + +### 方案1: 使用 Excalidraw + 录屏 +1. 打开 `textile-system-demo.excalidraw` 文件 +2. 使用录屏软件 (如 OBS、Screen Studio) +3. 按照脚本逐步展示每个场景 +4. 添加文字说明和转场效果 + +### 方案2: 使用 Framer Motion 制作动画 +创建 React 组件,使用 Framer Motion 实现自动播放的演示动画 + +### 方案3: 使用视频编辑工具 +1. 截取系统实际运行画面 +2. 使用 Premiere Pro / Final Cut Pro / 剪映 编辑 +3. 添加转场、文字、背景音乐 + +--- + +## 关键画面截图建议 + +1. **登录页**: 展示纺织主题动画效果 +2. **采购商Dashboard**: 统计卡片 + 快捷操作 +3. **新建计划页**: 表单填写过程 +4. **分享链接弹窗**: 加密链接展示 +5. **导入计划页**: 对比显示可见/隐藏字段 +6. **流程节点页**: 5个步骤的确认过程 +7. **仓库管理页**: 入库记录和库存统计 +8. **通知中心**: 实时通知弹窗 + +--- + +## 配音脚本 (可选) + +``` +"协作管理demo样机,专为纺织行业打造。 +采购商创建计划,敏感信息自动加密。 +纺织厂通过链接导入,只能看到生产相关信息。 +实时跟踪生产进度,每个节点一键确认。 +入库数据双向同步,实时通知确保信息不遗漏。 +多角色协作,敏感信息保护,实时数据同步。 +协作管理demo样机,让纺织生产更高效。" +``` + +--- + +## 输出文件 + +- `textile-system-demo.excalidraw` - Excalidraw 流程图源文件 +- 可在 https://excalidraw.com 打开编辑 +- 支持导出为 PNG/SVG/剪贴板 diff --git a/outputs/demo-files.zip b/outputs/demo-files.zip new file mode 100644 index 0000000000000000000000000000000000000000..88086ede141528885787cd92ca45e46fe4edefaa GIT binary patch literal 3782 zcmajibx_pb-Usk6-Rja^N{G93DJr!z0!xE*Nta8AGzb`U$B$+aB!wlGkZ@UGr9(Oe zq&p-Ze)pNVbLZaY&NF8|=bV4e=kw2*dCgl(4G*6h003fulcm8VaUpB3m=FMpBmsaG zU^X5F1+;H-1}RYfEPb8&69gJzWw2ZxEN#aqqUge8~VD{M$~z zKepMI+iufP`kU-;oV~5VqppEL>-8?dY2gSJ1>E-s?MQ+5dYBmJ$U5C*1jm$P60G_~ zE(dS2K1IlQNW`FW@s*g@@HO4cUx||YYW*H9Qwmp<^VnsK&$l(7Z%u0`SjqUzvG`|* zu9L+JJ#y2UVKiQ7cYuGiN_JKZs5AUYVLfCj z;lD<#jubuecf48rC92Bq|`Mb zQj3vdXJLm}7*#gur}MAbwXAK-B2F3K+kW}+iY$* ztXtX5ex7wL0q3WOBL#SFYZuo;vO#WNZVnoPn$7>JJTa5rklr4Tcvi{ehqqXSkjn{q z295tBe-^!Aj6zWC<#wDtLXm8}Z?AH0$;$I}-9D{`qZ6qq!bu&MV=Jwu$N5Y%$yPzBqotY28;wg`q;W=XU+nSd~aLA$bOCDxRlgo9$F&NCeDj(4u>FMDwN#}}AN)($E zU3i!!_*lFxHjT%UG?6}`Ra`|Yr z>l-si_+a-sA#Yl!LIkv(T7WDx3#_t^!aZZ)Dv>cKCcq>(g@;ahY76F2O24bA;-!7? zk^oT)&T_QgebzwqW&`cCyD6Wux-hcYG*{zx9(%%(w}P&;%zr;WS?t06(f1h7k#>O$ zf8p+!UP#<4yv5!@2Jr(24mR1RZ~5nJD18rS27OG>-fq7Uh)jx`=&;1Ti9V_ia0YHPLkVUx<6T0j;}=EecU zcdzj_%PZOY<0}>QeIDw7FK)9j%0zE^vGJ#8ZEat_Y|&ql^tX>oU5H6gH}p)LWmj?G zCn|x!3cpVZW|84HiXym|2F?&{S`R|9;2l1#g+?$d&T{IsQXaXp7A*xKXZAi`-mZ%k z1kVG$fRie4uN4@Rl?(Rl7E(%9NFiU^>djBekj;-OsiRM#u3)vV;60Axy3r>+7F6BO zW|sc`sJ^~_`fcFE#r}&Mho9zshXWFcV^HIIenp))?Z{4<=WIt}Xot|Jb>?BrN6yog z-<#|?wj%Qc?0T4JEBS`WLf6-PRMJ|t-U-EWKOD(J+YA^c3mp#2_EJ9CL>ONhw-ZPt zPMlF|*7=fD(!@ja(zsxTAD~S>M{2u~L4L6}TLEu>9qzwu%}w8vqg)^HH#1I8suG15 zRCc<-E$0tRo-Qr_$w{#BOIM-pacp4)^>Bcd>XNKd;9N%c=^%l(Kk&VSKz2`Pa?Yy* zb$zw)m%pu2Mok~4ZEfu&USH=0buu}smGQI`%+B@Ct)J}9UCm#f#}oId;b4Xppyzgb z5NylQq^)}?l*K%yK#YHJXF!)${R2VDeHOZ?R1v+@#5*yZ7ghvA-jnoHAxTOUZ1(**+=yrX5i{LHc+J91x~TZ%~5=}v7c z(Dmc`(U5{z%I2c5r>a`+=NDfh%AGoc*qOk3y!=4+SzoW+inP{0`QyLkPmQ9NJm;1_ z3j6zV3pn9Fvc?ye>}aTakWIoGPl2&$OJLUTS27V~5sAlia*;ZGPVUU{8G=$2V3S zW!c&%4^V!e3mrv|G+KMVD#s~`aO-GlVq!OXX|b)`Ep;I5La?^_%(8?Zai6Mlja3cR zC)p7k#wmmtNm%?G4z{YnnIj-zK>@pca?BuLz9p~BCLl{dI4v9|v8K~7Ya-~hHl+zrEdweDcGPB{;NPNy1%ZH|t zvgBFYdfItS^5si$?cE=yDhb~ObEeGSz# za0$~GfV$VQ7GfBXp8ewEz8kAdzPu$*C)UO2^O(YOOP&r0QpXkDhfG>HT@?CE2u~46 z)EYb@a-OWDnZqR|22F8_N(fO4lt&uQ<|P9W4S2$+lwvd;IG`inR*K=~vkC{^D#EoTxTS(gcT-W%SVqc_jbpbh# zHg?7{b#L}y%H5J{m0;TV^ElLtqP8K3$a}XYP|lhL`P-=_P20S<`r}uN zc#Y&yj;x_4bR1LqCVc?<)Ax5DyR{|T<`*iZ0)HaWsv8H%2?4V5Jj&9xO{7NFy)3{T zGRh8$lksBaRxMInWHV9AbBk(<0P^Se*>LXH>Kmj2WlZ+H(adj6Wia_SdG-g-(=&)+94yVm$>VNipYQg6Xt2#X0tb%B z!qXDL&0zcQ8iFj0u~o|fjVQ#M`lP3|r7qo8o8x7JNk^hx;*4=%Z!|h&hL>@Yhxw}w zwM=NrO_m~md1t4b?oOV$n2vOUc%T%)#rh)HWj z5uBt1<107kDkZs-xTCYbqEK?5%x$#rd=JGSBd2|@!kJr93A8@UIK3a1GI zDScawejQ3IQ@yq~rrznkn@_0i;vbk1_1U2EPc%HLrI&G&(fQ}`;C7k^s^^q{Ajv6Z z;XGtIf~1)HPY=oK2wo(Oa&l})g*UFWm?5rCilkjh#8;%1iRq{INaOY~E1Bs4l8!QQ zvkqFhD>~d^t@mBy@kR><(??(WB*Cz5loN3eRsCzb-gWPec7?KEi=$=M8U9y43T|>D z)SRs&bKiy($`Etld`g za{5_U{3zn)QPsD58@wKVW(7023gmCAXjFcu7(ZVOh>LQkkeZVLtLKo^Ld~&b)>9_5 zgoN!KQx)c_hf0h4##o^T6>mDL7-M5&GhNq%G?pE;D^$BESd5Jhcl`JG(@{G^{&kE{ z(#Dtt&&;}E`53L{nEJM~&26Pqz%^(Z_+?2kqF3Sf_z^)@2RY`aDdSzj+)`AtenEyn z=Z28wd`XwY2Pl{8g>Y$=U@L_Q@={66pua~-Bde&XIzCb3GRh|)vBWm zQRGtOmva(+SaGp{*CbaH`KtsYBH>nVTp4}AV=10'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-create-regexp-features-plugin@7.28.5': + resolution: {integrity: sha512-N1EhvLtHzOvj7QQOUCCS3NrPJP8c5W6ZXCHDn7Yialuy1iu4r5EmIYkXlKNqT99Ciw+W0mDqWoR6HWMZlFP3hw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-define-polyfill-provider@0.6.7': + resolution: {integrity: sha512-6Fqi8MtQ/PweQ9xvux65emkLQ83uB+qAVtfHkC9UodyHMIZdxNI01HjLCLUtybElp2KY2XNE0nOgyP1E1vXw9w==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-remap-async-to-generator@7.27.1': + resolution: {integrity: sha512-7fiA521aVw8lSPeI4ZOD3vRFkoqkJcS+z4hFo82bFSH/2tNd6eJ5qCVMS5OzDmZh/kaHQeBaeyxK6wljcPtveA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-wrap-function@7.28.6': + resolution: {integrity: sha512-z+PwLziMNBeSQJonizz2AGnndLsP2DeGHIxDAn+wdHOGuo4Fo1x1HBPPXeE9TAOPHNNWQKCSlA2VZyYyyibDnQ==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5': + resolution: {integrity: sha512-87GDMS3tsmMSi/3bWOte1UblL+YUTFMV8SZPZ2eSEL17s74Cw/l63rR6NmGVKMYW2GYi85nE+/d6Hw5N0bEk2Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1': + resolution: {integrity: sha512-qNeq3bCKnGgLkEXUuFry6dPlGfCdQNZbn7yUAPCInwAJHMU7THJfrBSozkcWq5sNM6RcF3S8XyQL2A52KNR9IA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1': + resolution: {integrity: sha512-g4L7OYun04N1WyqMNjldFwlfPCLVkgB54A/YCXICZYBsvJJE3kByKv9c9+R/nAfmIfjl2rKYLNyMHboYbZaWaA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1': + resolution: {integrity: sha512-oO02gcONcD5O1iTLi/6frMJBIwWEHceWGSGqrpCmEL8nogiS6J9PBlE48CaK20/Jx1LuRml9aDftLgdjXT8+Cw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.13.0 + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6': + resolution: {integrity: sha512-a0aBScVTlNaiUe35UtfxAN7A/tehvvG4/ByO6+46VPKTRSlfnAFsgKy0FUh+qAkQrDTmhDkT+IBOKlOoMUxQ0g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2': + resolution: {integrity: sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-async-generators@7.8.4': + resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-bigint@7.8.3': + resolution: {integrity: sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-properties@7.12.13': + resolution: {integrity: sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-class-static-block@7.14.5': + resolution: {integrity: sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-assertions@7.28.6': + resolution: {integrity: sha512-pSJUpFHdx9z5nqTSirOCMtYVP2wFgoWhP0p3g8ONK/4IHhLIBd0B9NYqAvIUAhq+OkhO4VM1tENCt0cjlsNShw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-import-meta@7.10.4': + resolution: {integrity: sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-json-strings@7.8.3': + resolution: {integrity: sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4': + resolution: {integrity: sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3': + resolution: {integrity: sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-numeric-separator@7.10.4': + resolution: {integrity: sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-object-rest-spread@7.8.3': + resolution: {integrity: sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3': + resolution: {integrity: sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-optional-chaining@7.8.3': + resolution: {integrity: sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-private-property-in-object@7.14.5': + resolution: {integrity: sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-top-level-await@7.14.5': + resolution: {integrity: sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6': + resolution: {integrity: sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-arrow-functions@7.27.1': + resolution: {integrity: sha512-8Z4TGic6xW70FKThA5HYEKKyBpOOsucTOD1DjU3fZxDg+K3zBJcXMFnt/4yQiZnf5+MiOMSXQ9PaEK/Ilh1DeA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-generator-functions@7.29.0': + resolution: {integrity: sha512-va0VdWro4zlBr2JsXC+ofCPB2iG12wPtVGTWFx2WLDOM3nYQZZIGP82qku2eW/JR83sD+k2k+CsNtyEbUqhU6w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-async-to-generator@7.28.6': + resolution: {integrity: sha512-ilTRcmbuXjsMmcZ3HASTe4caH5Tpo93PkTxF9oG2VZsSWsahydmcEHhix9Ik122RcTnZnUzPbmux4wh1swfv7g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoped-functions@7.27.1': + resolution: {integrity: sha512-cnqkuOtZLapWYZUYM5rVIdv1nXYuFVIltZ6ZJ7nIj585QsjKM5dhL2Fu/lICXZ1OyIAFc7Qy+bvDAtTXqGrlhg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-block-scoping@7.28.6': + resolution: {integrity: sha512-tt/7wOtBmwHPNMPu7ax4pdPz6shjFrmHDghvNC+FG9Qvj7D6mJcoRQIF5dy4njmxR941l6rgtvfSB2zX3VlUIw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-properties@7.28.6': + resolution: {integrity: sha512-dY2wS3I2G7D697VHndN91TJr8/AAfXQNt5ynCTI/MpxMsSzHp+52uNivYT5wCPax3whc47DR8Ba7cmlQMg24bw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-class-static-block@7.28.6': + resolution: {integrity: sha512-rfQ++ghVwTWTqQ7w8qyDxL1XGihjBss4CmTgGRCTAC9RIbhVpyp4fOeZtta0Lbf+dTNIVJer6ych2ibHwkZqsQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.12.0 + + '@babel/plugin-transform-classes@7.28.6': + resolution: {integrity: sha512-EF5KONAqC5zAqT783iMGuM2ZtmEBy+mJMOKl2BCvPZ2lVrwvXnB6o+OBWCS+CoeCCpVRF2sA2RBKUxvT8tQT5Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-computed-properties@7.28.6': + resolution: {integrity: sha512-bcc3k0ijhHbc2lEfpFHgx7eYw9KNXqOerKWfzbxEHUGKnS3sz9C4CNL9OiFN1297bDNfUiSO7DaLzbvHQQQ1BQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-destructuring@7.28.5': + resolution: {integrity: sha512-Kl9Bc6D0zTUcFUvkNuQh4eGXPKKNDOJQXVyyM4ZAQPMveniJdxi8XMJwLo+xSoW3MIq81bD33lcUe9kZpl0MCw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-dotall-regex@7.28.6': + resolution: {integrity: sha512-SljjowuNKB7q5Oayv4FoPzeB74g3QgLt8IVJw9ADvWy3QnUb/01aw8I4AVv8wYnPvQz2GDDZ/g3GhcNyDBI4Bg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-keys@7.27.1': + resolution: {integrity: sha512-MTyJk98sHvSs+cvZ4nOauwTTG1JeonDjSGvGGUNHreGQns+Mpt6WX/dVzWBHgg+dYZhkC4X+zTDfkTU+Vy9y7Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-zBPcW2lFGxdiD8PUnPwJjag2J9otbcLQzvbiOzDxpYXyCuYX9agOwMPGn1prVH0a4qzhCKu24rlH4c1f7yA8rw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-dynamic-import@7.27.1': + resolution: {integrity: sha512-MHzkWQcEmjzzVW9j2q8LGjwGWpG2mjwaaB0BNQwst3FIjqsg8Ct/mIZlvSPJvfi9y2AC8mi/ktxbFVL9pZ1I4A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-explicit-resource-management@7.28.6': + resolution: {integrity: sha512-Iao5Konzx2b6g7EPqTy40UZbcdXE126tTxVFr/nAIj+WItNxjKSYTEw3RC+A2/ZetmdJsgueL1KhaMCQHkLPIg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-exponentiation-operator@7.28.6': + resolution: {integrity: sha512-WitabqiGjV/vJ0aPOLSFfNY1u9U3R7W36B03r5I2KoNix+a3sOhJ3pKFB3R5It9/UiK78NiO0KE9P21cMhlPkw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-export-namespace-from@7.27.1': + resolution: {integrity: sha512-tQvHWSZ3/jH2xuq/vZDy0jNn+ZdXJeM8gHvX4lnJmsc3+50yPlWdZXIc5ay+umX+2/tJIqHqiEqcJvxlmIvRvQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-for-of@7.27.1': + resolution: {integrity: sha512-BfbWFFEJFQzLCQ5N8VocnCtA8J1CLkNTe2Ms2wocj75dd6VpiqS5Z5quTYcUoo4Yq+DN0rtikODccuv7RU81sw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-function-name@7.27.1': + resolution: {integrity: sha512-1bQeydJF9Nr1eBCMMbC+hdwmRlsv5XYOMu03YSWFwNs0HsAmtSxxF1fyuYPqemVldVyFmlCU7w8UE14LupUSZQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-json-strings@7.28.6': + resolution: {integrity: sha512-Nr+hEN+0geQkzhbdgQVPoqr47lZbm+5fCUmO70722xJZd0Mvb59+33QLImGj6F+DkK3xgDi1YVysP8whD6FQAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-literals@7.27.1': + resolution: {integrity: sha512-0HCFSepIpLTkLcsi86GG3mTUzxV5jpmbv97hTETW3yzrAij8aqlD36toB1D0daVFJM8NK6GvKO0gslVQmm+zZA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-logical-assignment-operators@7.28.6': + resolution: {integrity: sha512-+anKKair6gpi8VsM/95kmomGNMD0eLz1NQ8+Pfw5sAwWH9fGYXT50E55ZpV0pHUHWf6IUTWPM+f/7AAff+wr9A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-member-expression-literals@7.27.1': + resolution: {integrity: sha512-hqoBX4dcZ1I33jCSWcXrP+1Ku7kdqXf1oeah7ooKOIiAdKQ+uqftgCFNOSzA5AMS2XIHEYeGFg4cKRCdpxzVOQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-amd@7.27.1': + resolution: {integrity: sha512-iCsytMg/N9/oFq6n+gFTvUYDZQOMK5kEdeYxmxt91fcJGycfxVP9CnrxoliM0oumFERba2i8ZtwRUCMhvP1LnA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-systemjs@7.29.0': + resolution: {integrity: sha512-PrujnVFbOdUpw4UHiVwKvKRLMMic8+eC0CuNlxjsyZUiBjhFdPsewdXCkveh2KqBA9/waD0W1b4hXSOBQJezpQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-umd@7.27.1': + resolution: {integrity: sha512-iQBE/xC5BV1OxJbp6WG7jq9IWiD+xxlZhLrdwpPkTX3ydmXdvoCpyfJN7acaIBZaOqTfr76pgzqBJflNbeRK+w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0': + resolution: {integrity: sha512-1CZQA5KNAD6ZYQLPw7oi5ewtDNxH/2vuCh+6SmvgDfhumForvs8a1o9n0UrEoBD8HU4djO2yWngTQlXl1NDVEQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-new-target@7.27.1': + resolution: {integrity: sha512-f6PiYeqXQ05lYq3TIfIDu/MtliKUbNwkGApPUvyo6+tc7uaR4cPjPe7DFPr15Uyycg2lZU6btZ575CuQoYh7MQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6': + resolution: {integrity: sha512-3wKbRgmzYbw24mDJXT7N+ADXw8BC/imU9yo9c9X9NKaLF1fW+e5H1U5QjMUBe4Qo4Ox/o++IyUkl1sVCLgevKg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-numeric-separator@7.28.6': + resolution: {integrity: sha512-SJR8hPynj8outz+SlStQSwvziMN4+Bq99it4tMIf5/Caq+3iOc0JtKyse8puvyXkk3eFRIA5ID/XfunGgO5i6w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-rest-spread@7.28.6': + resolution: {integrity: sha512-5rh+JR4JBC4pGkXLAcYdLHZjXudVxWMXbB6u6+E9lRL5TrGVbHt1TjxGbZ8CkmYw9zjkB7jutzOROArsqtncEA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-object-super@7.27.1': + resolution: {integrity: sha512-SFy8S9plRPbIcxlJ8A6mT/CxFdJx/c04JEctz4jf8YZaVS2px34j7NXRrlGlHkN/M2gnpL37ZpGRGVFLd3l8Ng==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-catch-binding@7.28.6': + resolution: {integrity: sha512-R8ja/Pyrv0OGAvAXQhSTmWyPJPml+0TMqXlO5w+AsMEiwb2fg3WkOvob7UxFSL3OIttFSGSRFKQsOhJ/X6HQdQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-optional-chaining@7.28.6': + resolution: {integrity: sha512-A4zobikRGJTsX9uqVFdafzGkqD30t26ck2LmOzAuLL8b2x6k3TIqRiT2xVvA9fNmFeTX484VpsdgmKNA0bS23w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-parameters@7.27.7': + resolution: {integrity: sha512-qBkYTYCb76RRxUM6CcZA5KRu8K4SM8ajzVeUgVdMVO9NN9uI/GaVmBg/WKJJGnNokV9SY8FxNOVWGXzqzUidBg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-methods@7.28.6': + resolution: {integrity: sha512-piiuapX9CRv7+0st8lmuUlRSmX6mBcVeNQ1b4AYzJxfCMuBfB0vBXDiGSmm03pKJw1v6cZ8KSeM+oUnM6yAExg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-private-property-in-object@7.28.6': + resolution: {integrity: sha512-b97jvNSOb5+ehyQmBpmhOCiUC5oVK4PMnpRvO7+ymFBoqYjeDHIU9jnrNUuwHOiL9RpGDoKBpSViarV+BU+eVA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-property-literals@7.27.1': + resolution: {integrity: sha512-oThy3BCuCha8kDZ8ZkgOg2exvPYUlprMukKQXI1r1pJ47NCvxfkEy8vK+r/hT9nF0Aa4H1WUPZZjHTFtAhGfmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-display-name@7.28.0': + resolution: {integrity: sha512-D6Eujc2zMxKjfa4Zxl4GHMsmhKKZ9VpcqIchJLvwTxad9zWIYulwYItBovpDOoNLISpcZSXoDJ5gaGbQUDqViA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-development@7.27.1': + resolution: {integrity: sha512-ykDdF5yI4f1WrAolLqeF3hmYU12j9ntLQl/AOG1HAS21jxyg1Q0/J/tpREuYLfatGdGmXp/3yS0ZA76kOlVq9Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx@7.28.6': + resolution: {integrity: sha512-61bxqhiRfAACulXSLd/GxqmAedUSrRZIu/cbaT18T1CetkTmtDN15it7i80ru4DVqRK1WMxQhXs+Lf9kajm5Ow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-pure-annotations@7.27.1': + resolution: {integrity: sha512-JfuinvDOsD9FVMTHpzA/pBLisxpv1aSf+OIV8lgH3MuWrks19R27e6a6DipIg4aX1Zm9Wpb04p8wljfKrVSnPA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regenerator@7.29.0': + resolution: {integrity: sha512-FijqlqMA7DmRdg/aINBSs04y8XNTYw/lr1gJ2WsmBnnaNw1iS43EPkJW+zK7z65auG3AWRFXWj+NcTQwYptUog==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-regexp-modifiers@7.28.6': + resolution: {integrity: sha512-QGWAepm9qxpaIs7UM9FvUSnCGlb8Ua1RhyM4/veAxLwt3gMat/LSGrZixyuj4I6+Kn9iwvqCyPTtbdxanYoWYg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/plugin-transform-reserved-words@7.27.1': + resolution: {integrity: sha512-V2ABPHIJX4kC7HegLkYoDpfg9PVmuWy/i6vUM5eGK22bx4YVFD3M5F0QQnWQoDs6AGsUWTVOopBiMFQgHaSkVw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-shorthand-properties@7.27.1': + resolution: {integrity: sha512-N/wH1vcn4oYawbJ13Y/FxcQrWk63jhfNa7jef0ih7PHSIHX2LB7GWE1rkPrOnka9kwMxb6hMl19p7lidA+EHmQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-spread@7.28.6': + resolution: {integrity: sha512-9U4QObUC0FtJl05AsUcodau/RWDytrU6uKgkxu09mLR9HLDAtUMoPuuskm5huQsoktmsYpI+bGmq+iapDcriKA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-sticky-regex@7.27.1': + resolution: {integrity: sha512-lhInBO5bi/Kowe2/aLdBAawijx+q1pQzicSgnkB6dUPc1+RC8QmJHKf2OjvU+NZWitguJHEaEmbV6VWEouT58g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-template-literals@7.27.1': + resolution: {integrity: sha512-fBJKiV7F2DxZUkg5EtHKXQdbsbURW3DZKQUWphDum0uRP6eHGGa/He9mc0mypL680pb+e/lDIthRohlv8NCHkg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typeof-symbol@7.27.1': + resolution: {integrity: sha512-RiSILC+nRJM7FY5srIyc4/fGIwUhyDuuBSdWn4y6yT6gm652DpCHZjIipgn6B7MQ1ITOUnAKWixEUjQRIBIcLw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-escapes@7.27.1': + resolution: {integrity: sha512-Ysg4v6AmF26k9vpfFuTZg8HRfVWzsh1kVfowA23y9j/Gu6dOuahdUVhkLqpObp3JIv27MLSii6noRnuKN8H0Mg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-property-regex@7.28.6': + resolution: {integrity: sha512-4Wlbdl/sIZjzi/8St0evF0gEZrgOswVO6aOzqxh1kDZOl9WmLrHq2HtGhnOJZmHZYKP8WZ1MDLCt5DAWwRo57A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-regex@7.27.1': + resolution: {integrity: sha512-xvINq24TRojDuyt6JGtHmkVkrfVV3FPT16uytxImLeBZqW3/H52yN+kM1MGuyPkIQxrzKwPHs5U/MP3qKyzkGw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6': + resolution: {integrity: sha512-/wHc/paTUmsDYN7SZkpWxogTOBNnlx7nBQYfy6JJlCT7G3mVhltk3e++N7zV0XfgGsrqBxd4rJQt9H16I21Y1Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/preset-env@7.29.0': + resolution: {integrity: sha512-fNEdfc0yi16lt6IZo2Qxk3knHVdfMYX33czNb4v8yWhemoBhibCpQK/uYHtSKIiO+p/zd3+8fYVXhQdOVV608w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-modules@0.1.6-no-external-plugins': + resolution: {integrity: sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA==} + peerDependencies: + '@babel/core': ^7.0.0-0 || ^8.0.0-0 <8.0.0 + + '@babel/preset-react@7.28.5': + resolution: {integrity: sha512-Z3J8vhRq7CeLjdC58jLv4lnZ5RKFUJWqH5emvxmv9Hv3BD1T9R/Im713R4MTKwvFaV74ejZ3sM01LyEKk4ugNQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.28.5': + resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@bcoe/v8-coverage@0.2.3': + resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} + + '@discoveryjs/json-ext@0.5.7': + resolution: {integrity: sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==} + engines: {node: '>=10.0.0'} + + '@emotion/is-prop-valid@1.4.0': + resolution: {integrity: sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==} + + '@emotion/memoize@0.9.0': + resolution: {integrity: sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==} + + '@istanbuljs/load-nyc-config@1.1.0': + resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} + engines: {node: '>=8'} + + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} + engines: {node: '>=8'} + + '@jest/console@29.7.0': + resolution: {integrity: sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/core@29.7.0': + resolution: {integrity: sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/environment@29.7.0': + resolution: {integrity: sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect-utils@29.7.0': + resolution: {integrity: sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/expect@29.7.0': + resolution: {integrity: sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/fake-timers@29.7.0': + resolution: {integrity: sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/globals@29.7.0': + resolution: {integrity: sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/reporters@29.7.0': + resolution: {integrity: sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + '@jest/schemas@29.6.3': + resolution: {integrity: sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/source-map@29.6.3': + resolution: {integrity: sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-result@29.7.0': + resolution: {integrity: sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/test-sequencer@29.7.0': + resolution: {integrity: sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/transform@29.7.0': + resolution: {integrity: sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jest/types@29.6.3': + resolution: {integrity: sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/source-map@0.3.11': + resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@leichtgewicht/ip-codec@2.0.5': + resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@playwright/test@1.60.0': + resolution: {integrity: sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==} + engines: {node: '>=18'} + hasBin: true + + '@remix-run/router@1.23.2': + resolution: {integrity: sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==} + engines: {node: '>=14.0.0'} + + '@sinclair/typebox@0.27.10': + resolution: {integrity: sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==} + + '@sinonjs/commons@3.0.1': + resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} + + '@sinonjs/fake-timers@10.3.0': + resolution: {integrity: sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==} + + '@supabase/auth-js@2.98.0': + resolution: {integrity: sha512-GBH361T0peHU91AQNzOlIrjUZw9TZbB9YDRiyFgk/3Kvr3/Z1NWUZ2athWTfHhwNNi8IrW00foyFxQD9IO/Trg==, tarball: https://registry.anpm.alibaba-inc.com/@supabase/auth-js/-/auth-js-2.98.0.tgz} + engines: {node: '>=20.0.0'} + + '@supabase/functions-js@2.98.0': + resolution: {integrity: sha512-N/xEyiNU5Org+d+PNCpv+TWniAXRzxIURxDYsS/m2I/sfAB/HcM9aM2Dmf5edj5oWb9GxID1OBaZ8NMmPXL+Lg==, tarball: https://registry.anpm.alibaba-inc.com/@supabase/functions-js/-/functions-js-2.98.0.tgz} + engines: {node: '>=20.0.0'} + + '@supabase/postgrest-js@2.98.0': + resolution: {integrity: sha512-v6e9WeZuJijzUut8HyXu6gMqWFepIbaeaMIm1uKzei4yLg9bC9OtEW9O14LE/9ezqNbSAnSLO5GtOLFdm7Bpkg==, tarball: https://registry.anpm.alibaba-inc.com/@supabase/postgrest-js/-/postgrest-js-2.98.0.tgz} + engines: {node: '>=20.0.0'} + + '@supabase/realtime-js@2.98.0': + resolution: {integrity: sha512-rOWt28uGyFipWOSd+n0WVMr9kUXiWaa7J4hvyLCIHjRFqWm1z9CaaKAoYyfYMC1Exn3WT8WePCgiVhlAtWC2yw==, tarball: https://registry.anpm.alibaba-inc.com/@supabase/realtime-js/-/realtime-js-2.98.0.tgz} + engines: {node: '>=20.0.0'} + + '@supabase/storage-js@2.98.0': + resolution: {integrity: sha512-tzr2mG+v7ILSAZSfZMSL9OPyIH4z1ikgQ8EcQTKfMRz4EwmlFt3UnJaGzSOxyvF5b+fc9So7qdSUWTqGgeLokQ==, tarball: https://registry.anpm.alibaba-inc.com/@supabase/storage-js/-/storage-js-2.98.0.tgz} + engines: {node: '>=20.0.0'} + + '@supabase/supabase-js@2.98.0': + resolution: {integrity: sha512-Ohc97CtInLwZyiSASz7tT9/Abm/vqnIbO9REp+PivVUII8UZsuI3bngRQnYgJdFoOIwvaEII1fX1qy8x0CyNiw==, tarball: https://registry.anpm.alibaba-inc.com/@supabase/supabase-js/-/supabase-js-2.98.0.tgz} + engines: {node: '>=20.0.0'} + + '@testing-library/dom@9.3.4': + resolution: {integrity: sha512-FlS4ZWlp97iiNWig0Muq8p+3rVDjRiYE+YKGbAqXOu9nwJFFOdL00kFpz42M+4huzYi86vAK1sOOfyOG45muIQ==} + engines: {node: '>=14'} + + '@testing-library/jest-dom@6.9.1': + resolution: {integrity: sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==} + engines: {node: '>=14', npm: '>=6', yarn: '>=1'} + + '@testing-library/react-hooks@8.0.1': + resolution: {integrity: sha512-Aqhl2IVmLt8IovEVarNDFuJDVWVvhnr9/GCU6UUnrYXwgDFF9h2L2o2P9KBni1AST5sT6riAyoukFLyjQUgD/g==} + engines: {node: '>=12'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 + react: ^16.9.0 || ^17.0.0 + react-dom: ^16.9.0 || ^17.0.0 + react-test-renderer: ^16.9.0 || ^17.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + react-dom: + optional: true + react-test-renderer: + optional: true + + '@testing-library/react@14.3.1': + resolution: {integrity: sha512-H99XjUhWQw0lTgyMN05W3xQG1Nh4lq574D8keFf1dDoNTJgp66VbJozRaczoF+wsiaPJNt/TcnfpLGufGxSrZQ==} + engines: {node: '>=14'} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + + '@tootallnate/once@2.0.1': + resolution: {integrity: sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==} + engines: {node: '>= 10'} + + '@types/aria-query@5.0.4': + resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/body-parser@1.19.6': + resolution: {integrity: sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==} + + '@types/bonjour@3.5.13': + resolution: {integrity: sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ==} + + '@types/connect-history-api-fallback@1.5.4': + resolution: {integrity: sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw==} + + '@types/connect@3.4.38': + resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + + '@types/d3-array@3.2.2': + resolution: {integrity: sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==} + + '@types/d3-color@3.1.3': + resolution: {integrity: sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==} + + '@types/d3-ease@3.0.2': + resolution: {integrity: sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==} + + '@types/d3-interpolate@3.0.4': + resolution: {integrity: sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==} + + '@types/d3-path@3.1.1': + resolution: {integrity: sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==} + + '@types/d3-scale@4.0.9': + resolution: {integrity: sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==} + + '@types/d3-shape@3.1.8': + resolution: {integrity: sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==} + + '@types/d3-time@3.0.4': + resolution: {integrity: sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==} + + '@types/d3-timer@3.0.2': + resolution: {integrity: sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==} + + '@types/eslint-scope@3.7.7': + resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} + + '@types/eslint@9.6.1': + resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/express-serve-static-core@4.19.8': + resolution: {integrity: sha512-02S5fmqeoKzVZCHPZid4b8JH2eM5HzQLZWN2FohQEy/0eXTq8VXZfSN6Pcr3F6N9R/vNrj7cpgbhjie6m/1tCA==} + + '@types/express-serve-static-core@5.1.1': + resolution: {integrity: sha512-v4zIMr/cX7/d2BpAEX3KNKL/JrT1s43s96lLvvdTmza1oEvDudCqK9aF/djc/SWgy8Yh0h30TZx5VpzqFCxk5A==} + + '@types/express@4.17.25': + resolution: {integrity: sha512-dVd04UKsfpINUnK0yBoYHDF3xu7xVH4BuDotC/xGuycx4CgbP48X/KF/586bcObxT0HENHXEU8Nqtu6NR+eKhw==} + + '@types/graceful-fs@4.1.9': + resolution: {integrity: sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==} + + '@types/html-minifier-terser@6.1.0': + resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} + + '@types/http-errors@2.0.5': + resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} + + '@types/http-proxy@1.17.17': + resolution: {integrity: sha512-ED6LB+Z1AVylNTu7hdzuBqOgMnvG/ld6wGCG8wFnAzKX5uyW2K3WD52v0gnLCTK/VLpXtKckgWuyScYK6cSPaw==} + + '@types/istanbul-lib-coverage@2.0.6': + resolution: {integrity: sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==} + + '@types/istanbul-lib-report@3.0.3': + resolution: {integrity: sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==} + + '@types/istanbul-reports@3.0.4': + resolution: {integrity: sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==} + + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} + + '@types/jsdom@20.0.1': + resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/mime@1.3.5': + resolution: {integrity: sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==} + + '@types/node-forge@1.3.14': + resolution: {integrity: sha512-mhVF2BnD4BO+jtOp7z1CdzaK4mbuK0LLQYAvdOLqHTavxFNq4zA1EmYkpnFjP8HOUzedfQkRnp0E2ulSAYSzAw==} + + '@types/node@25.3.5': + resolution: {integrity: sha512-oX8xrhvpiyRCQkG1MFchB09f+cXftgIXb3a7UUa4Y3wpmZPw5tyZGTLWhlESOLq1Rq6oDlc8npVU2/9xiCuXMA==} + + '@types/phoenix@1.6.7': + resolution: {integrity: sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==, tarball: https://registry.anpm.alibaba-inc.com/@types/phoenix/-/phoenix-1.6.7.tgz} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/qs@6.15.0': + resolution: {integrity: sha512-JawvT8iBVWpzTrz3EGw9BTQFg3BQNmwERdKE22vlTxawwtbyUSlMppvZYKLZzB5zgACXdXxbD3m1bXaMqP/9ow==} + + '@types/range-parser@1.2.7': + resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.28': + resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + + '@types/retry@0.12.0': + resolution: {integrity: sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==} + + '@types/send@0.17.6': + resolution: {integrity: sha512-Uqt8rPBE8SY0RK8JB1EzVOIZ32uqy8HwdxCnoCOsYrvnswqmFZ/k+9Ikidlk/ImhsdvBsloHbAlewb2IEBV/Og==} + + '@types/send@1.2.1': + resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} + + '@types/serve-index@1.9.4': + resolution: {integrity: sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug==} + + '@types/serve-static@1.15.10': + resolution: {integrity: sha512-tRs1dB+g8Itk72rlSI2ZrW6vZg0YrLI81iQSTkMmOqnqCaNr/8Ek4VwWcN5vZgCYWbg/JJSGBlUaYGAOP73qBw==} + + '@types/sockjs@0.3.36': + resolution: {integrity: sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q==} + + '@types/stack-utils@2.0.3': + resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} + + '@types/tough-cookie@4.0.5': + resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==} + + '@types/ws@8.18.1': + resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} + + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + + '@webassemblyjs/ast@1.14.1': + resolution: {integrity: sha512-nuBEDgQfm1ccRp/8bCQrx1frohyufl4JlbMMZ4P1wpeOfDhF6FQkxZJ1b/e+PLwr6X1Nhw6OLme5usuBWYBvuQ==} + + '@webassemblyjs/floating-point-hex-parser@1.13.2': + resolution: {integrity: sha512-6oXyTOzbKxGH4steLbLNOu71Oj+C8Lg34n6CqRvqfS2O71BxY6ByfMDRhBytzknj9yGUPVJ1qIKhRlAwO1AovA==} + + '@webassemblyjs/helper-api-error@1.13.2': + resolution: {integrity: sha512-U56GMYxy4ZQCbDZd6JuvvNV/WFildOjsaWD3Tzzvmw/mas3cXzRJPMjP83JqEsgSbyrmaGjBfDtV7KDXV9UzFQ==} + + '@webassemblyjs/helper-buffer@1.14.1': + resolution: {integrity: sha512-jyH7wtcHiKssDtFPRB+iQdxlDf96m0E39yb0k5uJVhFGleZFoNw1c4aeIcVUPPbXUVJ94wwnMOAqUHyzoEPVMA==} + + '@webassemblyjs/helper-numbers@1.13.2': + resolution: {integrity: sha512-FE8aCmS5Q6eQYcV3gI35O4J789wlQA+7JrqTTpJqn5emA4U2hvwJmvFRC0HODS+3Ye6WioDklgd6scJ3+PLnEA==} + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': + resolution: {integrity: sha512-3QbLKy93F0EAIXLh0ogEVR6rOubA9AoZ+WRYhNbFyuB70j3dRdwH9g+qXhLAO0kiYGlg3TxDV+I4rQTr/YNXkA==} + + '@webassemblyjs/helper-wasm-section@1.14.1': + resolution: {integrity: sha512-ds5mXEqTJ6oxRoqjhWDU83OgzAYjwsCV8Lo/N+oRsNDmx/ZDpqalmrtgOMkHwxsG0iI//3BwWAErYRHtgn0dZw==} + + '@webassemblyjs/ieee754@1.13.2': + resolution: {integrity: sha512-4LtOzh58S/5lX4ITKxnAK2USuNEvpdVV9AlgGQb8rJDHaLeHciwG4zlGr0j/SNWlr7x3vO1lDEsuePvtcDNCkw==} + + '@webassemblyjs/leb128@1.13.2': + resolution: {integrity: sha512-Lde1oNoIdzVzdkNEAWZ1dZ5orIbff80YPdHx20mrHwHrVNNTjNr8E3xz9BdpcGqRQbAEa+fkrCb+fRFTl/6sQw==} + + '@webassemblyjs/utf8@1.13.2': + resolution: {integrity: sha512-3NQWGjKTASY1xV5m7Hr0iPeXD9+RDobLll3T9d2AO+g3my8xy5peVyjSag4I50mR1bBSN/Ct12lo+R9tJk0NZQ==} + + '@webassemblyjs/wasm-edit@1.14.1': + resolution: {integrity: sha512-RNJUIQH/J8iA/1NzlE4N7KtyZNHi3w7at7hDjvRNm5rcUXa00z1vRz3glZoULfJ5mpvYhLybmVcwcjGrC1pRrQ==} + + '@webassemblyjs/wasm-gen@1.14.1': + resolution: {integrity: sha512-AmomSIjP8ZbfGQhumkNvgC33AY7qtMCXnN6bL2u2Js4gVCg8fp735aEiMSBbDR7UQIj90n4wKAFUSEd0QN2Ukg==} + + '@webassemblyjs/wasm-opt@1.14.1': + resolution: {integrity: sha512-PTcKLUNvBqnY2U6E5bdOQcSM+oVP/PmrDY9NzowJjislEjwP/C4an2303MCVS2Mg9d3AJpIGdUFIQQWbPds0Sw==} + + '@webassemblyjs/wasm-parser@1.14.1': + resolution: {integrity: sha512-JLBl+KZ0R5qB7mCnud/yyX08jWFw5MsoalJ1pQ4EdFlgj9VdXKGuENGsiCIjegI1W7p91rUlcB/LB5yRJKNTcQ==} + + '@webassemblyjs/wast-printer@1.14.1': + resolution: {integrity: sha512-kPSSXE6De1XOR820C90RIo2ogvZG+c3KiHzqUoO/F34Y2shGzesfqv7o57xrxovZJH/MetF5UjroJ/R/3isoiw==} + + '@webpack-cli/configtest@2.1.1': + resolution: {integrity: sha512-wy0mglZpDSiSS0XHrVR+BAdId2+yxPSoJW8fsna3ZpYSlufjvxnP4YbKTCBZnNIcGN4r6ZPXV55X4mYExOfLmw==} + engines: {node: '>=14.15.0'} + peerDependencies: + webpack: 5.x.x + webpack-cli: 5.x.x + + '@webpack-cli/info@2.0.2': + resolution: {integrity: sha512-zLHQdI/Qs1UyT5UBdWNqsARasIA+AaF8t+4u2aS2nEpBQh2mWIVb8qAklq0eUENnC5mOItrIB4LiS9xMtph18A==} + engines: {node: '>=14.15.0'} + peerDependencies: + webpack: 5.x.x + webpack-cli: 5.x.x + + '@webpack-cli/serve@2.0.5': + resolution: {integrity: sha512-lqaoKnRYBdo1UgDX8uF24AfGMifWK19TxPmM5FHc2vAGxrJ/qtyUyFBWoY1tISZdelsQ5fBcOusifo5o5wSJxQ==} + engines: {node: '>=14.15.0'} + peerDependencies: + webpack: 5.x.x + webpack-cli: 5.x.x + webpack-dev-server: '*' + peerDependenciesMeta: + webpack-dev-server: + optional: true + + '@xtuc/ieee754@1.2.0': + resolution: {integrity: sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==} + + '@xtuc/long@4.2.2': + resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} + + abab@2.0.6: + resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + deprecated: Use your platform's native atob() and btoa() methods instead + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn-globals@7.0.1: + resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} + + acorn-import-phases@1.0.4: + resolution: {integrity: sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==} + engines: {node: '>=10.13.0'} + peerDependencies: + acorn: ^8.14.0 + + acorn-walk@8.3.5: + resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==} + engines: {node: '>=0.4.0'} + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + ajv-formats@2.1.1: + resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} + peerDependencies: + ajv: ^8.0.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv-keywords@5.1.0: + resolution: {integrity: sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==} + peerDependencies: + ajv: ^8.8.2 + + ajv@8.18.0: + resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==} + + ansi-escapes@4.3.2: + resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} + engines: {node: '>=8'} + + ansi-html-community@0.0.8: + resolution: {integrity: sha512-1APHAyr3+PCamwNw3bXCPp4HFLONZt/yIH0sZp0/469KWNTEy+qN5jQ3GVX6DMZ1UXAi34yVwtTeaG/HpBuuzw==} + engines: {'0': node >= 0.8.0} + hasBin: true + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@5.2.0: + resolution: {integrity: sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==} + engines: {node: '>=10'} + + any-promise@1.3.0: + resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@1.0.10: + resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.1.3: + resolution: {integrity: sha512-R5iJ5lkuHybztUfuOAznmboyjWq8O6sqNqtK7CLOqdydi54VNbORp49mb14KbWgG1QD3JFO9hJdZ+y4KutfdOQ==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + autoprefixer@10.4.27: + resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + babel-jest@29.7.0: + resolution: {integrity: sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.8.0 + + babel-loader@9.2.1: + resolution: {integrity: sha512-fqe8naHt46e0yIdkjUZYqddSXfej3AHajX+CSO5X7oy0EmPc6o5Xh+RClNoHjnieWz9AW4kZxW9yyFMhVB1QLA==} + engines: {node: '>= 14.15.0'} + peerDependencies: + '@babel/core': ^7.12.0 + webpack: '>=5' + + babel-plugin-istanbul@6.1.1: + resolution: {integrity: sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==} + engines: {node: '>=8'} + + babel-plugin-jest-hoist@29.6.3: + resolution: {integrity: sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + babel-plugin-polyfill-corejs2@0.4.16: + resolution: {integrity: sha512-xaVwwSfebXf0ooE11BJovZYKhFjIvQo7TsyVpETuIeH2JHv0k/T6Y5j22pPTvqYqmpkxdlPAJlyJ0tfOJAoMxw==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-corejs3@0.14.1: + resolution: {integrity: sha512-ENp89vM9Pw4kv/koBb5N2f9bDZsR0hpf3BdPMOg/pkS3pwO4dzNnQZVXtBbeyAadgm865DmQG2jMMLqmZXvuCw==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-plugin-polyfill-regenerator@0.6.7: + resolution: {integrity: sha512-OTYbUlSwXhNgr4g6efMZgsO8//jA61P7ZbRX3iTT53VON8l+WQS8IAUEVo4a4cWknrg2W8Cj4gQhRYNCJ8GkAA==} + peerDependencies: + '@babel/core': ^7.4.0 || ^8.0.0-0 <8.0.0 + + babel-preset-current-node-syntax@1.2.0: + resolution: {integrity: sha512-E/VlAEzRrsLEb2+dv8yp3bo4scof3l9nR4lrld+Iy5NyVqgVYUJnDAmunkhPMisRI32Qc4iRiz425d8vM++2fg==} + peerDependencies: + '@babel/core': ^7.0.0 || ^8.0.0-0 + + babel-preset-jest@29.6.3: + resolution: {integrity: sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@babel/core': ^7.0.0 + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-arraybuffer@1.0.2: + resolution: {integrity: sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==} + engines: {node: '>= 0.6.0'} + + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} + hasBin: true + + batch@0.6.1: + resolution: {integrity: sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + body-parser@1.20.4: + resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + bonjour-service@1.3.0: + resolution: {integrity: sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA==} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + bs-logger@0.2.6: + resolution: {integrity: sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==} + engines: {node: '>= 6'} + + bser@2.1.1: + resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.9: + resolution: {integrity: sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + camel-case@4.1.2: + resolution: {integrity: sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==} + + camelcase-css@2.0.1: + resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} + engines: {node: '>= 6'} + + camelcase@5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + + caniuse-lite@1.0.30001777: + resolution: {integrity: sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + char-regex@1.0.2: + resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} + engines: {node: '>=10'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chrome-trace-event@1.0.4: + resolution: {integrity: sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==} + engines: {node: '>=6.0'} + + ci-info@3.9.0: + resolution: {integrity: sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==} + engines: {node: '>=8'} + + cjs-module-lexer@1.4.3: + resolution: {integrity: sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==} + + clean-css@5.3.3: + resolution: {integrity: sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==} + engines: {node: '>= 10.0'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone-deep@4.0.1: + resolution: {integrity: sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==} + engines: {node: '>=6'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + co@4.6.0: + resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} + engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} + + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@10.0.1: + resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} + engines: {node: '>=14'} + + commander@2.20.3: + resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} + + commander@4.1.1: + resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} + engines: {node: '>= 6'} + + commander@8.3.0: + resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==} + engines: {node: '>= 12'} + + common-path-prefix@3.0.0: + resolution: {integrity: sha512-QE33hToZseCH3jS0qN96O/bSh3kaw/h+Tq7ngyY9eWDUnTlTNUyqfqvCXioLe5Na5jFsL78ra/wuBU4iuEgd4w==} + + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.8.1: + resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} + engines: {node: '>= 0.8.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + connect-history-api-fallback@2.0.0: + resolution: {integrity: sha512-U73+6lQFmfiNPrYbXqr6kZ1i1wiRqXnp2nhMsINseWXO8lDau0LGEffJ8kQi4EjLZympVgRdvqjAgiZ1tgzDDA==} + engines: {node: '>=0.8'} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + core-js-compat@3.48.0: + resolution: {integrity: sha512-OM4cAF3D6VtH/WkLtWvyNC56EZVXsZdU3iqaMG2B4WvYrlqU831pc4UtG5yp0sE9z8Y02wVN7PjW5Zf9Gt0f1Q==} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cosmiconfig@8.3.6: + resolution: {integrity: sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + create-jest@29.7.0: + resolution: {integrity: sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-loader@6.11.0: + resolution: {integrity: sha512-CTJ+AEQJjq5NzLga5pE39qdiSV56F8ywCIsqNIRF0r7BDgWsN25aazToqAFg7ZrtA/U016xudB3ffgweORxX7g==} + engines: {node: '>= 12.13.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + webpack: ^5.0.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + + css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + css.escape@1.5.1: + resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + cssom@0.3.8: + resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} + + cssom@0.5.0: + resolution: {integrity: sha512-iKuQcq+NdHqlAcwUY0o/HL69XQrUaQdMjmStJ8JFmUaiiQErlhrmuigkg/CU4E2J0IyUKUrMAgl36TvN67MqTw==} + + cssstyle@2.3.0: + resolution: {integrity: sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==} + engines: {node: '>=8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + d3-array@3.2.4: + resolution: {integrity: sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==} + engines: {node: '>=12'} + + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-format@3.1.2: + resolution: {integrity: sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-path@3.1.0: + resolution: {integrity: sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==} + engines: {node: '>=12'} + + d3-scale@4.0.2: + resolution: {integrity: sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==} + engines: {node: '>=12'} + + d3-shape@3.2.0: + resolution: {integrity: sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==} + engines: {node: '>=12'} + + d3-time-format@4.1.0: + resolution: {integrity: sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==} + engines: {node: '>=12'} + + d3-time@3.1.0: + resolution: {integrity: sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + data-urls@3.0.2: + resolution: {integrity: sha512-Jy/tj3ldjZJo63sVAvg6LHt2mHvl4V6AgRAmNDtLdm7faqtsx+aJG42rsyCo9JCoRVKwPFzKlIPx3DIibwSIaQ==} + engines: {node: '>=12'} + + date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decimal.js-light@2.5.1: + resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==} + + decimal.js@10.6.0: + resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==} + + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-equal@2.2.3: + resolution: {integrity: sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==} + engines: {node: '>= 0.4'} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + default-gateway@6.0.3: + resolution: {integrity: sha512-fwSOJsbbNzZ/CUFpqFBqYfYNLj1NbMPm8MMCIzHjC83iSJRBEGmDUxU+WP661BaBQImeC2yHwXtz+P/O9o+XEg==} + engines: {node: '>= 10'} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-lazy-prop@2.0.0: + resolution: {integrity: sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==} + engines: {node: '>=8'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + depd@1.1.2: + resolution: {integrity: sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==} + engines: {node: '>= 0.6'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-newline@3.1.0: + resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} + engines: {node: '>=8'} + + detect-node@2.1.0: + resolution: {integrity: sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==} + + didyoumean@1.2.2: + resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + + diff-sequences@29.6.3: + resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dns-packet@5.6.1: + resolution: {integrity: sha512-l4gcSouhcgIKRvyy99RNVOgxXiicE+2jZoNmaNmZ6JXiGajBOJAesk1OBlJuM5k2c+eudGdLxDqXuPCKIj6kpw==} + engines: {node: '>=6'} + + dom-accessibility-api@0.5.16: + resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} + + dom-accessibility-api@0.6.3: + resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==} + + dom-converter@0.2.0: + resolution: {integrity: sha512-gd3ypIPfOMr9h5jIKq8E3sHOTCjeirnl0WK5ZdS1AW0Odt0b1PaWaHdJ4Qk4klv+YB9aJBS7mESXjFoDQPu6DA==} + + dom-helpers@5.2.1: + resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domexception@4.0.0: + resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} + engines: {node: '>=12'} + deprecated: Use your platform's native DOMException instead + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + dot-case@3.0.4: + resolution: {integrity: sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.307: + resolution: {integrity: sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==} + + emittery@0.13.1: + resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} + engines: {node: '>=12'} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + enhanced-resolve@5.20.0: + resolution: {integrity: sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==} + engines: {node: '>=10.13.0'} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + envinfo@7.21.0: + resolution: {integrity: sha512-Lw7I8Zp5YKHFCXL7+Dz95g4CcbMEpgvqZNNq3AmlT5XAV6CgAAk6gyAMqn2zjw08K9BHfcNuKrMiCPLByGafow==} + engines: {node: '>=4'} + hasBin: true + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-get-iterator@1.1.3: + resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} + + es-module-lexer@2.0.0: + resolution: {integrity: sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@2.0.0: + resolution: {integrity: sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==} + engines: {node: '>=8'} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + + eslint-scope@5.1.1: + resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==} + engines: {node: '>=8.0.0'} + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@4.3.0: + resolution: {integrity: sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit@0.1.2: + resolution: {integrity: sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==} + engines: {node: '>= 0.8.0'} + + expect@29.7.0: + resolution: {integrity: sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + express@4.22.1: + resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} + engines: {node: '>= 0.10.0'} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-equals@5.4.0: + resolution: {integrity: sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==} + engines: {node: '>=6.0.0'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastest-levenshtein@1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + faye-websocket@0.11.4: + resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} + engines: {node: '>=0.8.0'} + + fb-watchman@2.0.2: + resolution: {integrity: sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} + engines: {node: '>= 0.8'} + + find-cache-dir@4.0.0: + resolution: {integrity: sha512-9ZonPT4ZAK4a+1pUPVPZJapbi7O5qbbJPdYw/NOQWZZbVLdDTYM3A4R9z/DpAM08IDaFGsvPgiGZ82WEwUDWjg==} + engines: {node: '>=14.16'} + + find-up@4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + + find-up@6.3.0: + resolution: {integrity: sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + + follow-redirects@1.15.11: + resolution: {integrity: sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + framer-motion@11.18.2: + resolution: {integrity: sha512-5F5Och7wrvtLVElIpclDT0CBzMVg3dL22B64aZwHtsIY8RB4mXICLrkajK4G9R+ieSAGcgrLeae2SeUTg2pr6w==} + peerDependencies: + '@emotion/is-prop-valid': '*' + react: ^18.0.0 || ^19.0.0 + react-dom: ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@emotion/is-prop-valid': + optional: true + react: + optional: true + react-dom: + optional: true + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-monkey@1.1.0: + resolution: {integrity: sha512-QMUezzXWII9EV5aTFXW1UBVUO77wYPpjqIF8/AviUCThNeSYZykpoTixUeaNNBwmCev0AMDWMAni+f8Hxb1IFw==} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-package-type@0.1.0: + resolution: {integrity: sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==} + engines: {node: '>=8.0.0'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob-to-regexp@0.4.1: + resolution: {integrity: sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==} + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + handle-thing@2.0.1: + resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} + + handlebars@4.7.9: + resolution: {integrity: sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==} + engines: {node: '>=0.4.7'} + hasBin: true + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + he@1.2.0: + resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} + hasBin: true + + hpack.js@2.1.6: + resolution: {integrity: sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==} + + html-encoding-sniffer@3.0.0: + resolution: {integrity: sha512-oWv4T4yJ52iKrufjnyZPkrN0CH3QnrUqdB6In1g5Fe1mia8GmF36gnfNySxoZtxD5+NmYw1EElVXiBk93UeskA==} + engines: {node: '>=12'} + + html-entities@2.6.0: + resolution: {integrity: sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==} + + html-escaper@2.0.2: + resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} + + html-minifier-terser@6.1.0: + resolution: {integrity: sha512-YXxSlJBZTP7RS3tWnQw74ooKa6L9b9i9QYXY21eUEvhZ3u9XLfv6OnFsQq6RxkhHygsaUMvYsZRV5rU/OVNZxw==} + engines: {node: '>=12'} + hasBin: true + + html-webpack-plugin@5.6.6: + resolution: {integrity: sha512-bLjW01UTrvoWTJQL5LsMRo1SypHW80FTm12OJRSnr3v6YHNhfe+1r0MYUZJMACxnCHURVnBWRwAsWs2yPU9Ezw==} + engines: {node: '>=10.13.0'} + peerDependencies: + '@rspack/core': 0.x || 1.x + webpack: ^5.20.0 + peerDependenciesMeta: + '@rspack/core': + optional: true + webpack: + optional: true + + htmlparser2@6.1.0: + resolution: {integrity: sha512-gyyPk6rgonLFEDGoeRgQNaEUvdJ4ktTmmUh/h2t7s+M8oPpIPxgNACWa+6ESR57kXstwqPiCut0V8NRpcwgU7A==} + + http-deceiver@1.2.7: + resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} + + http-errors@1.8.1: + resolution: {integrity: sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==} + engines: {node: '>= 0.6'} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + http-parser-js@0.5.10: + resolution: {integrity: sha512-Pysuw9XpUq5dVc/2SMHpuTY01RFl8fttgcyunjL7eEMhGM3cI4eOmiCycJDVCo/7O7ClfQD3SaI6ftDzqOXYMA==} + + http-proxy-agent@5.0.0: + resolution: {integrity: sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==} + engines: {node: '>= 6'} + + http-proxy-middleware@2.0.9: + resolution: {integrity: sha512-c1IyJYLYppU574+YI7R4QyX2ystMtVXZwIdzazUIPIJsHuWNd+mho2j+bKoHftndicGj9yh+xjd+l0yj7VeT1Q==} + engines: {node: '>=12.0.0'} + peerDependencies: + '@types/express': ^4.17.13 + peerDependenciesMeta: + '@types/express': + optional: true + + http-proxy@1.18.1: + resolution: {integrity: sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==} + engines: {node: '>=8.0.0'} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iceberg-js@0.8.1: + resolution: {integrity: sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==, tarball: https://registry.anpm.alibaba-inc.com/iceberg-js/-/iceberg-js-0.8.1.tgz} + engines: {node: '>=20.0.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + + icss-utils@5.1.0: + resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + immer@10.2.0: + resolution: {integrity: sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-local@3.2.0: + resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} + engines: {node: '>=8'} + hasBin: true + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + internmap@2.0.3: + resolution: {integrity: sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==} + engines: {node: '>=12'} + + interpret@3.1.1: + resolution: {integrity: sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==} + engines: {node: '>=10.13.0'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + ipaddr.js@2.3.0: + resolution: {integrity: sha512-Zv/pA+ciVFbCSBBjGfaKUya/CcGmUHzTydLMaTwrUUEM2DIEO3iZvueGxmacvmN50fGpGVKeTXpb2LcYQxeVdg==} + engines: {node: '>= 10'} + + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-docker@2.2.1: + resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} + engines: {node: '>=8'} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-fn@2.1.0: + resolution: {integrity: sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==} + engines: {node: '>=6'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@3.0.0: + resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} + engines: {node: '>=10'} + + is-plain-object@2.0.4: + resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} + engines: {node: '>=0.10.0'} + + is-potential-custom-element-name@1.0.1: + resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + is-wsl@2.2.0: + resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} + engines: {node: '>=8'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + isobject@3.0.1: + resolution: {integrity: sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==} + engines: {node: '>=0.10.0'} + + istanbul-lib-coverage@3.2.2: + resolution: {integrity: sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@5.2.1: + resolution: {integrity: sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==} + engines: {node: '>=8'} + + istanbul-lib-instrument@6.0.3: + resolution: {integrity: sha512-Vtgk7L/R2JHyyGW07spoFlB8/lpjiOLTjMdms6AFMraYt3BaJauod/NGrfnVG/y4Ix1JEuMRPDPEj2ua+zz1/Q==} + engines: {node: '>=10'} + + istanbul-lib-report@3.0.1: + resolution: {integrity: sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==} + engines: {node: '>=10'} + + istanbul-lib-source-maps@4.0.1: + resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} + engines: {node: '>=10'} + + istanbul-reports@3.2.0: + resolution: {integrity: sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==} + engines: {node: '>=8'} + + jest-changed-files@29.7.0: + resolution: {integrity: sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-circus@29.7.0: + resolution: {integrity: sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-cli@29.7.0: + resolution: {integrity: sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jest-config@29.7.0: + resolution: {integrity: sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + '@types/node': '*' + ts-node: '>=9.0.0' + peerDependenciesMeta: + '@types/node': + optional: true + ts-node: + optional: true + + jest-diff@29.7.0: + resolution: {integrity: sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-docblock@29.7.0: + resolution: {integrity: sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-each@29.7.0: + resolution: {integrity: sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-environment-jsdom@29.7.0: + resolution: {integrity: sha512-k9iQbsf9OyOfdzWH8HDmrRT0gSIcX+FLNW7IQq94tFX0gynPwqDTW0Ho6iMVNjGz/nb+l/vW3dWM2bbLLpkbXA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + + jest-environment-node@29.7.0: + resolution: {integrity: sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-get-type@29.6.3: + resolution: {integrity: sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-haste-map@29.7.0: + resolution: {integrity: sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-leak-detector@29.7.0: + resolution: {integrity: sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-matcher-utils@29.7.0: + resolution: {integrity: sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-message-util@29.7.0: + resolution: {integrity: sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-mock@29.7.0: + resolution: {integrity: sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-pnp-resolver@1.2.3: + resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} + engines: {node: '>=6'} + peerDependencies: + jest-resolve: '*' + peerDependenciesMeta: + jest-resolve: + optional: true + + jest-regex-util@29.6.3: + resolution: {integrity: sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve-dependencies@29.7.0: + resolution: {integrity: sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-resolve@29.7.0: + resolution: {integrity: sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runner@29.7.0: + resolution: {integrity: sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-runtime@29.7.0: + resolution: {integrity: sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-snapshot@29.7.0: + resolution: {integrity: sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-util@29.7.0: + resolution: {integrity: sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-validate@29.7.0: + resolution: {integrity: sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-watcher@29.7.0: + resolution: {integrity: sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest-worker@27.5.1: + resolution: {integrity: sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==} + engines: {node: '>= 10.13.0'} + + jest-worker@29.7.0: + resolution: {integrity: sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + jest@29.7.0: + resolution: {integrity: sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + hasBin: true + peerDependencies: + node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 + peerDependenciesMeta: + node-notifier: + optional: true + + jiti@1.21.7: + resolution: {integrity: sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@3.14.2: + resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} + hasBin: true + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsdom@20.0.3: + resolution: {integrity: sha512-SYhBvTh89tTfCD/CRdSOm13mOBa42iTaTyfyEWBdKcGdPxPtLFBXuHR8XHb33YNYaP+lLbmSvBTsnoesCNJEsQ==} + engines: {node: '>=14'} + peerDependencies: + canvas: ^2.5.0 + peerDependenciesMeta: + canvas: + optional: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + kind-of@6.0.3: + resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} + engines: {node: '>=0.10.0'} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + launch-editor@2.13.1: + resolution: {integrity: sha512-lPSddlAAluRKJ7/cjRFoXUFzaX7q/YKI7yPHuEvSJVqoXvFnJov1/Ud87Aa4zULIbA9Nja4mSPK8l0z/7eV2wA==} + + leven@3.1.0: + resolution: {integrity: sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==} + engines: {node: '>=6'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + loader-runner@4.3.1: + resolution: {integrity: sha512-IWqP2SCPhyVFTBtRcgMHdzlf9ul25NwaFx4wCEH/KjAXuuHY4yNjvPXsBokp8jCB936PyWRaPKUNh8NvylLp2Q==} + engines: {node: '>=6.11.5'} + + locate-path@5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + + locate-path@7.2.0: + resolution: {integrity: sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lower-case@2.0.2: + resolution: {integrity: sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lucide-react@0.294.0: + resolution: {integrity: sha512-V7o0/VECSGbLHn3/1O67FUgBwWB+hmzshrgDVRJQhMh8uj5D3HBuIvhuAmQTtlupILSplwIZg5FTc4tTKMA2SA==, tarball: https://registry.anpm.alibaba-inc.com/lucide-react/-/lucide-react-0.294.0.tgz} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 + + lz-string@1.5.0: + resolution: {integrity: sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==} + hasBin: true + + make-dir@4.0.0: + resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} + engines: {node: '>=10'} + + make-error@1.3.6: + resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} + + makeerror@1.0.12: + resolution: {integrity: sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + memfs@3.5.3: + resolution: {integrity: sha512-UERzLsxzllchadvbPs5aolHh65ISpKpM+ccLbOJ8/vvpBKmAWf+la7dXFy7Mr0ySHbdHrFv5kGFCUHHe6GFEmw==} + engines: {node: '>= 4.0.0'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + min-indent@1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + + minimalistic-assert@1.0.1: + resolution: {integrity: sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + motion-dom@11.18.1: + resolution: {integrity: sha512-g76KvA001z+atjfxczdRtw/RXOM3OMSdd1f4DL77qCTF/+avrRJiawSG4yDibEQ215sr9kpinSlX2pCTJ9zbhw==} + + motion-utils@11.18.1: + resolution: {integrity: sha512-49Kt+HKjtbJKLtgO/LKj9Ld+6vw9BjH5d9sc40R/kVyH8GLAXgT42M2NnuPcJNuA3s9ZfZBUcwIgpmZWGEE+hA==} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + multicast-dns@7.2.5: + resolution: {integrity: sha512-2eznPJP8z2BFLX50tf0LuODrpINqP1RVIm/CObbTcBRITQgmC/TjcREF1NeTBzIcR5XO/ukWo+YHOjBbFwIupg==} + hasBin: true + + mz@2.7.0: + resolution: {integrity: sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + neo-async@2.6.2: + resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} + + no-case@3.0.4: + resolution: {integrity: sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==} + + node-forge@1.3.3: + resolution: {integrity: sha512-rLvcdSyRCyouf6jcOIPe/BgwG/d7hKjzMKOas33/pHEr6gbq18IK9zV7DiPvzsz0oBJPme6qr6H6kGZuI9/DZg==} + engines: {node: '>= 6.13.0'} + + node-int64@0.4.0: + resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} + + node-releases@2.0.36: + resolution: {integrity: sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + nwsapi@2.2.23: + resolution: {integrity: sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-hash@3.0.0: + resolution: {integrity: sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==} + engines: {node: '>= 6'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-is@1.1.6: + resolution: {integrity: sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + obuf@1.1.2: + resolution: {integrity: sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + open@8.4.2: + resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} + engines: {node: '>=12'} + + p-limit@2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-limit@4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-locate@4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + + p-locate@6.0.0: + resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + p-retry@4.6.2: + resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} + engines: {node: '>=8'} + + p-try@2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + + param-case@3.0.4: + resolution: {integrity: sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + pascal-case@3.1.2: + resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-exists@5.0.0: + resolution: {integrity: sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pify@2.3.0: + resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} + engines: {node: '>=0.10.0'} + + pirates@4.0.7: + resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} + engines: {node: '>= 6'} + + pkg-dir@4.2.0: + resolution: {integrity: sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==} + engines: {node: '>=8'} + + pkg-dir@7.0.0: + resolution: {integrity: sha512-Ie9z/WINcxxLp27BKOCHGde4ITq9UklYKDzVo1nhk5sqGEXU3FpkwP5GM2voTGJkGd9B3Otl+Q4uwSOeSUtOBA==} + engines: {node: '>=14.16'} + + playwright-core@1.60.0: + resolution: {integrity: sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.60.0: + resolution: {integrity: sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==} + engines: {node: '>=18'} + hasBin: true + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-import@15.1.0: + resolution: {integrity: sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==} + engines: {node: '>=14.0.0'} + peerDependencies: + postcss: ^8.0.0 + + postcss-js@4.1.0: + resolution: {integrity: sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==} + engines: {node: ^12 || ^14 || >= 16} + peerDependencies: + postcss: ^8.4.21 + + postcss-load-config@6.0.1: + resolution: {integrity: sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==} + engines: {node: '>= 18'} + peerDependencies: + jiti: '>=1.21.0' + postcss: '>=8.0.9' + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + jiti: + optional: true + postcss: + optional: true + tsx: + optional: true + yaml: + optional: true + + postcss-loader@7.3.4: + resolution: {integrity: sha512-iW5WTTBSC5BfsBJ9daFMPVrLT36MrNiC6fqOZTTaHjBNX6Pfd5p+hSBqe/fEeNd7pc13QiAyGt7VdGMw4eRC4A==} + engines: {node: '>= 14.15.0'} + peerDependencies: + postcss: ^7.0.0 || ^8.0.1 + webpack: ^5.0.0 + + postcss-modules-extract-imports@3.1.0: + resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-local-by-default@4.2.0: + resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-scope@3.2.1: + resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-values@4.0.0: + resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + pretty-error@4.0.0: + resolution: {integrity: sha512-AoJ5YMAcXKYxKhuJGdcvse+Voc6v1RgnsR3nWcYU7q4t6z0Q6T86sv5Zq8VIRbOWWFpvdGE83LtdSMNd+6Y0xw==} + + pretty-format@27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + + pretty-format@29.7.0: + resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} + engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + psl@1.15.0: + resolution: {integrity: sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + pure-rand@6.1.0: + resolution: {integrity: sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==} + + qs@6.14.2: + resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} + engines: {node: '>=0.6'} + + querystringify@2.2.0: + resolution: {integrity: sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-error-boundary@3.1.4: + resolution: {integrity: sha512-uM9uPzZJTF6wRQORmSrvOIgt4lJ9MC1sNgEOj2XGsDTRE4kmpWxg7ENK9EWNKJRMAOY9z0MuF4yIfl6gp4sotA==} + engines: {node: '>=10', npm: '>=6'} + peerDependencies: + react: '>=16.13.1' + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-is@17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + + react-is@18.3.1: + resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + + react-router-dom@6.30.3: + resolution: {integrity: sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-router@6.30.3: + resolution: {integrity: sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + + react-smooth@4.0.4: + resolution: {integrity: sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + react-transition-group@4.4.5: + resolution: {integrity: sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==} + peerDependencies: + react: '>=16.6.0' + react-dom: '>=16.6.0' + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + read-cache@1.0.0: + resolution: {integrity: sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + recharts-scale@0.4.5: + resolution: {integrity: sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==} + + recharts@2.15.4: + resolution: {integrity: sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==} + engines: {node: '>=14'} + peerDependencies: + react: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + rechoir@0.8.0: + resolution: {integrity: sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==} + engines: {node: '>= 10.13.0'} + + redent@3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + + regenerate-unicode-properties@10.2.2: + resolution: {integrity: sha512-m03P+zhBeQd1RGnYxrGyDAPpWX/epKirLrp8e3qevZdVkKtnCrjjWczIbYc8+xd6vcTStVlqfycTx1KR4LOr0g==} + engines: {node: '>=4'} + + regenerate@1.4.2: + resolution: {integrity: sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + regexpu-core@6.4.0: + resolution: {integrity: sha512-0ghuzq67LI9bLXpOX/ISfve/Mq33a4aFRzoQYhnnok1JOFpmE/A2TBGkNVenOGEeSBCjIiWcc6MVOG5HEQv0sA==} + engines: {node: '>=4'} + + regjsgen@0.8.0: + resolution: {integrity: sha512-RvwtGe3d7LvWiDQXeQw8p5asZUmfU1G/l6WbUXeHta7Y2PEIvBTwH6E2EfmYUK8pxcxEdEmaomqyp0vZZ7C+3Q==} + + regjsparser@0.13.0: + resolution: {integrity: sha512-NZQZdC5wOE/H3UT28fVGL+ikOZcEzfMGk/c3iN9UGxzWHMa1op7274oyiUVrAG4B2EuFhus8SvkaYnhvW92p9Q==} + hasBin: true + + relateurl@0.2.7: + resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==} + engines: {node: '>= 0.10'} + + renderkid@3.0.0: + resolution: {integrity: sha512-q/7VIQA8lmM1hF+jn+sFSPWGlMkSAeNYcPLmDQx2zzuiDfaLrOmumR8iaUKlenFgh0XRPIUeSPlH3A+AW3Z5pg==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + requires-port@1.0.0: + resolution: {integrity: sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==} + + resolve-cwd@3.0.0: + resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} + engines: {node: '>=8'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + retry@0.13.1: + resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} + engines: {node: '>= 4'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + saxes@6.0.0: + resolution: {integrity: sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==} + engines: {node: '>=v12.22.7'} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + schema-utils@4.3.3: + resolution: {integrity: sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==} + engines: {node: '>= 10.13.0'} + + select-hose@2.0.0: + resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} + + selfsigned@2.4.1: + resolution: {integrity: sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q==} + engines: {node: '>=10'} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + + serve-index@1.9.2: + resolution: {integrity: sha512-KDj11HScOaLmrPxl70KYNW1PksP4Nb/CLL2yvC+Qd2kHMPEEpfc4Re2e4FOay+bC/+XQl/7zAcWON3JVo5v3KQ==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shallow-clone@3.0.1: + resolution: {integrity: sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==} + engines: {node: '>=8'} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shell-quote@1.8.3: + resolution: {integrity: sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==} + engines: {node: '>= 0.4'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + sockjs@0.3.24: + resolution: {integrity: sha512-GJgLTZ7vYb/JtPSSZ10hsOYIvEYsjbNU+zPdIHcUaWVNUEPivzxku31865sSSud0Da0W4lEeOPlmw93zLQchuQ==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.13: + resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + spdy-transport@3.0.0: + resolution: {integrity: sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw==} + + spdy@4.0.2: + resolution: {integrity: sha512-r46gZQZQV+Kl9oItvl1JZZqJKGr+oEkB08A6BzkiR7593/7IbtuncXHd2YoYeTsG4157ZssMu9KYvUHLcjcDoA==} + engines: {node: '>=6.0.0'} + + sprintf-js@1.0.3: + resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} + + stack-utils@2.0.6: + resolution: {integrity: sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==} + engines: {node: '>=10'} + + statuses@1.5.0: + resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} + engines: {node: '>= 0.6'} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + string-length@4.0.2: + resolution: {integrity: sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==} + engines: {node: '>=10'} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-bom@4.0.0: + resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} + engines: {node: '>=8'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-indent@3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + style-loader@3.3.4: + resolution: {integrity: sha512-0WqXzrsMTyb8yjZJHDqwmnwRJvhALK9LfRtRc6B4UTWe8AijYLZYZ9thuJTZc2VfQWINADW/j+LiJnfy2RoC1w==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^5.0.0 + + sucrase@3.35.1: + resolution: {integrity: sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==} + engines: {node: '>=16 || 14 >=14.17'} + hasBin: true + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + symbol-tree@3.2.4: + resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} + + tailwindcss@3.4.19: + resolution: {integrity: sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==} + engines: {node: '>=14.0.0'} + hasBin: true + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + terser-webpack-plugin@5.3.17: + resolution: {integrity: sha512-YR7PtUp6GMU91BgSJmlaX/rS2lGDbAF7D+Wtq7hRO+MiljNmodYvqslzCFiYVAgW+Qoaaia/QUIP4lGXufjdZw==} + engines: {node: '>= 10.13.0'} + peerDependencies: + '@swc/core': '*' + esbuild: '*' + uglify-js: '*' + webpack: ^5.1.0 + peerDependenciesMeta: + '@swc/core': + optional: true + esbuild: + optional: true + uglify-js: + optional: true + + terser@5.46.0: + resolution: {integrity: sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==} + engines: {node: '>=10'} + hasBin: true + + test-exclude@6.0.0: + resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} + engines: {node: '>=8'} + + thenify-all@1.6.0: + resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} + engines: {node: '>=0.8'} + + thenify@3.3.1: + resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==} + + thunky@1.1.0: + resolution: {integrity: sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==} + + tiny-invariant@1.3.3: + resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tmpl@1.0.5: + resolution: {integrity: sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + tough-cookie@4.1.4: + resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} + engines: {node: '>=6'} + + tr46@3.0.0: + resolution: {integrity: sha512-l7FvfAHlcmulp8kr+flpQZmVwtu7nfRV7NZujtN0OqES8EL4O4e0qqzL0DC5gAvx/ZC/9lk6rhcUwYvkBnBnYA==} + engines: {node: '>=12'} + + ts-interface-checker@0.1.13: + resolution: {integrity: sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==} + + ts-jest@29.4.11: + resolution: {integrity: sha512-IrFl7l9AuB/qrNw5quqvAv/hmKMb8dhWOH4jQOGo0Oq8tCeo1O86/iTFG1FaRimgUkF13l4PcepO8ATFT6Ns4g==} + engines: {node: ^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@babel/core': '>=7.0.0-beta.0 <8' + '@jest/transform': ^29.0.0 || ^30.0.0 + '@jest/types': ^29.0.0 || ^30.0.0 + babel-jest: ^29.0.0 || ^30.0.0 + esbuild: '*' + jest: ^29.0.0 || ^30.0.0 + jest-util: ^29.0.0 || ^30.0.0 + typescript: '>=4.3 <7' + peerDependenciesMeta: + '@babel/core': + optional: true + '@jest/transform': + optional: true + '@jest/types': + optional: true + babel-jest: + optional: true + esbuild: + optional: true + jest-util: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-detect@4.0.8: + resolution: {integrity: sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==} + engines: {node: '>=4'} + + type-fest@0.21.3: + resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} + engines: {node: '>=10'} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + uglify-js@3.19.3: + resolution: {integrity: sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==} + engines: {node: '>=0.8.0'} + hasBin: true + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + unicode-canonical-property-names-ecmascript@2.0.1: + resolution: {integrity: sha512-dA8WbNeb2a6oQzAQ55YlT5vQAWGV9WXOsi3SskE3bcCdM0P4SDd+24zS/OCacdRq5BkdsRj9q3Pg6YyQoxIGqg==} + engines: {node: '>=4'} + + unicode-match-property-ecmascript@2.0.0: + resolution: {integrity: sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q==} + engines: {node: '>=4'} + + unicode-match-property-value-ecmascript@2.2.1: + resolution: {integrity: sha512-JQ84qTuMg4nVkx8ga4A16a1epI9H6uTXAknqxkGF/aFfRLw1xC/Bp24HNLaZhHSkWd3+84t8iXnp1J0kYcZHhg==} + engines: {node: '>=4'} + + unicode-property-aliases-ecmascript@2.2.0: + resolution: {integrity: sha512-hpbDzxUY9BFwX+UeBnxv3Sh1q7HFxj48DTmXchNgRa46lO8uj3/1iEn3MiNUYTg1g9ctIqXCCERn8gYZhHC5lQ==} + engines: {node: '>=4'} + + universalify@0.2.0: + resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} + engines: {node: '>= 4.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + url-parse@1.5.10: + resolution: {integrity: sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==} + + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + utila@0.4.0: + resolution: {integrity: sha512-Z0DbgELS9/L/75wZbro8xAnT50pBVFQZ+hUEueGDU5FN51YSCYM+jdxsfCiHjwNP/4LCDD0i/graKpeBnOXKRA==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uuid@8.3.2: + resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + hasBin: true + + v8-to-istanbul@9.3.0: + resolution: {integrity: sha512-kiGUalWN+rgBJ/1OHZsBtU4rXZOfj/7rKQxULKlIzwzQSvMJUUNgPwJEEh7gU6xEVxC0ahoOBvN2YI8GH6FNgA==} + engines: {node: '>=10.12.0'} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + victory-vendor@36.9.2: + resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==} + + w3c-xmlserializer@4.0.0: + resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} + engines: {node: '>=14'} + + walker@1.0.8: + resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} + + watchpack@2.5.1: + resolution: {integrity: sha512-Zn5uXdcFNIA1+1Ei5McRd+iRzfhENPCe7LeABkJtNulSxjma+l7ltNx55BWZkRlwRnpOgHqxnjyaDgJnNXnqzg==} + engines: {node: '>=10.13.0'} + + wbuf@1.7.3: + resolution: {integrity: sha512-O84QOnr0icsbFGLS0O3bI5FswxzRr8/gHwWkDlQFskhSPryQXvrTMxjxGP4+iWYoauLoBvfDpkrOauZ+0iZpDA==} + + webidl-conversions@7.0.0: + resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} + engines: {node: '>=12'} + + webpack-cli@5.1.4: + resolution: {integrity: sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==} + engines: {node: '>=14.15.0'} + hasBin: true + peerDependencies: + '@webpack-cli/generators': '*' + webpack: 5.x.x + webpack-bundle-analyzer: '*' + webpack-dev-server: '*' + peerDependenciesMeta: + '@webpack-cli/generators': + optional: true + webpack-bundle-analyzer: + optional: true + webpack-dev-server: + optional: true + + webpack-dev-middleware@5.3.4: + resolution: {integrity: sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q==} + engines: {node: '>= 12.13.0'} + peerDependencies: + webpack: ^4.0.0 || ^5.0.0 + + webpack-dev-server@4.15.2: + resolution: {integrity: sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g==} + engines: {node: '>= 12.13.0'} + hasBin: true + peerDependencies: + webpack: ^4.37.0 || ^5.0.0 + webpack-cli: '*' + peerDependenciesMeta: + webpack: + optional: true + webpack-cli: + optional: true + + webpack-merge@5.10.0: + resolution: {integrity: sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==} + engines: {node: '>=10.0.0'} + + webpack-sources@3.3.4: + resolution: {integrity: sha512-7tP1PdV4vF+lYPnkMR0jMY5/la2ub5Fc/8VQrrU+lXkiM6C4TjVfGw7iKfyhnTQOsD+6Q/iKw0eFciziRgD58Q==} + engines: {node: '>=10.13.0'} + + webpack@5.105.4: + resolution: {integrity: sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==} + engines: {node: '>=10.13.0'} + hasBin: true + peerDependencies: + webpack-cli: '*' + peerDependenciesMeta: + webpack-cli: + optional: true + + websocket-driver@0.7.4: + resolution: {integrity: sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==} + engines: {node: '>=0.8.0'} + + websocket-extensions@0.1.4: + resolution: {integrity: sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==} + engines: {node: '>=0.8.0'} + + whatwg-encoding@2.0.0: + resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} + engines: {node: '>=12'} + + whatwg-mimetype@3.0.0: + resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} + engines: {node: '>=12'} + + whatwg-url@11.0.0: + resolution: {integrity: sha512-RKT8HExMpoYx4igMiVMY83lN6UeITKJlBQ+vR/8ZJ8OCdSiN3RwCq+9gH0+Xzj0+5IrM6i4j/6LuvzbZIQgEcQ==} + engines: {node: '>=12'} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + wildcard@2.0.1: + resolution: {integrity: sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==} + + wordwrap@1.0.0: + resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + write-file-atomic@4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + + ws@8.19.0: + resolution: {integrity: sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xml-name-validator@4.0.0: + resolution: {integrity: sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==} + engines: {node: '>=12'} + + xmlchars@2.2.0: + resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + +snapshots: + + '@adobe/css-tools@4.5.0': {} + + '@alloc/quick-lru@5.2.0': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-create-regexp-features-plugin@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + regexpu-core: 6.4.0 + semver: 6.3.1 + + '@babel/helper-define-polyfill-provider@0.6.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + debug: 4.4.3 + lodash.debounce: 4.0.8 + resolve: 1.22.11 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-remap-async-to-generator@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-wrap-function': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helper-wrap-function@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-bugfix-firefox-class-in-computed-class-key@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-safari-class-field-initializer-scope@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-assertions@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-unicode-sets-regex@7.18.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-arrow-functions@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-async-generator-functions@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-async-to-generator@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-remap-async-to-generator': 7.27.1(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-block-scoped-functions@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-block-scoping@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-class-properties@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-class-static-block@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-classes@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-globals': 7.28.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-computed-properties@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/template': 7.28.6 + + '@babel/plugin-transform-destructuring@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-dotall-regex@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-keys@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-duplicate-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-dynamic-import@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-explicit-resource-management@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-exponentiation-operator@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-export-namespace-from@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-for-of@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-function-name@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-json-strings@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-literals@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-logical-assignment-operators@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-member-expression-literals@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-modules-amd@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-systemjs@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-modules-umd@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-named-capturing-groups-regex@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-new-target@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-nullish-coalescing-operator@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-numeric-separator@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-object-rest-spread@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-object-super@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-optional-catch-binding@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-optional-chaining@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-parameters@7.27.7(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-private-methods@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-private-property-in-object@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-property-literals@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-display-name@7.28.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-react-jsx-development@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regenerator@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-regexp-modifiers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-reserved-words@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-shorthand-properties@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-spread@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-sticky-regex@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-template-literals@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typeof-symbol@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-unicode-escapes@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-property-regex@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-regex@7.27.1(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-unicode-sets-regex@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-create-regexp-features-plugin': 7.28.5(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/preset-env@7.29.0(@babel/core@7.29.0)': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-bugfix-firefox-class-in-computed-class-key': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-class-field-initializer-scope': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-proposal-private-property-in-object': 7.21.0-placeholder-for-preset-env.2(@babel/core@7.29.0) + '@babel/plugin-syntax-import-assertions': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-unicode-sets-regex': 7.18.6(@babel/core@7.29.0) + '@babel/plugin-transform-arrow-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-async-generator-functions': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-async-to-generator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoped-functions': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-block-scoping': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-class-static-block': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-classes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-computed-properties': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-destructuring': 7.28.5(@babel/core@7.29.0) + '@babel/plugin-transform-dotall-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-keys': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-duplicate-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-dynamic-import': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-explicit-resource-management': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-exponentiation-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-export-namespace-from': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-for-of': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-function-name': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-json-strings': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-logical-assignment-operators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-member-expression-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-amd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-systemjs': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-modules-umd': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-named-capturing-groups-regex': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-new-target': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-nullish-coalescing-operator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-numeric-separator': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-rest-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-object-super': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-optional-catch-binding': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-optional-chaining': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-parameters': 7.27.7(@babel/core@7.29.0) + '@babel/plugin-transform-private-methods': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-private-property-in-object': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-property-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-regenerator': 7.29.0(@babel/core@7.29.0) + '@babel/plugin-transform-regexp-modifiers': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-reserved-words': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-shorthand-properties': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-spread': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-sticky-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-template-literals': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-typeof-symbol': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-escapes': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-property-regex': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-regex': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-unicode-sets-regex': 7.28.6(@babel/core@7.29.0) + '@babel/preset-modules': 0.1.6-no-external-plugins(@babel/core@7.29.0) + babel-plugin-polyfill-corejs2: 0.4.16(@babel/core@7.29.0) + babel-plugin-polyfill-corejs3: 0.14.1(@babel/core@7.29.0) + babel-plugin-polyfill-regenerator: 0.6.7(@babel/core@7.29.0) + core-js-compat: 3.48.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/preset-modules@0.1.6-no-external-plugins(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/types': 7.29.0 + esutils: 2.0.3 + + '@babel/preset-react@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-transform-react-display-name': 7.28.0(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-react-jsx-development': 7.27.1(@babel/core@7.29.0) + '@babel/plugin-transform-react-pure-annotations': 7.27.1(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.28.6': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@bcoe/v8-coverage@0.2.3': {} + + '@discoveryjs/json-ext@0.5.7': {} + + '@emotion/is-prop-valid@1.4.0': + dependencies: + '@emotion/memoize': 0.9.0 + + '@emotion/memoize@0.9.0': {} + + '@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.14.2 + resolve-from: 5.0.0 + + '@istanbuljs/schema@0.1.6': {} + + '@jest/console@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@types/node': 25.3.5 + chalk: 4.1.2 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + slash: 3.0.0 + + '@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': 25.3.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + ci-info: 3.9.0 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-changed-files: 29.7.0 + jest-config: 29.7.0(@types/node@25.3.5) + 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.8 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-ansi: 6.0.1 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + - ts-node + + '@jest/environment@29.7.0': + dependencies: + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 25.3.5 + jest-mock: 29.7.0 + + '@jest/expect-utils@29.7.0': + dependencies: + jest-get-type: 29.6.3 + + '@jest/expect@29.7.0': + dependencies: + expect: 29.7.0 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + '@jest/fake-timers@29.7.0': + dependencies: + '@jest/types': 29.6.3 + '@sinonjs/fake-timers': 10.3.0 + '@types/node': 25.3.5 + jest-message-util: 29.7.0 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + '@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 + transitivePeerDependencies: + - supports-color + + '@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.31 + '@types/node': 25.3.5 + chalk: 4.1.2 + collect-v8-coverage: 1.0.3 + exit: 0.1.2 + glob: 7.2.3 + graceful-fs: 4.2.11 + istanbul-lib-coverage: 3.2.2 + istanbul-lib-instrument: 6.0.3 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 4.0.1 + istanbul-reports: 3.2.0 + jest-message-util: 29.7.0 + jest-util: 29.7.0 + jest-worker: 29.7.0 + slash: 3.0.0 + string-length: 4.0.2 + strip-ansi: 6.0.1 + v8-to-istanbul: 9.3.0 + transitivePeerDependencies: + - supports-color + + '@jest/schemas@29.6.3': + dependencies: + '@sinclair/typebox': 0.27.10 + + '@jest/source-map@29.6.3': + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + callsites: 3.1.0 + graceful-fs: 4.2.11 + + '@jest/test-result@29.7.0': + dependencies: + '@jest/console': 29.7.0 + '@jest/types': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + collect-v8-coverage: 1.0.3 + + '@jest/test-sequencer@29.7.0': + dependencies: + '@jest/test-result': 29.7.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + slash: 3.0.0 + + '@jest/transform@29.7.0': + dependencies: + '@babel/core': 7.29.0 + '@jest/types': 29.6.3 + '@jridgewell/trace-mapping': 0.3.31 + babel-plugin-istanbul: 6.1.1 + chalk: 4.1.2 + convert-source-map: 2.0.0 + fast-json-stable-stringify: 2.1.0 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + micromatch: 4.0.8 + pirates: 4.0.7 + slash: 3.0.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + + '@jest/types@29.6.3': + dependencies: + '@jest/schemas': 29.6.3 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 25.3.5 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/source-map@0.3.11': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@leichtgewicht/ip-codec@2.0.5': {} + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@playwright/test@1.60.0': + dependencies: + playwright: 1.60.0 + + '@remix-run/router@1.23.2': {} + + '@sinclair/typebox@0.27.10': {} + + '@sinonjs/commons@3.0.1': + dependencies: + type-detect: 4.0.8 + + '@sinonjs/fake-timers@10.3.0': + dependencies: + '@sinonjs/commons': 3.0.1 + + '@supabase/auth-js@2.98.0': + dependencies: + tslib: 2.8.1 + + '@supabase/functions-js@2.98.0': + dependencies: + tslib: 2.8.1 + + '@supabase/postgrest-js@2.98.0': + dependencies: + tslib: 2.8.1 + + '@supabase/realtime-js@2.98.0': + dependencies: + '@types/phoenix': 1.6.7 + '@types/ws': 8.18.1 + tslib: 2.8.1 + ws: 8.19.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@supabase/storage-js@2.98.0': + dependencies: + iceberg-js: 0.8.1 + tslib: 2.8.1 + + '@supabase/supabase-js@2.98.0': + dependencies: + '@supabase/auth-js': 2.98.0 + '@supabase/functions-js': 2.98.0 + '@supabase/postgrest-js': 2.98.0 + '@supabase/realtime-js': 2.98.0 + '@supabase/storage-js': 2.98.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + + '@testing-library/dom@9.3.4': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/runtime': 7.28.6 + '@types/aria-query': 5.0.4 + aria-query: 5.1.3 + chalk: 4.1.2 + dom-accessibility-api: 0.5.16 + lz-string: 1.5.0 + pretty-format: 27.5.1 + + '@testing-library/jest-dom@6.9.1': + dependencies: + '@adobe/css-tools': 4.5.0 + aria-query: 5.3.2 + css.escape: 1.5.1 + dom-accessibility-api: 0.6.3 + picocolors: 1.1.1 + redent: 3.0.0 + + '@testing-library/react-hooks@8.0.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.28.6 + react: 18.3.1 + react-error-boundary: 3.1.4(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + react-dom: 18.3.1(react@18.3.1) + + '@testing-library/react@14.3.1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.28.6 + '@testing-library/dom': 9.3.4 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + transitivePeerDependencies: + - '@types/react' + + '@tootallnate/once@2.0.1': {} + + '@types/aria-query@5.0.4': {} + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.0 + + '@types/body-parser@1.19.6': + dependencies: + '@types/connect': 3.4.38 + '@types/node': 25.3.5 + + '@types/bonjour@3.5.13': + dependencies: + '@types/node': 25.3.5 + + '@types/connect-history-api-fallback@1.5.4': + dependencies: + '@types/express-serve-static-core': 5.1.1 + '@types/node': 25.3.5 + + '@types/connect@3.4.38': + dependencies: + '@types/node': 25.3.5 + + '@types/d3-array@3.2.2': {} + + '@types/d3-color@3.1.3': {} + + '@types/d3-ease@3.0.2': {} + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-path@3.1.1': {} + + '@types/d3-scale@4.0.9': + dependencies: + '@types/d3-time': 3.0.4 + + '@types/d3-shape@3.1.8': + dependencies: + '@types/d3-path': 3.1.1 + + '@types/d3-time@3.0.4': {} + + '@types/d3-timer@3.0.2': {} + + '@types/eslint-scope@3.7.7': + dependencies: + '@types/eslint': 9.6.1 + '@types/estree': 1.0.8 + + '@types/eslint@9.6.1': + dependencies: + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + + '@types/estree@1.0.8': {} + + '@types/express-serve-static-core@4.19.8': + dependencies: + '@types/node': 25.3.5 + '@types/qs': 6.15.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express-serve-static-core@5.1.1': + dependencies: + '@types/node': 25.3.5 + '@types/qs': 6.15.0 + '@types/range-parser': 1.2.7 + '@types/send': 1.2.1 + + '@types/express@4.17.25': + dependencies: + '@types/body-parser': 1.19.6 + '@types/express-serve-static-core': 4.19.8 + '@types/qs': 6.15.0 + '@types/serve-static': 1.15.10 + + '@types/graceful-fs@4.1.9': + dependencies: + '@types/node': 25.3.5 + + '@types/html-minifier-terser@6.1.0': {} + + '@types/http-errors@2.0.5': {} + + '@types/http-proxy@1.17.17': + dependencies: + '@types/node': 25.3.5 + + '@types/istanbul-lib-coverage@2.0.6': {} + + '@types/istanbul-lib-report@3.0.3': + dependencies: + '@types/istanbul-lib-coverage': 2.0.6 + + '@types/istanbul-reports@3.0.4': + dependencies: + '@types/istanbul-lib-report': 3.0.3 + + '@types/jest@29.5.14': + dependencies: + expect: 29.7.0 + pretty-format: 29.7.0 + + '@types/jsdom@20.0.1': + dependencies: + '@types/node': 25.3.5 + '@types/tough-cookie': 4.0.5 + parse5: 7.3.0 + + '@types/json-schema@7.0.15': {} + + '@types/mime@1.3.5': {} + + '@types/node-forge@1.3.14': + dependencies: + '@types/node': 25.3.5 + + '@types/node@25.3.5': + dependencies: + undici-types: 7.18.2 + + '@types/phoenix@1.6.7': {} + + '@types/prop-types@15.7.15': {} + + '@types/qs@6.15.0': {} + + '@types/range-parser@1.2.7': {} + + '@types/react-dom@18.3.7(@types/react@18.3.28)': + dependencies: + '@types/react': 18.3.28 + + '@types/react@18.3.28': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/retry@0.12.0': {} + + '@types/send@0.17.6': + dependencies: + '@types/mime': 1.3.5 + '@types/node': 25.3.5 + + '@types/send@1.2.1': + dependencies: + '@types/node': 25.3.5 + + '@types/serve-index@1.9.4': + dependencies: + '@types/express': 4.17.25 + + '@types/serve-static@1.15.10': + dependencies: + '@types/http-errors': 2.0.5 + '@types/node': 25.3.5 + '@types/send': 0.17.6 + + '@types/sockjs@0.3.36': + dependencies: + '@types/node': 25.3.5 + + '@types/stack-utils@2.0.3': {} + + '@types/tough-cookie@4.0.5': {} + + '@types/ws@8.18.1': + dependencies: + '@types/node': 25.3.5 + + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + + '@webassemblyjs/ast@1.14.1': + dependencies: + '@webassemblyjs/helper-numbers': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + + '@webassemblyjs/floating-point-hex-parser@1.13.2': {} + + '@webassemblyjs/helper-api-error@1.13.2': {} + + '@webassemblyjs/helper-buffer@1.14.1': {} + + '@webassemblyjs/helper-numbers@1.13.2': + dependencies: + '@webassemblyjs/floating-point-hex-parser': 1.13.2 + '@webassemblyjs/helper-api-error': 1.13.2 + '@xtuc/long': 4.2.2 + + '@webassemblyjs/helper-wasm-bytecode@1.13.2': {} + + '@webassemblyjs/helper-wasm-section@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/wasm-gen': 1.14.1 + + '@webassemblyjs/ieee754@1.13.2': + dependencies: + '@xtuc/ieee754': 1.2.0 + + '@webassemblyjs/leb128@1.13.2': + dependencies: + '@xtuc/long': 4.2.2 + + '@webassemblyjs/utf8@1.13.2': {} + + '@webassemblyjs/wasm-edit@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/helper-wasm-section': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-opt': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + '@webassemblyjs/wast-printer': 1.14.1 + + '@webassemblyjs/wasm-gen@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wasm-opt@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-buffer': 1.14.1 + '@webassemblyjs/wasm-gen': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + + '@webassemblyjs/wasm-parser@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/helper-api-error': 1.13.2 + '@webassemblyjs/helper-wasm-bytecode': 1.13.2 + '@webassemblyjs/ieee754': 1.13.2 + '@webassemblyjs/leb128': 1.13.2 + '@webassemblyjs/utf8': 1.13.2 + + '@webassemblyjs/wast-printer@1.14.1': + dependencies: + '@webassemblyjs/ast': 1.14.1 + '@xtuc/long': 4.2.2 + + '@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.105.4)': + dependencies: + webpack: 5.105.4(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.105.4) + + '@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.105.4)': + dependencies: + webpack: 5.105.4(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.105.4) + + '@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack-dev-server@4.15.2)(webpack@5.105.4)': + dependencies: + webpack: 5.105.4(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.105.4) + optionalDependencies: + webpack-dev-server: 4.15.2(webpack-cli@5.1.4)(webpack@5.105.4) + + '@xtuc/ieee754@1.2.0': {} + + '@xtuc/long@4.2.2': {} + + abab@2.0.6: {} + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-globals@7.0.1: + dependencies: + acorn: 8.16.0 + acorn-walk: 8.3.5 + + acorn-import-phases@1.0.4(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn-walk@8.3.5: + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + ajv-formats@2.1.1(ajv@8.18.0): + optionalDependencies: + ajv: 8.18.0 + + ajv-keywords@5.1.0(ajv@8.18.0): + dependencies: + ajv: 8.18.0 + fast-deep-equal: 3.1.3 + + ajv@8.18.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-escapes@4.3.2: + dependencies: + type-fest: 0.21.3 + + ansi-html-community@0.0.8: {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@5.2.0: {} + + any-promise@1.3.0: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@1.0.10: + dependencies: + sprintf-js: 1.0.3 + + argparse@2.0.1: {} + + aria-query@5.1.3: + dependencies: + deep-equal: 2.2.3 + + aria-query@5.3.2: {} + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-flatten@1.1.1: {} + + asynckit@0.4.0: {} + + autoprefixer@10.4.27(postcss@8.5.8): + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001777 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + babel-jest@29.7.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.5 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.29.0) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + + babel-loader@9.2.1(@babel/core@7.29.0)(webpack@5.105.4): + dependencies: + '@babel/core': 7.29.0 + find-cache-dir: 4.0.0 + schema-utils: 4.3.3 + webpack: 5.105.4(webpack-cli@5.1.4) + + babel-plugin-istanbul@6.1.1: + dependencies: + '@babel/helper-plugin-utils': 7.28.6 + '@istanbuljs/load-nyc-config': 1.1.0 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-instrument: 5.2.1 + test-exclude: 6.0.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-jest-hoist@29.6.3: + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + '@types/babel__core': 7.20.5 + '@types/babel__traverse': 7.28.0 + + babel-plugin-polyfill-corejs2@0.4.16(@babel/core@7.29.0): + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0) + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-corejs3@0.14.1(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0) + core-js-compat: 3.48.0 + transitivePeerDependencies: + - supports-color + + babel-plugin-polyfill-regenerator@0.6.7(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-define-polyfill-provider': 0.6.7(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@29.6.3(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + + balanced-match@1.0.2: {} + + base64-arraybuffer@1.0.2: {} + + baseline-browser-mapping@2.10.0: {} + + batch@0.6.1: {} + + binary-extensions@2.3.0: {} + + body-parser@1.20.4: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.14.2 + raw-body: 2.5.3 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + bonjour-service@1.3.0: + dependencies: + fast-deep-equal: 3.1.3 + multicast-dns: 7.2.5 + + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001777 + electron-to-chromium: 1.5.307 + node-releases: 2.0.36 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + bs-logger@0.2.6: + dependencies: + fast-json-stable-stringify: 2.1.0 + + bser@2.1.1: + dependencies: + node-int64: 0.4.0 + + buffer-from@1.1.2: {} + + bytes@3.1.2: {} + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.9: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + camel-case@4.1.2: + dependencies: + pascal-case: 3.1.2 + tslib: 2.8.1 + + camelcase-css@2.0.1: {} + + camelcase@5.3.1: {} + + camelcase@6.3.0: {} + + caniuse-lite@1.0.30001777: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + char-regex@1.0.2: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chrome-trace-event@1.0.4: {} + + ci-info@3.9.0: {} + + cjs-module-lexer@1.4.3: {} + + clean-css@5.3.3: + dependencies: + source-map: 0.6.1 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone-deep@4.0.1: + dependencies: + is-plain-object: 2.0.4 + kind-of: 6.0.3 + shallow-clone: 3.0.1 + + clsx@2.1.1: {} + + co@4.6.0: {} + + collect-v8-coverage@1.0.3: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@10.0.1: {} + + commander@2.20.3: {} + + commander@4.1.1: {} + + commander@8.3.0: {} + + common-path-prefix@3.0.0: {} + + compressible@2.0.18: + dependencies: + mime-db: 1.54.0 + + compression@1.8.1: + dependencies: + bytes: 3.1.2 + compressible: 2.0.18 + debug: 2.6.9 + negotiator: 0.6.4 + on-headers: 1.1.0 + safe-buffer: 5.2.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + concat-map@0.0.1: {} + + connect-history-api-fallback@2.0.0: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + convert-source-map@2.0.0: {} + + cookie-signature@1.0.7: {} + + cookie@0.7.2: {} + + core-js-compat@3.48.0: + dependencies: + browserslist: 4.28.1 + + core-util-is@1.0.3: {} + + cosmiconfig@8.3.6(typescript@5.9.3): + dependencies: + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + path-type: 4.0.0 + optionalDependencies: + typescript: 5.9.3 + + create-jest@29.7.0(@types/node@25.3.5): + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + exit: 0.1.2 + graceful-fs: 4.2.11 + jest-config: 29.7.0(@types/node@25.3.5) + jest-util: 29.7.0 + prompts: 2.4.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-loader@6.11.0(webpack@5.105.4): + dependencies: + icss-utils: 5.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.8) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.8) + postcss-modules-scope: 3.2.1(postcss@8.5.8) + postcss-modules-values: 4.0.0(postcss@8.5.8) + postcss-value-parser: 4.2.0 + semver: 7.7.4 + optionalDependencies: + webpack: 5.105.4(webpack-cli@5.1.4) + + css-select@4.3.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + + css-what@6.2.2: {} + + css.escape@1.5.1: {} + + cssesc@3.0.0: {} + + cssom@0.3.8: {} + + cssom@0.5.0: {} + + cssstyle@2.3.0: + dependencies: + cssom: 0.3.8 + + csstype@3.2.3: {} + + d3-array@3.2.4: + dependencies: + internmap: 2.0.3 + + d3-color@3.1.0: {} + + d3-ease@3.0.1: {} + + d3-format@3.1.2: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-path@3.1.0: {} + + d3-scale@4.0.2: + dependencies: + d3-array: 3.2.4 + d3-format: 3.1.2 + d3-interpolate: 3.0.1 + d3-time: 3.1.0 + d3-time-format: 4.1.0 + + d3-shape@3.2.0: + dependencies: + d3-path: 3.1.0 + + d3-time-format@4.1.0: + dependencies: + d3-time: 3.1.0 + + d3-time@3.1.0: + dependencies: + d3-array: 3.2.4 + + d3-timer@3.0.1: {} + + data-urls@3.0.2: + dependencies: + abab: 2.0.6 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.28.6 + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decimal.js-light@2.5.1: {} + + decimal.js@10.6.0: {} + + dedent@1.7.2: {} + + deep-equal@2.2.3: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.9 + es-get-iterator: 1.1.3 + get-intrinsic: 1.3.0 + is-arguments: 1.2.0 + is-array-buffer: 3.0.5 + is-date-object: 1.1.0 + is-regex: 1.2.1 + is-shared-array-buffer: 1.0.4 + isarray: 2.0.5 + object-is: 1.1.6 + object-keys: 1.1.1 + object.assign: 4.1.7 + regexp.prototype.flags: 1.5.4 + side-channel: 1.1.0 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.20 + + deepmerge@4.3.1: {} + + default-gateway@6.0.3: + dependencies: + execa: 5.1.1 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-lazy-prop@2.0.0: {} + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + delayed-stream@1.0.0: {} + + depd@1.1.2: {} + + depd@2.0.0: {} + + destroy@1.2.0: {} + + detect-newline@3.1.0: {} + + detect-node@2.1.0: {} + + didyoumean@1.2.2: {} + + diff-sequences@29.6.3: {} + + dlv@1.1.3: {} + + dns-packet@5.6.1: + dependencies: + '@leichtgewicht/ip-codec': 2.0.5 + + dom-accessibility-api@0.5.16: {} + + dom-accessibility-api@0.6.3: {} + + dom-converter@0.2.0: + dependencies: + utila: 0.4.0 + + dom-helpers@5.2.1: + dependencies: + '@babel/runtime': 7.28.6 + csstype: 3.2.3 + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + domelementtype@2.3.0: {} + + domexception@4.0.0: + dependencies: + webidl-conversions: 7.0.0 + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + dot-case@3.0.4: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.307: {} + + emittery@0.13.1: {} + + emoji-regex@8.0.0: {} + + encodeurl@2.0.0: {} + + enhanced-resolve@5.20.0: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@2.2.0: {} + + entities@6.0.1: {} + + envinfo@7.21.0: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-get-iterator@1.1.3: + dependencies: + call-bind: 1.0.9 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + is-arguments: 1.2.0 + is-map: 2.0.3 + is-set: 2.0.3 + is-string: 1.1.1 + isarray: 2.0.5 + stop-iteration-iterator: 1.1.0 + + es-module-lexer@2.0.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@2.0.0: {} + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + eslint-scope@5.1.1: + dependencies: + esrecurse: 4.3.0 + estraverse: 4.3.0 + + esprima@4.0.1: {} + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@4.3.0: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + etag@1.8.1: {} + + eventemitter3@4.0.7: {} + + events@3.3.0: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit@0.1.2: {} + + 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 + + express@4.22.1: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.4 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.0.7 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.2 + fresh: 0.5.2 + http-errors: 2.0.1 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.14.2 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.2 + serve-static: 1.16.3 + setprototypeof: 1.2.0 + statuses: 2.0.2 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + fast-deep-equal@3.1.3: {} + + fast-equals@5.4.0: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-uri@3.1.0: {} + + fastest-levenshtein@1.0.16: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + faye-websocket@0.11.4: + dependencies: + websocket-driver: 0.7.4 + + fb-watchman@2.0.2: + dependencies: + bser: 2.1.1 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.3.2: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-cache-dir@4.0.0: + dependencies: + common-path-prefix: 3.0.0 + pkg-dir: 7.0.0 + + find-up@4.1.0: + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + + find-up@6.3.0: + dependencies: + locate-path: 7.2.0 + path-exists: 5.0.0 + + flat@5.0.2: {} + + follow-redirects@1.15.11: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.2 + mime-types: 2.1.35 + + forwarded@0.2.0: {} + + fraction.js@5.3.4: {} + + framer-motion@11.18.2(@emotion/is-prop-valid@1.4.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + motion-dom: 11.18.1 + motion-utils: 11.18.1 + tslib: 2.8.1 + optionalDependencies: + '@emotion/is-prop-valid': 1.4.0 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + fresh@0.5.2: {} + + fs-monkey@1.1.0: {} + + fs.realpath@1.0.0: {} + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + functions-have-names@1.2.3: {} + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + 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 + + get-package-type@0.1.0: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob-to-regexp@0.4.1: {} + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + handle-thing@2.0.1: {} + + handlebars@4.7.9: + dependencies: + minimist: 1.2.8 + neo-async: 2.6.2 + source-map: 0.6.1 + wordwrap: 1.0.0 + optionalDependencies: + uglify-js: 3.19.3 + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + he@1.2.0: {} + + hpack.js@2.1.6: + dependencies: + inherits: 2.0.4 + obuf: 1.1.2 + readable-stream: 2.3.8 + wbuf: 1.7.3 + + html-encoding-sniffer@3.0.0: + dependencies: + whatwg-encoding: 2.0.0 + + html-entities@2.6.0: {} + + html-escaper@2.0.2: {} + + html-minifier-terser@6.1.0: + dependencies: + camel-case: 4.1.2 + clean-css: 5.3.3 + commander: 8.3.0 + he: 1.2.0 + param-case: 3.0.4 + relateurl: 0.2.7 + terser: 5.46.0 + + html-webpack-plugin@5.6.6(webpack@5.105.4): + dependencies: + '@types/html-minifier-terser': 6.1.0 + html-minifier-terser: 6.1.0 + lodash: 4.17.23 + pretty-error: 4.0.0 + tapable: 2.3.0 + optionalDependencies: + webpack: 5.105.4(webpack-cli@5.1.4) + + htmlparser2@6.1.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + domutils: 2.8.0 + entities: 2.2.0 + + http-deceiver@1.2.7: {} + + http-errors@1.8.1: + dependencies: + depd: 1.1.2 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 1.5.0 + toidentifier: 1.0.1 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + http-parser-js@0.5.10: {} + + http-proxy-agent@5.0.0: + dependencies: + '@tootallnate/once': 2.0.1 + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + http-proxy-middleware@2.0.9(@types/express@4.17.25): + dependencies: + '@types/http-proxy': 1.17.17 + http-proxy: 1.18.1 + is-glob: 4.0.3 + is-plain-obj: 3.0.0 + micromatch: 4.0.8 + optionalDependencies: + '@types/express': 4.17.25 + transitivePeerDependencies: + - debug + + http-proxy@1.18.1: + dependencies: + eventemitter3: 4.0.7 + follow-redirects: 1.15.11 + requires-port: 1.0.0 + transitivePeerDependencies: + - debug + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + human-signals@2.1.0: {} + + iceberg-js@0.8.1: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + + icss-utils@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + immer@10.2.0: {} + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-local@3.2.0: + dependencies: + pkg-dir: 4.2.0 + resolve-cwd: 3.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + internmap@2.0.3: {} + + interpret@3.1.1: {} + + ipaddr.js@1.9.1: {} + + ipaddr.js@2.3.0: {} + + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-arrayish@0.2.1: {} + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-docker@2.2.1: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-generator-fn@2.1.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-map@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-plain-obj@3.0.0: {} + + is-plain-object@2.0.4: + dependencies: + isobject: 3.0.1 + + is-potential-custom-element-name@1.0.1: {} + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-weakmap@2.0.2: {} + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-wsl@2.2.0: + dependencies: + is-docker: 2.2.1 + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isexe@2.0.0: {} + + isobject@3.0.1: {} + + istanbul-lib-coverage@3.2.2: {} + + istanbul-lib-instrument@5.2.1: + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-coverage: 3.2.2 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + istanbul-lib-instrument@6.0.3: + dependencies: + '@babel/core': 7.29.0 + '@babel/parser': 7.29.0 + '@istanbuljs/schema': 0.1.6 + istanbul-lib-coverage: 3.2.2 + semver: 7.7.4 + transitivePeerDependencies: + - supports-color + + istanbul-lib-report@3.0.1: + dependencies: + istanbul-lib-coverage: 3.2.2 + make-dir: 4.0.0 + supports-color: 7.2.0 + + istanbul-lib-source-maps@4.0.1: + dependencies: + debug: 4.4.3 + istanbul-lib-coverage: 3.2.2 + source-map: 0.6.1 + transitivePeerDependencies: + - supports-color + + istanbul-reports@3.2.0: + dependencies: + html-escaper: 2.0.2 + istanbul-lib-report: 3.0.1 + + jest-changed-files@29.7.0: + dependencies: + execa: 5.1.1 + jest-util: 29.7.0 + p-limit: 3.1.0 + + 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': 25.3.5 + chalk: 4.1.2 + co: 4.6.0 + dedent: 1.7.2 + is-generator-fn: 2.1.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.1.0 + slash: 3.0.0 + stack-utils: 2.0.6 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-cli@29.7.0(@types/node@25.3.5): + dependencies: + '@jest/core': 29.7.0 + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + chalk: 4.1.2 + create-jest: 29.7.0(@types/node@25.3.5) + exit: 0.1.2 + import-local: 3.2.0 + jest-config: 29.7.0(@types/node@25.3.5) + jest-util: 29.7.0 + jest-validate: 29.7.0 + yargs: 17.7.2 + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jest-config@29.7.0(@types/node@25.3.5): + dependencies: + '@babel/core': 7.29.0 + '@jest/test-sequencer': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.0) + chalk: 4.1.2 + ci-info: 3.9.0 + deepmerge: 4.3.1 + glob: 7.2.3 + graceful-fs: 4.2.11 + 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.8 + parse-json: 5.2.0 + pretty-format: 29.7.0 + slash: 3.0.0 + strip-json-comments: 3.1.1 + optionalDependencies: + '@types/node': 25.3.5 + transitivePeerDependencies: + - babel-plugin-macros + - supports-color + + jest-diff@29.7.0: + dependencies: + chalk: 4.1.2 + diff-sequences: 29.6.3 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-docblock@29.7.0: + dependencies: + detect-newline: 3.1.0 + + jest-each@29.7.0: + dependencies: + '@jest/types': 29.6.3 + chalk: 4.1.2 + jest-get-type: 29.6.3 + jest-util: 29.7.0 + pretty-format: 29.7.0 + + jest-environment-jsdom@29.7.0: + dependencies: + '@jest/environment': 29.7.0 + '@jest/fake-timers': 29.7.0 + '@jest/types': 29.6.3 + '@types/jsdom': 20.0.1 + '@types/node': 25.3.5 + jest-mock: 29.7.0 + jest-util: 29.7.0 + jsdom: 20.0.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + 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': 25.3.5 + jest-mock: 29.7.0 + jest-util: 29.7.0 + + jest-get-type@29.6.3: {} + + jest-haste-map@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/graceful-fs': 4.1.9 + '@types/node': 25.3.5 + anymatch: 3.1.3 + fb-watchman: 2.0.2 + graceful-fs: 4.2.11 + jest-regex-util: 29.6.3 + jest-util: 29.7.0 + jest-worker: 29.7.0 + micromatch: 4.0.8 + walker: 1.0.8 + optionalDependencies: + fsevents: 2.3.3 + + jest-leak-detector@29.7.0: + dependencies: + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-matcher-utils@29.7.0: + dependencies: + chalk: 4.1.2 + jest-diff: 29.7.0 + jest-get-type: 29.6.3 + pretty-format: 29.7.0 + + jest-message-util@29.7.0: + dependencies: + '@babel/code-frame': 7.29.0 + '@jest/types': 29.6.3 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + micromatch: 4.0.8 + pretty-format: 29.7.0 + slash: 3.0.0 + stack-utils: 2.0.6 + + jest-mock@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 25.3.5 + jest-util: 29.7.0 + + jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): + optionalDependencies: + jest-resolve: 29.7.0 + + jest-regex-util@29.6.3: {} + + jest-resolve-dependencies@29.7.0: + dependencies: + jest-regex-util: 29.6.3 + jest-snapshot: 29.7.0 + transitivePeerDependencies: + - supports-color + + jest-resolve@29.7.0: + dependencies: + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-haste-map: 29.7.0 + jest-pnp-resolver: 1.2.3(jest-resolve@29.7.0) + jest-util: 29.7.0 + jest-validate: 29.7.0 + resolve: 1.22.11 + resolve.exports: 2.0.3 + slash: 3.0.0 + + 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': 25.3.5 + chalk: 4.1.2 + emittery: 0.13.1 + graceful-fs: 4.2.11 + 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 + transitivePeerDependencies: + - supports-color + + 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': 25.3.5 + chalk: 4.1.2 + cjs-module-lexer: 1.4.3 + collect-v8-coverage: 1.0.3 + glob: 7.2.3 + graceful-fs: 4.2.11 + 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 + transitivePeerDependencies: + - supports-color + + jest-snapshot@29.7.0: + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 + '@jest/expect-utils': 29.7.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) + chalk: 4.1.2 + expect: 29.7.0 + graceful-fs: 4.2.11 + 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.7.4 + transitivePeerDependencies: + - supports-color + + jest-util@29.7.0: + dependencies: + '@jest/types': 29.6.3 + '@types/node': 25.3.5 + chalk: 4.1.2 + ci-info: 3.9.0 + graceful-fs: 4.2.11 + picomatch: 2.3.1 + + jest-validate@29.7.0: + dependencies: + '@jest/types': 29.6.3 + camelcase: 6.3.0 + chalk: 4.1.2 + jest-get-type: 29.6.3 + leven: 3.1.0 + pretty-format: 29.7.0 + + jest-watcher@29.7.0: + dependencies: + '@jest/test-result': 29.7.0 + '@jest/types': 29.6.3 + '@types/node': 25.3.5 + ansi-escapes: 4.3.2 + chalk: 4.1.2 + emittery: 0.13.1 + jest-util: 29.7.0 + string-length: 4.0.2 + + jest-worker@27.5.1: + dependencies: + '@types/node': 25.3.5 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest-worker@29.7.0: + dependencies: + '@types/node': 25.3.5 + jest-util: 29.7.0 + merge-stream: 2.0.0 + supports-color: 8.1.1 + + jest@29.7.0(@types/node@25.3.5): + dependencies: + '@jest/core': 29.7.0 + '@jest/types': 29.6.3 + import-local: 3.2.0 + jest-cli: 29.7.0(@types/node@25.3.5) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - supports-color + - ts-node + + jiti@1.21.7: {} + + js-tokens@4.0.0: {} + + js-yaml@3.14.2: + dependencies: + argparse: 1.0.10 + esprima: 4.0.1 + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsdom@20.0.3: + dependencies: + abab: 2.0.6 + acorn: 8.16.0 + acorn-globals: 7.0.1 + cssom: 0.5.0 + cssstyle: 2.3.0 + data-urls: 3.0.2 + decimal.js: 10.6.0 + domexception: 4.0.0 + escodegen: 2.1.0 + form-data: 4.0.5 + html-encoding-sniffer: 3.0.0 + http-proxy-agent: 5.0.0 + https-proxy-agent: 5.0.1 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.23 + parse5: 7.3.0 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 4.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 2.0.0 + whatwg-mimetype: 3.0.0 + whatwg-url: 11.0.0 + ws: 8.19.0 + xml-name-validator: 4.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + + jsesc@3.1.0: {} + + json-parse-even-better-errors@2.3.1: {} + + json-schema-traverse@1.0.0: {} + + json5@2.2.3: {} + + kind-of@6.0.3: {} + + kleur@3.0.3: {} + + launch-editor@2.13.1: + dependencies: + picocolors: 1.1.1 + shell-quote: 1.8.3 + + leven@3.1.0: {} + + lilconfig@3.1.3: {} + + lines-and-columns@1.2.4: {} + + loader-runner@4.3.1: {} + + locate-path@5.0.0: + dependencies: + p-locate: 4.1.0 + + locate-path@7.2.0: + dependencies: + p-locate: 6.0.0 + + lodash.debounce@4.0.8: {} + + lodash.memoize@4.1.2: {} + + lodash@4.17.23: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lower-case@2.0.2: + dependencies: + tslib: 2.8.1 + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lucide-react@0.294.0(react@18.3.1): + dependencies: + react: 18.3.1 + + lz-string@1.5.0: {} + + make-dir@4.0.0: + dependencies: + semver: 7.7.4 + + make-error@1.3.6: {} + + makeerror@1.0.12: + dependencies: + tmpl: 1.0.5 + + math-intrinsics@1.1.0: {} + + media-typer@0.3.0: {} + + memfs@3.5.3: + dependencies: + fs-monkey: 1.1.0 + + merge-descriptors@1.0.3: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + methods@1.1.2: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mimic-fn@2.1.0: {} + + min-indent@1.0.1: {} + + minimalistic-assert@1.0.1: {} + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + + minimist@1.2.8: {} + + motion-dom@11.18.1: + dependencies: + motion-utils: 11.18.1 + + motion-utils@11.18.1: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + multicast-dns@7.2.5: + dependencies: + dns-packet: 5.6.1 + thunky: 1.1.0 + + mz@2.7.0: + dependencies: + any-promise: 1.3.0 + object-assign: 4.1.1 + thenify-all: 1.6.0 + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + negotiator@0.6.3: {} + + negotiator@0.6.4: {} + + neo-async@2.6.2: {} + + no-case@3.0.4: + dependencies: + lower-case: 2.0.2 + tslib: 2.8.1 + + node-forge@1.3.3: {} + + node-int64@0.4.0: {} + + node-releases@2.0.36: {} + + normalize-path@3.0.0: {} + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + nwsapi@2.2.23: {} + + object-assign@4.1.1: {} + + object-hash@3.0.0: {} + + object-inspect@1.13.4: {} + + object-is@1.1.6: + dependencies: + call-bind: 1.0.9 + define-properties: 1.2.1 + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.9 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + obuf@1.1.2: {} + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.1.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + open@8.4.2: + dependencies: + define-lazy-prop: 2.0.0 + is-docker: 2.2.1 + is-wsl: 2.2.0 + + p-limit@2.3.0: + dependencies: + p-try: 2.2.0 + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-limit@4.0.0: + dependencies: + yocto-queue: 1.2.2 + + p-locate@4.1.0: + dependencies: + p-limit: 2.3.0 + + p-locate@6.0.0: + dependencies: + p-limit: 4.0.0 + + p-retry@4.6.2: + dependencies: + '@types/retry': 0.12.0 + retry: 0.13.1 + + p-try@2.2.0: {} + + param-case@3.0.4: + dependencies: + dot-case: 3.0.4 + tslib: 2.8.1 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + parseurl@1.3.3: {} + + pascal-case@3.1.2: + dependencies: + no-case: 3.0.4 + tslib: 2.8.1 + + path-exists@4.0.0: {} + + path-exists@5.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-to-regexp@0.1.12: {} + + path-type@4.0.0: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pify@2.3.0: {} + + pirates@4.0.7: {} + + pkg-dir@4.2.0: + dependencies: + find-up: 4.1.0 + + pkg-dir@7.0.0: + dependencies: + find-up: 6.3.0 + + playwright-core@1.60.0: {} + + playwright@1.60.0: + dependencies: + playwright-core: 1.60.0 + optionalDependencies: + fsevents: 2.3.2 + + possible-typed-array-names@1.1.0: {} + + postcss-import@15.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + read-cache: 1.0.0 + resolve: 1.22.11 + + postcss-js@4.1.0(postcss@8.5.8): + dependencies: + camelcase-css: 2.0.1 + postcss: 8.5.8 + + postcss-load-config@6.0.1(jiti@1.21.7)(postcss@8.5.8): + dependencies: + lilconfig: 3.1.3 + optionalDependencies: + jiti: 1.21.7 + postcss: 8.5.8 + + postcss-loader@7.3.4(postcss@8.5.8)(typescript@5.9.3)(webpack@5.105.4): + dependencies: + cosmiconfig: 8.3.6(typescript@5.9.3) + jiti: 1.21.7 + postcss: 8.5.8 + semver: 7.7.4 + webpack: 5.105.4(webpack-cli@5.1.4) + transitivePeerDependencies: + - typescript + + postcss-modules-extract-imports@3.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + postcss-modules-local-by-default@4.2.0(postcss@8.5.8): + dependencies: + icss-utils: 5.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 + + postcss-modules-scope@3.2.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 7.1.1 + + postcss-modules-values@4.0.0(postcss@8.5.8): + dependencies: + icss-utils: 5.1.0(postcss@8.5.8) + postcss: 8.5.8 + + postcss-nested@6.2.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + pretty-error@4.0.0: + dependencies: + lodash: 4.17.23 + renderkid: 3.0.0 + + pretty-format@27.5.1: + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + + pretty-format@29.7.0: + dependencies: + '@jest/schemas': 29.6.3 + ansi-styles: 5.2.0 + react-is: 18.3.1 + + process-nextick-args@2.0.1: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + psl@1.15.0: + dependencies: + punycode: 2.3.1 + + punycode@2.3.1: {} + + pure-rand@6.1.0: {} + + qs@6.14.2: + dependencies: + side-channel: 1.1.0 + + querystringify@2.2.0: {} + + queue-microtask@1.2.3: {} + + range-parser@1.2.1: {} + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-error-boundary@3.1.4(react@18.3.1): + dependencies: + '@babel/runtime': 7.28.6 + react: 18.3.1 + + react-is@16.13.1: {} + + react-is@17.0.2: {} + + react-is@18.3.1: {} + + react-router-dom@6.30.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.30.3(react@18.3.1) + + react-router@6.30.3(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.2 + react: 18.3.1 + + react-smooth@4.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + fast-equals: 5.4.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + + react-transition-group@4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@babel/runtime': 7.28.6 + dom-helpers: 5.2.1 + loose-envify: 1.4.0 + prop-types: 15.8.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + read-cache@1.0.0: + dependencies: + pify: 2.3.0 + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + recharts-scale@0.4.5: + dependencies: + decimal.js-light: 2.5.1 + + recharts@2.15.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + clsx: 2.1.1 + eventemitter3: 4.0.7 + lodash: 4.17.23 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-is: 18.3.1 + react-smooth: 4.0.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + recharts-scale: 0.4.5 + tiny-invariant: 1.3.3 + victory-vendor: 36.9.2 + + rechoir@0.8.0: + dependencies: + resolve: 1.22.11 + + redent@3.0.0: + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + + regenerate-unicode-properties@10.2.2: + dependencies: + regenerate: 1.4.2 + + regenerate@1.4.2: {} + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.9 + 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 + + regexpu-core@6.4.0: + dependencies: + regenerate: 1.4.2 + regenerate-unicode-properties: 10.2.2 + regjsgen: 0.8.0 + regjsparser: 0.13.0 + unicode-match-property-ecmascript: 2.0.0 + unicode-match-property-value-ecmascript: 2.2.1 + + regjsgen@0.8.0: {} + + regjsparser@0.13.0: + dependencies: + jsesc: 3.1.0 + + relateurl@0.2.7: {} + + renderkid@3.0.0: + dependencies: + css-select: 4.3.0 + dom-converter: 0.2.0 + htmlparser2: 6.1.0 + lodash: 4.17.23 + strip-ansi: 6.0.1 + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + requires-port@1.0.0: {} + + resolve-cwd@3.0.0: + dependencies: + resolve-from: 5.0.0 + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve.exports@2.0.3: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + retry@0.13.1: {} + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + saxes@6.0.0: + dependencies: + xmlchars: 2.2.0 + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + schema-utils@4.3.3: + dependencies: + '@types/json-schema': 7.0.15 + ajv: 8.18.0 + ajv-formats: 2.1.1(ajv@8.18.0) + ajv-keywords: 5.1.0(ajv@8.18.0) + + select-hose@2.0.0: {} + + selfsigned@2.4.1: + dependencies: + '@types/node-forge': 1.3.14 + node-forge: 1.3.3 + + semver@6.3.1: {} + + semver@7.7.4: {} + + semver@7.8.1: {} + + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-index@1.9.2: + dependencies: + accepts: 1.3.8 + batch: 0.6.1 + debug: 2.6.9 + escape-html: 1.0.3 + http-errors: 1.8.1 + mime-types: 2.1.35 + parseurl: 1.3.3 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + setprototypeof@1.2.0: {} + + shallow-clone@3.0.1: + dependencies: + kind-of: 6.0.3 + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shell-quote@1.8.3: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + sisteransi@1.0.5: {} + + slash@3.0.0: {} + + sockjs@0.3.24: + dependencies: + faye-websocket: 0.11.4 + uuid: 8.3.2 + websocket-driver: 0.7.4 + + source-map-js@1.2.1: {} + + source-map-support@0.5.13: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + spdy-transport@3.0.0: + dependencies: + debug: 4.4.3 + detect-node: 2.1.0 + hpack.js: 2.1.6 + obuf: 1.1.2 + readable-stream: 3.6.2 + wbuf: 1.7.3 + transitivePeerDependencies: + - supports-color + + spdy@4.0.2: + dependencies: + debug: 4.4.3 + handle-thing: 2.0.1 + http-deceiver: 1.2.7 + select-hose: 2.0.0 + spdy-transport: 3.0.0 + transitivePeerDependencies: + - supports-color + + sprintf-js@1.0.3: {} + + stack-utils@2.0.6: + dependencies: + escape-string-regexp: 2.0.0 + + statuses@1.5.0: {} + + statuses@2.0.2: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + string-length@4.0.2: + dependencies: + char-regex: 1.0.2 + strip-ansi: 6.0.1 + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-bom@4.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-indent@3.0.0: + dependencies: + min-indent: 1.0.1 + + strip-json-comments@3.1.1: {} + + style-loader@3.3.4(webpack@5.105.4): + dependencies: + webpack: 5.105.4(webpack-cli@5.1.4) + + sucrase@3.35.1: + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + commander: 4.1.1 + lines-and-columns: 1.2.4 + mz: 2.7.0 + pirates: 4.0.7 + tinyglobby: 0.2.15 + ts-interface-checker: 0.1.13 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + symbol-tree@3.2.4: {} + + tailwindcss@3.4.19: + dependencies: + '@alloc/quick-lru': 5.2.0 + arg: 5.0.2 + chokidar: 3.6.0 + didyoumean: 1.2.2 + dlv: 1.1.3 + fast-glob: 3.3.3 + glob-parent: 6.0.2 + is-glob: 4.0.3 + jiti: 1.21.7 + lilconfig: 3.1.3 + micromatch: 4.0.8 + normalize-path: 3.0.0 + object-hash: 3.0.0 + picocolors: 1.1.1 + postcss: 8.5.8 + postcss-import: 15.1.0(postcss@8.5.8) + postcss-js: 4.1.0(postcss@8.5.8) + postcss-load-config: 6.0.1(jiti@1.21.7)(postcss@8.5.8) + postcss-nested: 6.2.0(postcss@8.5.8) + postcss-selector-parser: 6.1.2 + resolve: 1.22.11 + sucrase: 3.35.1 + transitivePeerDependencies: + - tsx + - yaml + + tapable@2.3.0: {} + + terser-webpack-plugin@5.3.17(webpack@5.105.4): + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + jest-worker: 27.5.1 + schema-utils: 4.3.3 + terser: 5.46.0 + webpack: 5.105.4(webpack-cli@5.1.4) + + terser@5.46.0: + dependencies: + '@jridgewell/source-map': 0.3.11 + acorn: 8.16.0 + commander: 2.20.3 + source-map-support: 0.5.21 + + test-exclude@6.0.0: + dependencies: + '@istanbuljs/schema': 0.1.6 + glob: 7.2.3 + minimatch: 3.1.5 + + thenify-all@1.6.0: + dependencies: + thenify: 3.3.1 + + thenify@3.3.1: + dependencies: + any-promise: 1.3.0 + + thunky@1.1.0: {} + + tiny-invariant@1.3.3: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tmpl@1.0.5: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + tough-cookie@4.1.4: + dependencies: + psl: 1.15.0 + punycode: 2.3.1 + universalify: 0.2.0 + url-parse: 1.5.10 + + tr46@3.0.0: + dependencies: + punycode: 2.3.1 + + ts-interface-checker@0.1.13: {} + + ts-jest@29.4.11(@babel/core@7.29.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.29.0))(jest-util@29.7.0)(jest@29.7.0(@types/node@25.3.5))(typescript@5.9.3): + dependencies: + bs-logger: 0.2.6 + fast-json-stable-stringify: 2.1.0 + handlebars: 4.7.9 + jest: 29.7.0(@types/node@25.3.5) + json5: 2.2.3 + lodash.memoize: 4.1.2 + make-error: 1.3.6 + semver: 7.8.1 + type-fest: 4.41.0 + typescript: 5.9.3 + yargs-parser: 21.1.1 + optionalDependencies: + '@babel/core': 7.29.0 + '@jest/transform': 29.7.0 + '@jest/types': 29.6.3 + babel-jest: 29.7.0(@babel/core@7.29.0) + jest-util: 29.7.0 + + tslib@2.8.1: {} + + type-detect@4.0.8: {} + + type-fest@0.21.3: {} + + type-fest@4.41.0: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typescript@5.9.3: {} + + uglify-js@3.19.3: + optional: true + + undici-types@7.18.2: {} + + unicode-canonical-property-names-ecmascript@2.0.1: {} + + unicode-match-property-ecmascript@2.0.0: + dependencies: + unicode-canonical-property-names-ecmascript: 2.0.1 + unicode-property-aliases-ecmascript: 2.2.0 + + unicode-match-property-value-ecmascript@2.2.1: {} + + unicode-property-aliases-ecmascript@2.2.0: {} + + universalify@0.2.0: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + url-parse@1.5.10: + dependencies: + querystringify: 2.2.0 + requires-port: 1.0.0 + + use-sync-external-store@1.6.0(react@18.3.1): + dependencies: + react: 18.3.1 + + util-deprecate@1.0.2: {} + + utila@0.4.0: {} + + utils-merge@1.0.1: {} + + uuid@8.3.2: {} + + v8-to-istanbul@9.3.0: + dependencies: + '@jridgewell/trace-mapping': 0.3.31 + '@types/istanbul-lib-coverage': 2.0.6 + convert-source-map: 2.0.0 + + vary@1.1.2: {} + + victory-vendor@36.9.2: + dependencies: + '@types/d3-array': 3.2.2 + '@types/d3-ease': 3.0.2 + '@types/d3-interpolate': 3.0.4 + '@types/d3-scale': 4.0.9 + '@types/d3-shape': 3.1.8 + '@types/d3-time': 3.0.4 + '@types/d3-timer': 3.0.2 + d3-array: 3.2.4 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-scale: 4.0.2 + d3-shape: 3.2.0 + d3-time: 3.1.0 + d3-timer: 3.0.1 + + w3c-xmlserializer@4.0.0: + dependencies: + xml-name-validator: 4.0.0 + + walker@1.0.8: + dependencies: + makeerror: 1.0.12 + + watchpack@2.5.1: + dependencies: + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + + wbuf@1.7.3: + dependencies: + minimalistic-assert: 1.0.1 + + webidl-conversions@7.0.0: {} + + webpack-cli@5.1.4(webpack-dev-server@4.15.2)(webpack@5.105.4): + dependencies: + '@discoveryjs/json-ext': 0.5.7 + '@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.105.4) + '@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.105.4) + '@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack-dev-server@4.15.2)(webpack@5.105.4) + colorette: 2.0.20 + commander: 10.0.1 + cross-spawn: 7.0.6 + envinfo: 7.21.0 + fastest-levenshtein: 1.0.16 + import-local: 3.2.0 + interpret: 3.1.1 + rechoir: 0.8.0 + webpack: 5.105.4(webpack-cli@5.1.4) + webpack-merge: 5.10.0 + optionalDependencies: + webpack-dev-server: 4.15.2(webpack-cli@5.1.4)(webpack@5.105.4) + + webpack-dev-middleware@5.3.4(webpack@5.105.4): + dependencies: + colorette: 2.0.20 + memfs: 3.5.3 + mime-types: 2.1.35 + range-parser: 1.2.1 + schema-utils: 4.3.3 + webpack: 5.105.4(webpack-cli@5.1.4) + + webpack-dev-server@4.15.2(webpack-cli@5.1.4)(webpack@5.105.4): + dependencies: + '@types/bonjour': 3.5.13 + '@types/connect-history-api-fallback': 1.5.4 + '@types/express': 4.17.25 + '@types/serve-index': 1.9.4 + '@types/serve-static': 1.15.10 + '@types/sockjs': 0.3.36 + '@types/ws': 8.18.1 + ansi-html-community: 0.0.8 + bonjour-service: 1.3.0 + chokidar: 3.6.0 + colorette: 2.0.20 + compression: 1.8.1 + connect-history-api-fallback: 2.0.0 + default-gateway: 6.0.3 + express: 4.22.1 + graceful-fs: 4.2.11 + html-entities: 2.6.0 + http-proxy-middleware: 2.0.9(@types/express@4.17.25) + ipaddr.js: 2.3.0 + launch-editor: 2.13.1 + open: 8.4.2 + p-retry: 4.6.2 + rimraf: 3.0.2 + schema-utils: 4.3.3 + selfsigned: 2.4.1 + serve-index: 1.9.2 + sockjs: 0.3.24 + spdy: 4.0.2 + webpack-dev-middleware: 5.3.4(webpack@5.105.4) + ws: 8.19.0 + optionalDependencies: + webpack: 5.105.4(webpack-cli@5.1.4) + webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.105.4) + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + + webpack-merge@5.10.0: + dependencies: + clone-deep: 4.0.1 + flat: 5.0.2 + wildcard: 2.0.1 + + webpack-sources@3.3.4: {} + + webpack@5.105.4(webpack-cli@5.1.4): + dependencies: + '@types/eslint-scope': 3.7.7 + '@types/estree': 1.0.8 + '@types/json-schema': 7.0.15 + '@webassemblyjs/ast': 1.14.1 + '@webassemblyjs/wasm-edit': 1.14.1 + '@webassemblyjs/wasm-parser': 1.14.1 + acorn: 8.16.0 + acorn-import-phases: 1.0.4(acorn@8.16.0) + browserslist: 4.28.1 + chrome-trace-event: 1.0.4 + enhanced-resolve: 5.20.0 + es-module-lexer: 2.0.0 + eslint-scope: 5.1.1 + events: 3.3.0 + glob-to-regexp: 0.4.1 + graceful-fs: 4.2.11 + json-parse-even-better-errors: 2.3.1 + loader-runner: 4.3.1 + mime-types: 2.1.35 + neo-async: 2.6.2 + schema-utils: 4.3.3 + tapable: 2.3.0 + terser-webpack-plugin: 5.3.17(webpack@5.105.4) + watchpack: 2.5.1 + webpack-sources: 3.3.4 + optionalDependencies: + webpack-cli: 5.1.4(webpack-dev-server@4.15.2)(webpack@5.105.4) + transitivePeerDependencies: + - '@swc/core' + - esbuild + - uglify-js + + websocket-driver@0.7.4: + dependencies: + http-parser-js: 0.5.10 + safe-buffer: 5.2.1 + websocket-extensions: 0.1.4 + + websocket-extensions@0.1.4: {} + + whatwg-encoding@2.0.0: + dependencies: + iconv-lite: 0.6.3 + + whatwg-mimetype@3.0.0: {} + + whatwg-url@11.0.0: + dependencies: + tr46: 3.0.0 + webidl-conversions: 7.0.0 + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.9 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + wildcard@2.0.1: {} + + wordwrap@1.0.0: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + write-file-atomic@4.0.2: + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + + ws@8.19.0: {} + + xml-name-validator@4.0.0: {} + + xmlchars@2.2.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@0.1.0: {} + + yocto-queue@1.2.2: {} + + zustand@4.5.7(@types/react@18.3.28)(immer@10.2.0)(react@18.3.1): + dependencies: + use-sync-external-store: 1.6.0(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.28 + immer: 10.2.0 + react: 18.3.1 diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..5cbc2c7 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {} + } +}; diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..ef34f3e --- /dev/null +++ b/public/index.html @@ -0,0 +1,39 @@ +织机 - 以AI为梭,织天地经纬
\ No newline at end of file diff --git a/skills/excalidraw-diagram-generator/.skill-version b/skills/excalidraw-diagram-generator/.skill-version new file mode 100644 index 0000000..56a6051 --- /dev/null +++ b/skills/excalidraw-diagram-generator/.skill-version @@ -0,0 +1 @@ +1 \ No newline at end of file diff --git a/skills/excalidraw-diagram-generator/SKILL.md b/skills/excalidraw-diagram-generator/SKILL.md new file mode 100644 index 0000000..e33fd90 --- /dev/null +++ b/skills/excalidraw-diagram-generator/SKILL.md @@ -0,0 +1,613 @@ +--- +name: excalidraw-diagram-generator +description: 'Generate Excalidraw diagrams from natural language descriptions. Use when asked to "create a diagram", "make a flowchart", "visualize a process", "draw a system architecture", "create a mind map", or "generate an Excalidraw file". Supports flowcharts, relationship diagrams, mind maps, and system architecture diagrams. Outputs .excalidraw JSON files that can be opened directly in Excalidraw.' +--- + +# Excalidraw Diagram Generator + +A skill for generating Excalidraw-format diagrams from natural language descriptions. This skill helps create visual representations of processes, systems, relationships, and ideas without manual drawing. + +## When to Use This Skill + +Use this skill when users request: + +- "Create a diagram showing..." +- "Make a flowchart for..." +- "Visualize the process of..." +- "Draw the system architecture of..." +- "Generate a mind map about..." +- "Create an Excalidraw file for..." +- "Show the relationship between..." +- "Diagram the workflow of..." + +**Supported diagram types:** +- 📊 **Flowcharts**: Sequential processes, workflows, decision trees +- 🔗 **Relationship Diagrams**: Entity relationships, system components, dependencies +- 🧠 **Mind Maps**: Concept hierarchies, brainstorming results, topic organization +- 🏗️ **Architecture Diagrams**: System design, module interactions, data flow +- 📈 **Data Flow Diagrams (DFD)**: Data flow visualization, data transformation processes +- 🏊 **Business Flow (Swimlane)**: Cross-functional workflows, actor-based process flows +- 📦 **Class Diagrams**: Object-oriented design, class structures and relationships +- 🔄 **Sequence Diagrams**: Object interactions over time, message flows +- 🗃️ **ER Diagrams**: Database entity relationships, data models + +## Prerequisites + +- Clear description of what should be visualized +- Identification of key entities, steps, or concepts +- Understanding of relationships or flow between elements + +## Step-by-Step Workflow + +### Step 1: Understand the Request + +Analyze the user's description to determine: +1. **Diagram type** (flowchart, relationship, mind map, architecture) +2. **Key elements** (entities, steps, concepts) +3. **Relationships** (flow, connections, hierarchy) +4. **Complexity** (number of elements) + +### Step 2: Choose the Appropriate Diagram Type + +| User Intent | Diagram Type | Example Keywords | +|-------------|--------------|------------------| +| Process flow, steps, procedures | **Flowchart** | "workflow", "process", "steps", "procedure" | +| Connections, dependencies, associations | **Relationship Diagram** | "relationship", "connections", "dependencies", "structure" | +| Concept hierarchy, brainstorming | **Mind Map** | "mind map", "concepts", "ideas", "breakdown" | +| System design, components | **Architecture Diagram** | "architecture", "system", "components", "modules" | +| Data flow, transformation processes | **Data Flow Diagram (DFD)** | "data flow", "data processing", "data transformation" | +| Cross-functional processes, actor responsibilities | **Business Flow (Swimlane)** | "business process", "swimlane", "actors", "responsibilities" | +| Object-oriented design, class structures | **Class Diagram** | "class", "inheritance", "OOP", "object model" | +| Interaction sequences, message flows | **Sequence Diagram** | "sequence", "interaction", "messages", "timeline" | +| Database design, entity relationships | **ER Diagram** | "database", "entity", "relationship", "data model" | + +### Step 3: Extract Structured Information + +**For Flowcharts:** +- List of sequential steps +- Decision points (if any) +- Start and end points + +**For Relationship Diagrams:** +- Entities/nodes (name + optional description) +- Relationships between entities (from → to, with label) + +**For Mind Maps:** +- Central topic +- Main branches (3-6 recommended) +- Sub-topics for each branch (optional) + +**For Data Flow Diagrams (DFD):** +- Data sources and destinations (external entities) +- Processes (data transformations) +- Data stores (databases, files) +- Data flows (arrows showing data movement from left-to-right or from top-left to bottom-right) +- **Important**: Do not represent process order, only data flow + +**For Business Flow (Swimlane):** +- Actors/roles (departments, systems, people) - displayed as header columns +- Process lanes (vertical lanes under each actor) +- Process boxes (activities within each lane) +- Flow arrows (connecting process boxes, including cross-lane handoffs) + +**For Class Diagrams:** +- Classes with names +- Attributes with visibility (+, -, #) +- Methods with visibility and parameters +- Relationships: inheritance (solid line + white triangle), implementation (dashed line + white triangle), association (solid line), dependency (dashed line), aggregation (solid line + white diamond), composition (solid line + filled diamond) +- Multiplicity notations (1, 0..1, 1..*, *) + +**For Sequence Diagrams:** +- Objects/actors (arranged horizontally at top) +- Lifelines (vertical lines from each object) +- Messages (horizontal arrows between lifelines) +- Synchronous messages (solid arrow), asynchronous messages (dashed arrow) +- Return values (dashed arrows) +- Activation boxes (rectangles on lifelines during execution) +- Time flows from top to bottom + +**For ER Diagrams:** +- Entities (rectangles with entity names) +- Attributes (listed inside entities) +- Primary keys (underlined or marked with PK) +- Foreign keys (marked with FK) +- Relationships (lines connecting entities) +- Cardinality: 1:1 (one-to-one), 1:N (one-to-many), N:M (many-to-many) +- Junction/associative entities for many-to-many relationships (dashed rectangles) + +### Step 4: Generate the Excalidraw JSON + +Create the `.excalidraw` file with appropriate elements: + +**Available element types:** +- `rectangle`: Boxes for entities, steps, concepts +- `ellipse`: Alternative shapes for emphasis +- `diamond`: Decision points +- `arrow`: Directional connections +- `text`: Labels and annotations + +**Key properties to set:** +- **Position**: `x`, `y` coordinates +- **Size**: `width`, `height` +- **Style**: `strokeColor`, `backgroundColor`, `fillStyle` +- **Font**: `fontFamily: 5` (Excalifont - **required for all text elements**) +- **Text**: Embedded text for labels +- **Connections**: `points` array for arrows + +**Important**: All text elements must use `fontFamily: 5` (Excalifont) for consistent visual appearance. + +### Step 5: Format the Output + +Structure the complete Excalidraw file: + +```json +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + // Array of diagram elements + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} +``` + +### Step 6: Save and Provide Instructions + +1. Save as `.excalidraw` +2. Inform user how to open: + - Visit https://excalidraw.com + - Click "Open" or drag-and-drop the file + - Or use Excalidraw VS Code extension + +## Best Practices + +### Element Count Guidelines + +| Diagram Type | Recommended Count | Maximum | +|--------------|-------------------|---------| +| Flowchart steps | 3-10 | 15 | +| Relationship entities | 3-8 | 12 | +| Mind map branches | 4-6 | 8 | +| Mind map sub-topics per branch | 2-4 | 6 | + +### Layout Tips + +1. **Start positions**: Center important elements, use consistent spacing +2. **Spacing**: + - Horizontal gap: 200-300px between elements + - Vertical gap: 100-150px between rows +3. **Colors**: Use consistent color scheme + - Primary elements: Light blue (`#a5d8ff`) + - Secondary elements: Light green (`#b2f2bb`) + - Important/Central: Yellow (`#ffd43b`) + - Alerts/Warnings: Light red (`#ffc9c9`) +4. **Text sizing**: 16-24px for readability +5. **Font**: Always use `fontFamily: 5` (Excalifont) for all text elements +6. **Arrow style**: Use straight arrows for simple flows, curved for complex relationships + +### Complexity Management + +**If user request has too many elements:** +- Suggest breaking into multiple diagrams +- Focus on main elements first +- Offer to create detailed sub-diagrams + +**Example response:** +``` +"Your request includes 15 components. For clarity, I recommend: +1. High-level architecture diagram (6 main components) +2. Detailed diagram for each subsystem + +Would you like me to start with the high-level view?" +``` + +## Example Prompts and Responses + +### Example 1: Simple Flowchart + +**User:** "Create a flowchart for user registration" + +**Agent generates:** +1. Extract steps: "Enter email" → "Verify email" → "Set password" → "Complete" +2. Create flowchart with 4 rectangles + 3 arrows +3. Save as `user-registration-flow.excalidraw` + +### Example 2: Relationship Diagram + +**User:** "Diagram the relationship between User, Post, and Comment entities" + +**Agent generates:** +1. Entities: User, Post, Comment +2. Relationships: User → Post ("creates"), User → Comment ("writes"), Post → Comment ("contains") +3. Save as `user-content-relationships.excalidraw` + +### Example 3: Mind Map + +**User:** "Mind map about machine learning concepts" + +**Agent generates:** +1. Center: "Machine Learning" +2. Branches: Supervised Learning, Unsupervised Learning, Reinforcement Learning, Deep Learning +3. Sub-topics under each branch +4. Save as `machine-learning-mindmap.excalidraw` + +## Troubleshooting + +| Issue | Solution | +|-------|----------| +| Elements overlap | Increase spacing between coordinates | +| Text doesn't fit in boxes | Increase box width or reduce font size | +| Too many elements | Break into multiple diagrams | +| Unclear layout | Use grid layout (rows/columns) or radial layout (mind maps) | +| Colors inconsistent | Define color palette upfront based on element types | + +## Advanced Techniques + +### Grid Layout (for Relationship Diagrams) +```javascript +const columns = Math.ceil(Math.sqrt(entityCount)); +const x = startX + (index % columns) * horizontalGap; +const y = startY + Math.floor(index / columns) * verticalGap; +``` + +### Radial Layout (for Mind Maps) +```javascript +const angle = (2 * Math.PI * index) / branchCount; +const x = centerX + radius * Math.cos(angle); +const y = centerY + radius * Math.sin(angle); +``` + +### Auto-generated IDs +Use timestamp + random string for unique IDs: +```javascript +const id = Date.now().toString(36) + Math.random().toString(36).substr(2); +``` + +## Output Format + +Always provide: +1. ✅ Complete `.excalidraw` JSON file +2. 📊 Summary of what was created +3. 📝 Element count +4. 💡 Instructions for opening/editing + +**Example summary:** +``` +Created: user-workflow.excalidraw +Type: Flowchart +Elements: 7 rectangles, 6 arrows, 1 title text +Total: 14 elements + +To view: +1. Visit https://excalidraw.com +2. Drag and drop user-workflow.excalidraw +3. Or use File → Open in Excalidraw VS Code extension +``` + +## Validation Checklist + +Before delivering the diagram: +- [ ] All elements have unique IDs +- [ ] Coordinates prevent overlapping +- [ ] Text is readable (font size 16+) +- [ ] **All text elements use `fontFamily: 5` (Excalifont)** +- [ ] Arrows connect logically +- [ ] Colors follow consistent scheme +- [ ] File is valid JSON +- [ ] Element count is reasonable (<20 for clarity) + +## Icon Libraries (Optional Enhancement) + +For specialized diagrams (e.g., AWS/GCP/Azure architecture diagrams), you can use pre-made icon libraries from Excalidraw. This provides professional, standardized icons instead of basic shapes. + +### When User Requests Icons + +**If user asks for AWS/cloud architecture diagrams or mentions wanting to use specific icons:** + +1. **Check if library exists**: Look for `libraries//reference.md` +2. **If library exists**: Proceed to use icons (see AI Assistant Workflow below) +3. **If library does NOT exist**: Respond with setup instructions: + + ``` + To use [AWS/GCP/Azure/etc.] architecture icons, please follow these steps: + + 1. Visit https://libraries.excalidraw.com/ + 2. Search for "[AWS Architecture Icons/etc.]" and download the .excalidrawlib file + 3. Create directory: skills/excalidraw-diagram-generator/libraries/[icon-set-name]/ + 4. Place the downloaded file in that directory + 5. Run the splitter script: + python skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/[icon-set-name]/ + + This will split the library into individual icon files for efficient use. + After setup is complete, I can create your diagram using the actual AWS/cloud icons. + + Alternatively, I can create the diagram now using simple shapes (rectangles, ellipses) + which you can later replace with icons manually in Excalidraw. + ``` + +### User Setup Instructions (Detailed) + +**Step 1: Create Library Directory** +```bash +mkdir -p skills/excalidraw-diagram-generator/libraries/aws-architecture-icons +``` + +**Step 2: Download Library** +- Visit: https://libraries.excalidraw.com/ +- Search for your desired icon set (e.g., "AWS Architecture Icons") +- Click download to get the `.excalidrawlib` file +- Example categories (availability varies; confirm on the site): + - Cloud service icons + - UI/Material icons + - Flowchart symbols + +**Step 3: Place Library File** +- Rename the downloaded file to match the directory name (e.g., `aws-architecture-icons.excalidrawlib`) +- Move it to the directory created in Step 1 + +**Step 4: Run Splitter Script** +```bash +python skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/ +``` + +**Step 5: Verify Setup** +After running the script, verify the following structure exists: +``` +skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/ + aws-architecture-icons.excalidrawlib (original) + reference.md (generated - icon lookup table) + icons/ (generated - individual icon files) + API-Gateway.json + CloudFront.json + EC2.json + Lambda.json + RDS.json + S3.json + ... +``` + +### AI Assistant Workflow + +**When icon libraries are available in `libraries/`:** + +**RECOMMENDED APPROACH: Use Python Scripts (Efficient & Reliable)** + +The repository includes Python scripts that handle icon integration automatically: + +1. **Create base diagram structure**: + - Create `.excalidraw` file with basic layout (title, boxes, regions) + - This establishes the canvas and overall structure + +2. **Add icons using Python script**: + ```bash + python skills/excalidraw-diagram-generator/scripts/add-icon-to-diagram.py \ + [--label "Text"] [--library-path PATH] + ``` + - Edit via `.excalidraw.edit` is enabled by default to avoid overwrite issues; pass `--no-use-edit-suffix` to disable. + + **Examples**: + ```bash + # Add EC2 icon at position (400, 300) with label + python scripts/add-icon-to-diagram.py diagram.excalidraw EC2 400 300 --label "Web Server" + + # Add VPC icon at position (200, 150) + python scripts/add-icon-to-diagram.py diagram.excalidraw VPC 200 150 + + # Add icon from different library + python scripts/add-icon-to-diagram.py diagram.excalidraw Compute-Engine 500 200 \ + --library-path libraries/gcp-icons --label "API Server" + ``` + +3. **Add connecting arrows**: + ```bash + python skills/excalidraw-diagram-generator/scripts/add-arrow.py \ + [--label "Text"] [--style solid|dashed|dotted] [--color HEX] + ``` + - Edit via `.excalidraw.edit` is enabled by default to avoid overwrite issues; pass `--no-use-edit-suffix` to disable. + + **Examples**: + ```bash + # Simple arrow from (300, 250) to (500, 300) + python scripts/add-arrow.py diagram.excalidraw 300 250 500 300 + + # Arrow with label + python scripts/add-arrow.py diagram.excalidraw 300 250 500 300 --label "HTTPS" + + # Dashed arrow with custom color + python scripts/add-arrow.py diagram.excalidraw 400 350 600 400 --style dashed --color "#7950f2" + ``` + +4. **Workflow summary**: + ```bash + # Step 1: Create base diagram with title and structure + # (Create .excalidraw file with initial elements) + + # Step 2: Add icons with labels + python scripts/add-icon-to-diagram.py my-diagram.excalidraw "Internet-gateway" 200 150 --label "Internet Gateway" + python scripts/add-icon-to-diagram.py my-diagram.excalidraw VPC 250 250 + python scripts/add-icon-to-diagram.py my-diagram.excalidraw ELB 350 300 --label "Load Balancer" + python scripts/add-icon-to-diagram.py my-diagram.excalidraw EC2 450 350 --label "EC2 Instance" + python scripts/add-icon-to-diagram.py my-diagram.excalidraw RDS 550 400 --label "Database" + + # Step 3: Add connecting arrows + python scripts/add-arrow.py my-diagram.excalidraw 250 200 300 250 # Internet → VPC + python scripts/add-arrow.py my-diagram.excalidraw 300 300 400 300 # VPC → ELB + python scripts/add-arrow.py my-diagram.excalidraw 400 330 500 350 # ELB → EC2 + python scripts/add-arrow.py my-diagram.excalidraw 500 380 600 400 # EC2 → RDS + ``` + +**Benefits of Python Script Approach**: +- ✅ **No token consumption**: Icon JSON data (200-1000 lines each) never enters AI context +- ✅ **Accurate transformations**: Coordinate calculations handled deterministically +- ✅ **ID management**: Automatic UUID generation prevents conflicts +- ✅ **Reliable**: No risk of coordinate miscalculation or ID collision +- ✅ **Fast**: Direct file manipulation, no parsing overhead +- ✅ **Reusable**: Works with any Excalidraw library you provide + +**ALTERNATIVE: Manual Icon Integration (Not Recommended)** + +Only use this if Python scripts are unavailable: + +1. **Check for libraries**: + ``` + List directory: skills/excalidraw-diagram-generator/libraries/ + Look for subdirectories containing reference.md files + ``` + +2. **Read reference.md**: + ``` + Open: libraries//reference.md + This is lightweight (typically <300 lines) and lists all available icons + ``` + +3. **Find relevant icons**: + ``` + Search the reference.md table for icon names matching diagram needs + Example: For AWS diagram with EC2, S3, Lambda → Find "EC2", "S3", "Lambda" in table + ``` + +4. **Load specific icon data** (WARNING: Large files): + ``` + Read ONLY the needed icon files: + - libraries/aws-architecture-icons/icons/EC2.json (200-300 lines) + - libraries/aws-architecture-icons/icons/S3.json (200-300 lines) + - libraries/aws-architecture-icons/icons/Lambda.json (200-300 lines) + Note: Each icon file is 200-1000 lines - this consumes significant tokens + ``` + +5. **Extract and transform elements**: + ``` + Each icon JSON contains an "elements" array + Calculate bounding box (min_x, min_y, max_x, max_y) + Apply offset to all x/y coordinates + Generate new unique IDs for all elements + Update groupIds references + Copy transformed elements into your diagram + ``` + +6. **Position icons and add connections**: + ``` + Adjust x/y coordinates to position icons correctly in the diagram + Update IDs to ensure uniqueness across diagram + Add connecting arrows and labels as needed + ``` + +**Manual Integration Challenges**: +- ⚠️ High token consumption (200-1000 lines per icon × number of icons) +- ⚠️ Complex coordinate transformation calculations +- ⚠️ Risk of ID collision if not handled carefully +- ⚠️ Time-consuming for diagrams with many icons + +### Example: Creating AWS Diagram with Icons + +**Request**: "Create an AWS architecture diagram with Internet Gateway, VPC, ELB, EC2, and RDS" + +**Recommended Workflow (using Python scripts)**: +**Request**: "Create an AWS architecture diagram with Internet Gateway, VPC, ELB, EC2, and RDS" + +**Recommended Workflow (using Python scripts)**: + +```bash +# Step 1: Create base diagram file with title +# Create my-aws-diagram.excalidraw with basic structure (title, etc.) + +# Step 2: Check icon availability +# Read: libraries/aws-architecture-icons/reference.md +# Confirm icons exist: Internet-gateway, VPC, ELB, EC2, RDS + +# Step 3: Add icons with Python script +python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw "Internet-gateway" 150 100 --label "Internet Gateway" +python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw VPC 200 200 +python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw ELB 350 250 --label "Load Balancer" +python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw EC2 500 300 --label "Web Server" +python scripts/add-icon-to-diagram.py my-aws-diagram.excalidraw RDS 650 350 --label "Database" + +# Step 4: Add connecting arrows +python scripts/add-arrow.py my-aws-diagram.excalidraw 200 150 250 200 # Internet → VPC +python scripts/add-arrow.py my-aws-diagram.excalidraw 265 230 350 250 # VPC → ELB +python scripts/add-arrow.py my-aws-diagram.excalidraw 415 280 500 300 # ELB → EC2 +python scripts/add-arrow.py my-aws-diagram.excalidraw 565 330 650 350 --label "SQL" --style dashed + +# Result: Complete diagram with professional AWS icons, labels, and connections +``` + +**Benefits**: +- No manual coordinate calculation +- No token consumption for icon data +- Deterministic, reliable results +- Easy to iterate and adjust positions + +**Alternative Workflow (manual, if scripts unavailable)**: +1. Check: `libraries/aws-architecture-icons/reference.md` exists → Yes +2. Read reference.md → Find entries for Internet-gateway, VPC, ELB, EC2, RDS +3. Load: + - `icons/Internet-gateway.json` (298 lines) + - `icons/VPC.json` (550 lines) + - `icons/ELB.json` (363 lines) + - `icons/EC2.json` (231 lines) + - `icons/RDS.json` (similar size) + **Total: ~2000+ lines of JSON to process** +4. Extract elements from each JSON +5. Calculate bounding boxes and offsets for each icon +6. Transform all coordinates (x, y) for positioning +7. Generate unique IDs for all elements +8. Add arrows showing data flow +9. Add text labels +10. Generate final `.excalidraw` file + +**Challenges with manual approach**: +- High token consumption (~2000-5000 lines) +- Complex coordinate math +- Risk of ID conflicts + +### Supported Icon Libraries (Examples — verify availability) + +- This workflow works with any valid `.excalidrawlib` file you provide. +- Examples of library categories you may find on https://libraries.excalidraw.com/: + - Cloud service icons + - Kubernetes / infrastructure icons + - UI / Material icons + - Flowchart / diagram symbols + - Network diagram icons +- Availability and naming can change; verify exact library names on the site before use. + +### Fallback: No Icons Available + +**If no icon libraries are set up:** +- Create diagrams using basic shapes (rectangles, ellipses, arrows) +- Use color coding and text labels to distinguish components +- Inform user they can add icons later or set up libraries for future diagrams +- The diagram will still be functional and clear, just less visually polished + +## References + +See bundled references for: +- `references/excalidraw-schema.md` - Complete Excalidraw JSON schema +- `references/element-types.md` - Detailed element type specifications +- `templates/flowchart-template.json` - Basic flowchart starter +- `templates/relationship-template.json` - Relationship diagram starter +- `templates/mindmap-template.json` - Mind map starter +- `scripts/split-excalidraw-library.py` - Tool to split `.excalidrawlib` files +- `scripts/README.md` - Documentation for library tools +- `scripts/.gitignore` - Prevents local Python artifacts from being committed + +## Limitations + +- Complex curves are simplified to straight/basic curved lines +- Hand-drawn roughness is set to default (1) +- No embedded images support in auto-generation +- Maximum recommended elements: 20 per diagram +- No automatic collision detection (use spacing guidelines) + +## Future Enhancements + +Potential improvements: +- Auto-layout optimization algorithms +- Import from Mermaid/PlantUML syntax +- Template library expansion +- Interactive editing after generation diff --git a/skills/excalidraw-diagram-generator/references/element-types.md b/skills/excalidraw-diagram-generator/references/element-types.md new file mode 100644 index 0000000..3d85f8b --- /dev/null +++ b/skills/excalidraw-diagram-generator/references/element-types.md @@ -0,0 +1,497 @@ +# Excalidraw Element Types Guide + +Detailed specifications for each Excalidraw element type with visual examples and use cases. + +## Element Type Overview + +| Type | Visual | Primary Use | Text Support | +|------|--------|-------------|--------------| +| `rectangle` | □ | Boxes, containers, process steps | ✅ Yes | +| `ellipse` | ○ | Emphasis, terminals, states | ✅ Yes | +| `diamond` | ◇ | Decision points, choices | ✅ Yes | +| `arrow` | → | Directional flow, relationships | ❌ No (use separate text) | +| `line` | — | Connections, dividers | ❌ No | +| `text` | A | Labels, annotations, titles | ✅ (Its purpose) | + +--- + +## Rectangle + +**Best for:** Process steps, entities, data stores, components + +### Properties + +```typescript +{ + type: "rectangle", + roundness: { type: 3 }, // Rounded corners + text: "Step Name", // Optional embedded text + fontSize: 20, + textAlign: "center", + verticalAlign: "middle" +} +``` + +### Use Cases + +| Scenario | Configuration | +|----------|---------------| +| **Process step** | Green background (`#b2f2bb`), centered text | +| **Entity/Object** | Blue background (`#a5d8ff`), medium size | +| **System component** | Light color, descriptive text | +| **Data store** | Gray/white, database-like label | + +### Size Guidelines + +| Content | Width | Height | +|---------|-------|--------| +| Single word | 120-150px | 60-80px | +| Short phrase (2-4 words) | 180-220px | 80-100px | +| Sentence | 250-300px | 100-120px | + +### Example + +```json +{ + "type": "rectangle", + "x": 100, + "y": 100, + "width": 200, + "height": 80, + "backgroundColor": "#b2f2bb", + "text": "Validate Input", + "fontSize": 20, + "textAlign": "center", + "verticalAlign": "middle", + "roundness": { "type": 3 } +} +``` + +--- + +## Ellipse + +**Best for:** Start/end points, states, emphasis circles + +### Properties + +```typescript +{ + type: "ellipse", + text: "Start", + fontSize: 18, + textAlign: "center", + verticalAlign: "middle" +} +``` + +### Use Cases + +| Scenario | Configuration | +|----------|---------------| +| **Flow start** | Light green, "Start" text | +| **Flow end** | Light red, "End" text | +| **State** | Soft color, state name | +| **Highlight** | Bright color, emphasis text | + +### Size Guidelines + +For circular shapes, use `width === height`: + +| Content | Diameter | +|---------|----------| +| Icon/Symbol | 60-80px | +| Short text | 100-120px | +| Longer text | 150-180px | + +### Example + +```json +{ + "type": "ellipse", + "x": 100, + "y": 100, + "width": 120, + "height": 120, + "backgroundColor": "#d0f0c0", + "text": "Start", + "fontSize": 18, + "textAlign": "center", + "verticalAlign": "middle" +} +``` + +--- + +## Diamond + +**Best for:** Decision points, conditional branches + +### Properties + +```typescript +{ + type: "diamond", + text: "Valid?", + fontSize: 18, + textAlign: "center", + verticalAlign": "middle" +} +``` + +### Use Cases + +| Scenario | Text Example | +|----------|--------------| +| **Yes/No decision** | "Is Valid?", "Exists?" | +| **Multiple choice** | "Type?", "Status?" | +| **Conditional** | "Score > 50?" | + +### Size Guidelines + +Diamonds need more space than rectangles for the same text: + +| Content | Width | Height | +|---------|-------|--------| +| Yes/No | 120-140px | 120-140px | +| Short question | 160-180px | 160-180px | +| Longer question | 200-220px | 200-220px | + +### Example + +```json +{ + "type": "diamond", + "x": 100, + "y": 100, + "width": 150, + "height": 150, + "backgroundColor": "#ffe4a3", + "text": "Valid?", + "fontSize": 18, + "textAlign": "center", + "verticalAlign": "middle" +} +``` + +--- + +## Arrow + +**Best for:** Flow direction, relationships, dependencies + +### Properties + +```typescript +{ + type: "arrow", + points: [[0, 0], [endX, endY]], // Relative coordinates + roundness: { type: 2 }, // Curved + startBinding: null, // Or { elementId, focus, gap } + endBinding: null +} +``` + +### Arrow Directions + +#### Horizontal (Left to Right) + +```json +{ + "x": 100, + "y": 150, + "width": 200, + "height": 0, + "points": [[0, 0], [200, 0]] +} +``` + +#### Vertical (Top to Bottom) + +```json +{ + "x": 200, + "y": 100, + "width": 0, + "height": 150, + "points": [[0, 0], [0, 150]] +} +``` + +#### Diagonal + +```json +{ + "x": 100, + "y": 100, + "width": 200, + "height": 150, + "points": [[0, 0], [200, 150]] +} +``` + +### Arrow Styles + +| Style | `strokeStyle` | `strokeWidth` | Use Case | +|-------|---------------|---------------|----------| +| **Normal flow** | `"solid"` | 2 | Standard connections | +| **Optional/Weak** | `"dashed"` | 2 | Optional paths | +| **Important** | `"solid"` | 3-4 | Emphasized flow | +| **Dotted** | `"dotted"` | 2 | Indirect relationships | + +### Adding Arrow Labels + +Use separate text elements positioned near arrow midpoint: + +```json +[ + { + "type": "arrow", + "id": "arrow1", + "x": 100, + "y": 150, + "points": [[0, 0], [200, 0]] + }, + { + "type": "text", + "x": 180, // Near midpoint + "y": 130, // Above arrow + "text": "sends", + "fontSize": 14 + } +] +``` + +--- + +## Line + +**Best for:** Non-directional connections, dividers, borders + +### Properties + +```typescript +{ + type: "line", + points: [[0, 0], [x2, y2], [x3, y3], ...], + roundness: null // Or { type: 2 } for curved +} +``` + +### Use Cases + +| Scenario | Configuration | +|----------|---------------| +| **Divider** | Horizontal, thin stroke | +| **Border** | Closed path (polygon) | +| **Connection** | Multi-point path | +| **Underline** | Short horizontal line | + +### Multi-Point Line Example + +```json +{ + "type": "line", + "x": 100, + "y": 100, + "points": [ + [0, 0], + [100, 50], + [200, 0] + ] +} +``` + +--- + +## Text + +**Best for:** Labels, titles, annotations, standalone text + +### Properties + +```typescript +{ + type: "text", + text: "Label text", + fontSize: 20, + fontFamily: 1, // 1=Virgil, 2=Helvetica, 3=Cascadia + textAlign: "left", + verticalAlign: "top" +} +``` + +### Font Sizes by Purpose + +| Purpose | Font Size | +|---------|-----------| +| **Main title** | 28-36 | +| **Section header** | 24-28 | +| **Element label** | 18-22 | +| **Annotation** | 14-16 | +| **Small note** | 12-14 | + +### Width/Height Calculation + +```javascript +// Approximate width +const width = text.length * fontSize * 0.6; + +// Approximate height (single line) +const height = fontSize * 1.2; + +// Multi-line +const lines = text.split('\n').length; +const height = fontSize * 1.2 * lines; +``` + +### Text Positioning + +| Position | textAlign | verticalAlign | Use Case | +|----------|-----------|---------------|----------| +| **Top-left** | `"left"` | `"top"` | Default labels | +| **Centered** | `"center"` | `"middle"` | Titles | +| **Bottom-right** | `"right"` | `"bottom"` | Footnotes | + +### Example: Title + +```json +{ + "type": "text", + "x": 100, + "y": 50, + "width": 400, + "height": 40, + "text": "System Architecture", + "fontSize": 32, + "fontFamily": 2, + "textAlign": "center", + "verticalAlign": "top" +} +``` + +### Example: Annotation + +```json +{ + "type": "text", + "x": 150, + "y": 200, + "width": 100, + "height": 20, + "text": "User input", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" +} +``` + +--- + +## Combining Elements + +### Pattern: Labeled Box + +```json +[ + { + "type": "rectangle", + "id": "box1", + "x": 100, + "y": 100, + "width": 200, + "height": 100, + "text": "Component", + "textAlign": "center", + "verticalAlign": "middle" + } +] +``` + +### Pattern: Connected Boxes + +```json +[ + { + "type": "rectangle", + "id": "box1", + "x": 100, + "y": 100, + "width": 150, + "height": 80, + "text": "Step 1" + }, + { + "type": "arrow", + "id": "arrow1", + "x": 250, + "y": 140, + "points": [[0, 0], [100, 0]] + }, + { + "type": "rectangle", + "id": "box2", + "x": 350, + "y": 100, + "width": 150, + "height": 80, + "text": "Step 2" + } +] +``` + +### Pattern: Decision Tree + +```json +[ + { + "type": "diamond", + "id": "decision", + "x": 100, + "y": 100, + "width": 140, + "height": 140, + "text": "Valid?" + }, + { + "type": "arrow", + "id": "yes-arrow", + "x": 240, + "y": 170, + "points": [[0, 0], [60, 0]] + }, + { + "type": "text", + "id": "yes-label", + "x": 250, + "y": 150, + "text": "Yes", + "fontSize": 14 + }, + { + "type": "rectangle", + "id": "yes-box", + "x": 300, + "y": 140, + "width": 120, + "height": 60, + "text": "Process" + } +] +``` + +--- + +## Summary + +| When you need... | Use this element | +|------------------|------------------| +| Process box | `rectangle` with text | +| Decision point | `diamond` with question | +| Flow direction | `arrow` | +| Start/End | `ellipse` | +| Title/Header | `text` (large font) | +| Annotation | `text` (small font) | +| Non-directional link | `line` | +| Divider | `line` (horizontal) | diff --git a/skills/excalidraw-diagram-generator/references/excalidraw-schema.md b/skills/excalidraw-diagram-generator/references/excalidraw-schema.md new file mode 100644 index 0000000..bfdac6c --- /dev/null +++ b/skills/excalidraw-diagram-generator/references/excalidraw-schema.md @@ -0,0 +1,350 @@ +# Excalidraw JSON Schema Reference + +This document describes the structure of Excalidraw `.excalidraw` files for diagram generation. + +## Top-Level Structure + +```typescript +interface ExcalidrawFile { + type: "excalidraw"; + version: number; // Always 2 + source: string; // "https://excalidraw.com" + elements: ExcalidrawElement[]; + appState: AppState; + files: Record; // Usually empty {} +} +``` + +## AppState + +```typescript +interface AppState { + viewBackgroundColor: string; // Hex color, e.g., "#ffffff" + gridSize: number; // Typically 20 +} +``` + +## ExcalidrawElement Base Properties + +All elements share these common properties: + +```typescript +interface BaseElement { + id: string; // Unique identifier + type: ElementType; // See Element Types below + x: number; // X coordinate (pixels from top-left) + y: number; // Y coordinate (pixels from top-left) + width: number; // Width in pixels + height: number; // Height in pixels + angle: number; // Rotation angle in radians (usually 0) + strokeColor: string; // Hex color, e.g., "#1e1e1e" + backgroundColor: string; // Hex color or "transparent" + fillStyle: "solid" | "hachure" | "cross-hatch"; + strokeWidth: number; // 1-4 typically + strokeStyle: "solid" | "dashed" | "dotted"; + roughness: number; // 0-2, controls hand-drawn effect (1 = default) + opacity: number; // 0-100 + groupIds: string[]; // IDs of groups this element belongs to + frameId: null; // Usually null + index: string; // Stacking order identifier + roundness: Roundness | null; + seed: number; // Random seed for deterministic rendering + version: number; // Element version (increment on edit) + versionNonce: number; // Random number changed on edit + isDeleted: boolean; // Should be false + boundElements: any; // Usually null + updated: number; // Timestamp in milliseconds + link: null; // External link (usually null) + locked: boolean; // Whether element is locked +} +``` + +## Element Types + +### Rectangle + +```typescript +interface RectangleElement extends BaseElement { + type: "rectangle"; + roundness: { type: 3 }; // 3 = rounded corners + text?: string; // Optional text inside + fontSize?: number; // Font size (16-32 typical) + fontFamily?: number; // 1 = Virgil, 2 = Helvetica, 3 = Cascadia + textAlign?: "left" | "center" | "right"; + verticalAlign?: "top" | "middle" | "bottom"; +} +``` + +**Example:** +```json +{ + "id": "rect1", + "type": "rectangle", + "x": 100, + "y": 100, + "width": 200, + "height": 100, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "text": "My Box", + "fontSize": 20, + "textAlign": "center", + "verticalAlign": "middle", + "roundness": { "type": 3 } +} +``` + +### Ellipse + +```typescript +interface EllipseElement extends BaseElement { + type: "ellipse"; + text?: string; + fontSize?: number; + fontFamily?: number; + textAlign?: "left" | "center" | "right"; + verticalAlign?: "top" | "middle" | "bottom"; +} +``` + +### Diamond + +```typescript +interface DiamondElement extends BaseElement { + type: "diamond"; + text?: string; + fontSize?: number; + fontFamily?: number; + textAlign?: "left" | "center" | "right"; + verticalAlign?: "top" | "middle" | "bottom"; +} +``` + +### Arrow + +```typescript +interface ArrowElement extends BaseElement { + type: "arrow"; + points: [number, number][]; // Array of [x, y] coordinates relative to element + startBinding: Binding | null; + endBinding: Binding | null; + roundness: { type: 2 }; // 2 = curved arrow +} +``` + +**Example:** +```json +{ + "id": "arrow1", + "type": "arrow", + "x": 100, + "y": 100, + "width": 200, + "height": 0, + "points": [ + [0, 0], + [200, 0] + ], + "roundness": { "type": 2 }, + "startBinding": null, + "endBinding": null +} +``` + +**Points explanation:** +- First point `[0, 0]` is relative to `(x, y)` +- Subsequent points are relative to the first point +- For straight horizontal arrow: `[[0, 0], [width, 0]]` +- For straight vertical arrow: `[[0, 0], [0, height]]` + +### Line + +```typescript +interface LineElement extends BaseElement { + type: "line"; + points: [number, number][]; + startBinding: Binding | null; + endBinding: Binding | null; + roundness: { type: 2 } | null; +} +``` + +### Text + +```typescript +interface TextElement extends BaseElement { + type: "text"; + text: string; + fontSize: number; + fontFamily: number; // 1-3 + textAlign: "left" | "center" | "right"; + verticalAlign: "top" | "middle" | "bottom"; + roundness: null; // Text has no roundness +} +``` + +**Example:** +```json +{ + "id": "text1", + "type": "text", + "x": 100, + "y": 100, + "width": 150, + "height": 25, + "text": "Hello World", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "roundness": null +} +``` + +**Width/Height calculation:** +- Width ≈ `text.length * fontSize * 0.6` +- Height ≈ `fontSize * 1.2 * numberOfLines` + +## Bindings + +Bindings connect arrows to shapes: + +```typescript +interface Binding { + elementId: string; // ID of bound element + focus: number; // -1 to 1, position along edge + gap: number; // Distance from element edge +} +``` + +## Common Colors + +| Color Name | Hex Code | Use Case | +|------------|----------|----------| +| Black | `#1e1e1e` | Default stroke | +| Light Blue | `#a5d8ff` | Primary entities | +| Light Green | `#b2f2bb` | Process steps | +| Yellow | `#ffd43b` | Important/Central | +| Light Red | `#ffc9c9` | Warnings/Errors | +| Cyan | `#96f2d7` | Secondary items | +| Transparent | `transparent` | No fill | +| White | `#ffffff` | Background | + +## ID Generation + +IDs should be unique strings. Common patterns: + +```javascript +// Timestamp-based +const id = Date.now().toString(36) + Math.random().toString(36).substr(2); + +// Sequential +const id = "element-" + counter++; + +// Descriptive +const id = "step-1", "entity-user", "arrow-1-to-2"; +``` + +## Seed Generation + +Seeds are used for deterministic randomness in hand-drawn effect: + +```javascript +const seed = Math.floor(Math.random() * 2147483647); +``` + +## Version and VersionNonce + +```javascript +const version = 1; // Increment when element is edited +const versionNonce = Math.floor(Math.random() * 2147483647); +``` + +## Coordinate System + +- Origin `(0, 0)` is top-left corner +- X increases to the right +- Y increases downward +- All units are in pixels + +## Recommended Spacing + +| Context | Spacing | +|---------|---------| +| Horizontal gap between elements | 200-300px | +| Vertical gap between rows | 100-150px | +| Minimum margin from edge | 50px | +| Arrow-to-box clearance | 20-30px | + +## Font Families + +| ID | Name | Description | +|----|------|-------------| +| 1 | Virgil | Hand-drawn style (default) | +| 2 | Helvetica | Clean sans-serif | +| 3 | Cascadia | Monospace | + +## Validation Rules + +✅ **Required:** +- All IDs must be unique +- `type` must match actual element type +- `version` must be an integer ≥ 1 +- `opacity` must be 0-100 + +⚠️ **Recommended:** +- Keep `roughness` at 1 for consistency +- Use `strokeWidth` of 2 for clarity +- Set `isDeleted` to `false` +- Set `locked` to `false` +- Keep `frameId`, `boundElements`, `link` as `null` + +## Complete Minimal Example + +```json +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "box1", + "type": "rectangle", + "x": 100, + "y": 100, + "width": 200, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": { "type": 3 }, + "seed": 1234567890, + "version": 1, + "versionNonce": 987654321, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Hello", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} +``` diff --git a/skills/excalidraw-diagram-generator/scripts/.gitignore b/skills/excalidraw-diagram-generator/scripts/.gitignore new file mode 100644 index 0000000..bcc50b6 --- /dev/null +++ b/skills/excalidraw-diagram-generator/scripts/.gitignore @@ -0,0 +1,34 @@ +# Avoid accidentally committing local Python artifacts produced when running these scripts. + +# Byte-compiled / cache files +__pycache__/ +*.py[cod] +*$py.class + +# Virtual environments (people often create these next to scripts) +.venv/ +venv/ +env/ +ENV/ + +# Tool caches +.pytest_cache/ +.mypy_cache/ +.ruff_cache/ +.tox/ +.nox/ + +# Coverage outputs +.coverage +.coverage.* +htmlcov/ + +# Packaging/build outputs (rare here, but harmless) +build/ +dist/ +*.egg-info/ +.eggs/ + +# OS cruft +.DS_Store +Thumbs.db diff --git a/skills/excalidraw-diagram-generator/scripts/README.md b/skills/excalidraw-diagram-generator/scripts/README.md new file mode 100644 index 0000000..df810f1 --- /dev/null +++ b/skills/excalidraw-diagram-generator/scripts/README.md @@ -0,0 +1,193 @@ +# Excalidraw Library Tools + +This directory contains scripts for working with Excalidraw libraries. + +## split-excalidraw-library.py + +Splits an Excalidraw library file (`*.excalidrawlib`) into individual icon JSON files for efficient token usage by AI assistants. + +### Prerequisites + +- Python 3.6 or higher +- No additional dependencies required (uses only standard library) + +### Usage + +```bash +python split-excalidraw-library.py +``` + +### Step-by-Step Workflow + +1. **Create library directory**: + ```bash + mkdir -p skills/excalidraw-diagram-generator/libraries/aws-architecture-icons + ``` + +2. **Download and place library file**: + - Visit: https://libraries.excalidraw.com/ + - Search for "AWS Architecture Icons" and download the `.excalidrawlib` file + - Rename it to match the directory name: `aws-architecture-icons.excalidrawlib` + - Place it in the directory created in step 1 + +3. **Run the script**: + ```bash + python skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/ + ``` + +### Output Structure + +The script creates the following structure in the library directory: + +``` +skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/ + aws-architecture-icons.excalidrawlib # Original file (kept) + reference.md # Generated: Quick reference table + icons/ # Generated: Individual icon files + API-Gateway.json + CloudFront.json + EC2.json + S3.json + ... +``` + +### What the Script Does + +1. **Reads** the `.excalidrawlib` file +2. **Extracts** each icon from the `libraryItems` array +3. **Sanitizes** icon names to create valid filenames (spaces → hyphens, removes special characters) +4. **Saves** each icon as a separate JSON file in the `icons/` directory +5. **Generates** a `reference.md` file with a table mapping icon names to filenames + +### Benefits + +- **Token Efficiency**: AI can first read the lightweight `reference.md` to find relevant icons, then load only the specific icon files needed +- **Organization**: Icons are organized in a clear directory structure +- **Extensibility**: Users can add multiple library sets side-by-side + +### Recommended Workflow + +1. Download desired Excalidraw libraries from https://libraries.excalidraw.com/ +2. Run this script on each library file +3. Move the generated folders to `../libraries/` +4. The AI assistant will use `reference.md` files to locate and use icons efficiently + +### Library Sources (Examples — verify availability) + +- Examples found on https://libraries.excalidraw.com/ may include cloud/service icon sets. +- Availability changes over time; verify the exact library names on the site before use. +- This script works with any valid `.excalidrawlib` file you provide. + +### Troubleshooting + +**Error: File not found** +- Check that the file path is correct +- Make sure the file has a `.excalidrawlib` extension + +**Error: Invalid library file format** +- Ensure the file is a valid Excalidraw library file +- Check that it contains a `libraryItems` array + +### License Considerations + +When using third-party icon libraries: +- **AWS Architecture Icons**: Subject to AWS Content License +- **GCP Icons**: Subject to Google's terms +- **Other libraries**: Check each library's license + +This script is for personal/organizational use. Redistribution of split icon files should comply with the original library's license terms. + +## add-icon-to-diagram.py + +Adds a specific icon from a split Excalidraw library into an existing `.excalidraw` diagram. The script handles coordinate translation and ID collision avoidance, and can optionally add a label under the icon. + +### Prerequisites + +- Python 3.6 or higher +- A diagram file (`.excalidraw`) +- A split icon library directory (created by `split-excalidraw-library.py`) + +### Usage + +```bash +python add-icon-to-diagram.py [OPTIONS] +``` + +**Options** +- `--library-path PATH` : Path to the icon library directory (default: `aws-architecture-icons`) +- `--label TEXT` : Add a text label below the icon +-- `--use-edit-suffix` : Edit via `.excalidraw.edit` to avoid editor overwrite issues (enabled by default; pass `--no-use-edit-suffix` to disable) + +### Examples + +```bash +# Add EC2 icon at position (400, 300) +python add-icon-to-diagram.py diagram.excalidraw EC2 400 300 + +# Add VPC icon with label +python add-icon-to-diagram.py diagram.excalidraw VPC 200 150 --label "VPC" + +# Safe edit mode is enabled by default (avoids editor overwrite issues) +# Use `--no-use-edit-suffix` to disable +python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 + +# Add icon from another library +python add-icon-to-diagram.py diagram.excalidraw Compute-Engine 500 200 \ + --library-path libraries/gcp-icons --label "API Server" +``` + +### What the Script Does + +1. **Loads** the icon JSON from the library’s `icons/` directory +2. **Calculates** the icon’s bounding box +3. **Offsets** all coordinates to the target position +4. **Generates** unique IDs for all elements and groups +5. **Appends** the transformed elements to the diagram +6. **(Optional)** Adds a label beneath the icon + +--- + +## add-arrow.py + +Adds a straight arrow between two points in an existing `.excalidraw` diagram. Supports optional labels and line styles. + +### Prerequisites + +- Python 3.6 or higher +- A diagram file (`.excalidraw`) + +### Usage + +```bash +python add-arrow.py [OPTIONS] +``` + +**Options** +- `--style {solid|dashed|dotted}` : Line style (default: `solid`) +- `--color HEX` : Arrow color (default: `#1e1e1e`) +- `--label TEXT` : Add a text label on the arrow +-- `--use-edit-suffix` : Edit via `.excalidraw.edit` to avoid editor overwrite issues (enabled by default; pass `--no-use-edit-suffix` to disable) + +### Examples + +```bash +# Simple arrow +python add-arrow.py diagram.excalidraw 300 200 500 300 + +# Arrow with label +python add-arrow.py diagram.excalidraw 300 200 500 300 --label "HTTPS" + +# Dashed arrow with custom color +python add-arrow.py diagram.excalidraw 400 350 600 400 --style dashed --color "#7950f2" + +# Safe edit mode is enabled by default (avoids editor overwrite issues) +# Use `--no-use-edit-suffix` to disable +python add-arrow.py diagram.excalidraw 300 200 500 300 +``` + +### What the Script Does + +1. **Creates** an arrow element from the given coordinates +2. **(Optional)** Adds a label near the arrow midpoint +3. **Appends** elements to the diagram +4. **Saves** the updated file diff --git a/skills/excalidraw-diagram-generator/scripts/add-arrow.py b/skills/excalidraw-diagram-generator/scripts/add-arrow.py new file mode 100644 index 0000000..169f09f --- /dev/null +++ b/skills/excalidraw-diagram-generator/scripts/add-arrow.py @@ -0,0 +1,312 @@ +#!/usr/bin/env python3 +""" +Add arrows (connections) between elements in Excalidraw diagrams. + +Usage: + python add-arrow.py [OPTIONS] + +Options: + --style {solid|dashed|dotted} Arrow line style (default: solid) + --color HEX Arrow color (default: #1e1e1e) + --label TEXT Add text label on the arrow + --use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable) + +Examples: + python add-arrow.py diagram.excalidraw 300 200 500 300 + python add-arrow.py diagram.excalidraw 300 200 500 300 --label "HTTP" + python add-arrow.py diagram.excalidraw 300 200 500 300 --style dashed --color "#7950f2" + python add-arrow.py diagram.excalidraw 300 200 500 300 --use-edit-suffix +""" + +import json +import sys +import uuid +from pathlib import Path +from typing import Dict, Any + + +def generate_unique_id() -> str: + """Generate a unique ID for Excalidraw elements.""" + return str(uuid.uuid4()).replace('-', '')[:16] + + +def prepare_edit_path(diagram_path: Path, use_edit_suffix: bool) -> tuple[Path, Path | None]: + """ + Prepare a safe edit path to avoid editor overwrite issues. + + Returns: + (work_path, final_path) + - work_path: file path to read/write during edit + - final_path: file path to rename back to (or None if not used) + """ + if not use_edit_suffix: + return diagram_path, None + + if diagram_path.suffix != ".excalidraw": + return diagram_path, None + + edit_path = diagram_path.with_suffix(diagram_path.suffix + ".edit") + + if diagram_path.exists(): + if edit_path.exists(): + raise FileExistsError(f"Edit file already exists: {edit_path}") + diagram_path.rename(edit_path) + + return edit_path, diagram_path + + +def finalize_edit_path(work_path: Path, final_path: Path | None) -> None: + """Finalize edit by renaming .edit back to .excalidraw if needed.""" + if final_path is None: + return + + if final_path.exists(): + final_path.unlink() + + work_path.rename(final_path) + + +def create_arrow( + from_x: float, + from_y: float, + to_x: float, + to_y: float, + style: str = "solid", + color: str = "#1e1e1e", + label: str = None +) -> list: + """ + Create an arrow element. + + Args: + from_x: Starting X coordinate + from_y: Starting Y coordinate + to_x: Ending X coordinate + to_y: Ending Y coordinate + style: Line style (solid, dashed, dotted) + color: Arrow color + label: Optional text label on the arrow + + Returns: + List of elements (arrow and optional label) + """ + elements = [] + + # Arrow element + arrow = { + "id": generate_unique_id(), + "type": "arrow", + "x": from_x, + "y": from_y, + "width": to_x - from_x, + "height": to_y - from_y, + "angle": 0, + "strokeColor": color, + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": style, + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": None, + "index": "a0", + "roundness": { + "type": 2 + }, + "seed": 1000000000 + hash(f"{from_x}{from_y}{to_x}{to_y}") % 1000000000, + "version": 1, + "versionNonce": 2000000000 + hash(f"{from_x}{from_y}{to_x}{to_y}") % 1000000000, + "isDeleted": False, + "boundElements": [], + "updated": 1738195200000, + "link": None, + "locked": False, + "points": [ + [0, 0], + [to_x - from_x, to_y - from_y] + ], + "startBinding": None, + "endBinding": None, + "startArrowhead": None, + "endArrowhead": "arrow", + "lastCommittedPoint": None + } + elements.append(arrow) + + # Optional label + if label: + mid_x = (from_x + to_x) / 2 - (len(label) * 5) + mid_y = (from_y + to_y) / 2 - 10 + + label_element = { + "id": generate_unique_id(), + "type": "text", + "x": mid_x, + "y": mid_y, + "width": len(label) * 10, + "height": 20, + "angle": 0, + "strokeColor": color, + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": None, + "index": "a0", + "roundness": None, + "seed": 1000000000 + hash(label) % 1000000000, + "version": 1, + "versionNonce": 2000000000 + hash(label) % 1000000000, + "isDeleted": False, + "boundElements": [], + "updated": 1738195200000, + "link": None, + "locked": False, + "text": label, + "fontSize": 14, + "fontFamily": 5, + "textAlign": "center", + "verticalAlign": "top", + "containerId": None, + "originalText": label, + "autoResize": True, + "lineHeight": 1.25 + } + elements.append(label_element) + + return elements + + +def add_arrow_to_diagram( + diagram_path: Path, + from_x: float, + from_y: float, + to_x: float, + to_y: float, + style: str = "solid", + color: str = "#1e1e1e", + label: str = None +) -> None: + """ + Add an arrow to an Excalidraw diagram. + + Args: + diagram_path: Path to the Excalidraw diagram file + from_x: Starting X coordinate + from_y: Starting Y coordinate + to_x: Ending X coordinate + to_y: Ending Y coordinate + style: Line style (solid, dashed, dotted) + color: Arrow color + label: Optional text label + """ + print(f"Creating arrow from ({from_x}, {from_y}) to ({to_x}, {to_y})") + arrow_elements = create_arrow(from_x, from_y, to_x, to_y, style, color, label) + + if label: + print(f" With label: '{label}'") + + # Load diagram + print(f"Loading diagram: {diagram_path}") + with open(diagram_path, 'r', encoding='utf-8') as f: + diagram = json.load(f) + + # Add arrow elements + if 'elements' not in diagram: + diagram['elements'] = [] + + original_count = len(diagram['elements']) + diagram['elements'].extend(arrow_elements) + print(f" Added {len(arrow_elements)} elements (total: {original_count} -> {len(diagram['elements'])})") + + # Save diagram + print(f"Saving diagram") + with open(diagram_path, 'w', encoding='utf-8') as f: + json.dump(diagram, f, indent=2, ensure_ascii=False) + + print(f"✓ Successfully added arrow to diagram") + + +def main(): + """Main entry point.""" + if len(sys.argv) < 6: + print("Usage: python add-arrow.py [OPTIONS]") + print("\nOptions:") + print(" --style {solid|dashed|dotted} Line style (default: solid)") + print(" --color HEX Color (default: #1e1e1e)") + print(" --label TEXT Text label on arrow") + print(" --use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)") + print("\nExamples:") + print(" python add-arrow.py diagram.excalidraw 300 200 500 300") + print(" python add-arrow.py diagram.excalidraw 300 200 500 300 --label 'HTTP'") + sys.exit(1) + + diagram_path = Path(sys.argv[1]) + from_x = float(sys.argv[2]) + from_y = float(sys.argv[3]) + to_x = float(sys.argv[4]) + to_y = float(sys.argv[5]) + + # Parse optional arguments + style = "solid" + color = "#1e1e1e" + label = None + # Default: use edit suffix to avoid editor overwrite issues + use_edit_suffix = True + + i = 6 + while i < len(sys.argv): + if sys.argv[i] == '--style': + if i + 1 < len(sys.argv): + style = sys.argv[i + 1] + if style not in ['solid', 'dashed', 'dotted']: + print(f"Error: Invalid style '{style}'. Must be: solid, dashed, or dotted") + sys.exit(1) + i += 2 + else: + print("Error: --style requires an argument") + sys.exit(1) + elif sys.argv[i] == '--color': + if i + 1 < len(sys.argv): + color = sys.argv[i + 1] + i += 2 + else: + print("Error: --color requires an argument") + sys.exit(1) + elif sys.argv[i] == '--label': + if i + 1 < len(sys.argv): + label = sys.argv[i + 1] + i += 2 + else: + print("Error: --label requires a text argument") + sys.exit(1) + elif sys.argv[i] == '--use-edit-suffix': + use_edit_suffix = True + i += 1 + elif sys.argv[i] == '--no-use-edit-suffix': + use_edit_suffix = False + i += 1 + else: + print(f"Error: Unknown option: {sys.argv[i]}") + sys.exit(1) + + # Validate inputs + if not diagram_path.exists(): + print(f"Error: Diagram file not found: {diagram_path}") + sys.exit(1) + + try: + work_path, final_path = prepare_edit_path(diagram_path, use_edit_suffix) + add_arrow_to_diagram(work_path, from_x, from_y, to_x, to_y, style, color, label) + finalize_edit_path(work_path, final_path) + except Exception as e: + print(f"Error: {e}") + sys.exit(1) + + +if __name__ == '__main__': + main() diff --git a/skills/excalidraw-diagram-generator/scripts/add-icon-to-diagram.py b/skills/excalidraw-diagram-generator/scripts/add-icon-to-diagram.py new file mode 100644 index 0000000..f103525 --- /dev/null +++ b/skills/excalidraw-diagram-generator/scripts/add-icon-to-diagram.py @@ -0,0 +1,404 @@ +#!/usr/bin/env python3 +""" +Add icons from Excalidraw libraries to diagrams. + +This script reads an icon JSON file from an Excalidraw library, transforms its coordinates +to a target position, generates unique IDs, and adds it to an existing Excalidraw diagram. +Works with any Excalidraw library (AWS, GCP, Azure, Kubernetes, etc.). + +Usage: + python add-icon-to-diagram.py [OPTIONS] + +Options: + --library-path PATH Path to the icon library directory (default: aws-architecture-icons) + --label TEXT Add a text label below the icon + --use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable) + +Examples: + python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 + python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 --label "Web Server" + python add-icon-to-diagram.py diagram.excalidraw VPC 200 150 --library-path libraries/gcp-icons + python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 --use-edit-suffix +""" + +import json +import sys +import uuid +from pathlib import Path +from typing import Dict, List, Any, Tuple + + +def generate_unique_id() -> str: + """Generate a unique ID for Excalidraw elements.""" + return str(uuid.uuid4()).replace('-', '')[:16] + + +def calculate_bounding_box(elements: List[Dict[str, Any]]) -> Tuple[float, float, float, float]: + """Calculate the bounding box (min_x, min_y, max_x, max_y) of icon elements.""" + if not elements: + return (0, 0, 0, 0) + + min_x = float('inf') + min_y = float('inf') + max_x = float('-inf') + max_y = float('-inf') + + for element in elements: + if 'x' in element and 'y' in element: + x = element['x'] + y = element['y'] + width = element.get('width', 0) + height = element.get('height', 0) + + min_x = min(min_x, x) + min_y = min(min_y, y) + max_x = max(max_x, x + width) + max_y = max(max_y, y + height) + + return (min_x, min_y, max_x, max_y) + + +def transform_icon_elements( + elements: List[Dict[str, Any]], + target_x: float, + target_y: float +) -> List[Dict[str, Any]]: + """ + Transform icon elements to target coordinates with unique IDs. + + Args: + elements: Icon elements from JSON file + target_x: Target X coordinate (top-left position) + target_y: Target Y coordinate (top-left position) + + Returns: + Transformed elements with new coordinates and IDs + """ + if not elements: + return [] + + # Calculate bounding box + min_x, min_y, max_x, max_y = calculate_bounding_box(elements) + + # Calculate offset + offset_x = target_x - min_x + offset_y = target_y - min_y + + # Create ID mapping: old_id -> new_id + id_mapping = {} + for element in elements: + if 'id' in element: + old_id = element['id'] + id_mapping[old_id] = generate_unique_id() + + # Create group ID mapping + group_id_mapping = {} + for element in elements: + if 'groupIds' in element: + for old_group_id in element['groupIds']: + if old_group_id not in group_id_mapping: + group_id_mapping[old_group_id] = generate_unique_id() + + # Transform elements + transformed = [] + for element in elements: + new_element = element.copy() + + # Update coordinates + if 'x' in new_element: + new_element['x'] = new_element['x'] + offset_x + if 'y' in new_element: + new_element['y'] = new_element['y'] + offset_y + + # Update ID + if 'id' in new_element: + new_element['id'] = id_mapping[new_element['id']] + + # Update group IDs + if 'groupIds' in new_element: + new_element['groupIds'] = [ + group_id_mapping[gid] for gid in new_element['groupIds'] + ] + + # Update binding references if they exist + if 'startBinding' in new_element and new_element['startBinding']: + if 'elementId' in new_element['startBinding']: + old_id = new_element['startBinding']['elementId'] + if old_id in id_mapping: + new_element['startBinding']['elementId'] = id_mapping[old_id] + + if 'endBinding' in new_element and new_element['endBinding']: + if 'elementId' in new_element['endBinding']: + old_id = new_element['endBinding']['elementId'] + if old_id in id_mapping: + new_element['endBinding']['elementId'] = id_mapping[old_id] + + # Update containerId if it exists + if 'containerId' in new_element and new_element['containerId']: + old_id = new_element['containerId'] + if old_id in id_mapping: + new_element['containerId'] = id_mapping[old_id] + + # Update boundElements if they exist + if 'boundElements' in new_element and new_element['boundElements']: + new_bound_elements = [] + for bound_elem in new_element['boundElements']: + if isinstance(bound_elem, dict) and 'id' in bound_elem: + old_id = bound_elem['id'] + if old_id in id_mapping: + bound_elem['id'] = id_mapping[old_id] + new_bound_elements.append(bound_elem) + new_element['boundElements'] = new_bound_elements + + transformed.append(new_element) + + return transformed + + +def load_icon(icon_name: str, library_path: Path) -> List[Dict[str, Any]]: + """ + Load icon elements from library. + + Args: + icon_name: Name of the icon (e.g., "EC2", "VPC") + library_path: Path to the icon library directory + + Returns: + List of icon elements + """ + icon_file = library_path / "icons" / f"{icon_name}.json" + + if not icon_file.exists(): + raise FileNotFoundError(f"Icon file not found: {icon_file}") + + with open(icon_file, 'r', encoding='utf-8') as f: + icon_data = json.load(f) + + return icon_data.get('elements', []) + + +def prepare_edit_path(diagram_path: Path, use_edit_suffix: bool) -> tuple[Path, Path | None]: + """ + Prepare a safe edit path to avoid editor overwrite issues. + + Returns: + (work_path, final_path) + - work_path: file path to read/write during edit + - final_path: file path to rename back to (or None if not used) + """ + if not use_edit_suffix: + return diagram_path, None + + if diagram_path.suffix != ".excalidraw": + return diagram_path, None + + edit_path = diagram_path.with_suffix(diagram_path.suffix + ".edit") + + if diagram_path.exists(): + if edit_path.exists(): + raise FileExistsError(f"Edit file already exists: {edit_path}") + diagram_path.rename(edit_path) + + return edit_path, diagram_path + + +def finalize_edit_path(work_path: Path, final_path: Path | None) -> None: + """Finalize edit by renaming .edit back to .excalidraw if needed.""" + if final_path is None: + return + + if final_path.exists(): + final_path.unlink() + + work_path.rename(final_path) + + +def create_text_label(text: str, x: float, y: float) -> Dict[str, Any]: + """ + Create a text label element. + + Args: + text: Label text + x: X coordinate + y: Y coordinate + + Returns: + Text element dictionary + """ + return { + "id": generate_unique_id(), + "type": "text", + "x": x, + "y": y, + "width": len(text) * 10, # Approximate width + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": None, + "index": "a0", + "roundness": None, + "seed": 1000000000 + hash(text) % 1000000000, + "version": 1, + "versionNonce": 2000000000 + hash(text) % 1000000000, + "isDeleted": False, + "boundElements": [], + "updated": 1738195200000, + "link": None, + "locked": False, + "text": text, + "fontSize": 16, + "fontFamily": 5, # Excalifont + "textAlign": "center", + "verticalAlign": "top", + "containerId": None, + "originalText": text, + "autoResize": True, + "lineHeight": 1.25 + } + + +def add_icon_to_diagram( + diagram_path: Path, + icon_name: str, + x: float, + y: float, + library_path: Path, + label: str = None +) -> None: + """ + Add an icon to an Excalidraw diagram. + + Args: + diagram_path: Path to the Excalidraw diagram file + icon_name: Name of the icon to add + x: Target X coordinate + y: Target Y coordinate + library_path: Path to the icon library directory + label: Optional text label to add below the icon + """ + # Load icon elements + print(f"Loading icon: {icon_name}") + icon_elements = load_icon(icon_name, library_path) + print(f" Loaded {len(icon_elements)} elements") + + # Transform icon elements + print(f"Transforming to position ({x}, {y})") + transformed_elements = transform_icon_elements(icon_elements, x, y) + + # Calculate icon bounding box for label positioning + if label and transformed_elements: + min_x, min_y, max_x, max_y = calculate_bounding_box(transformed_elements) + icon_width = max_x - min_x + icon_height = max_y - min_y + + # Position label below icon, centered + label_x = min_x + (icon_width / 2) - (len(label) * 5) + label_y = max_y + 10 + + label_element = create_text_label(label, label_x, label_y) + transformed_elements.append(label_element) + print(f" Added label: '{label}'") + + # Load diagram + print(f"Loading diagram: {diagram_path}") + with open(diagram_path, 'r', encoding='utf-8') as f: + diagram = json.load(f) + + # Add transformed elements + if 'elements' not in diagram: + diagram['elements'] = [] + + original_count = len(diagram['elements']) + diagram['elements'].extend(transformed_elements) + print(f" Added {len(transformed_elements)} elements (total: {original_count} -> {len(diagram['elements'])})") + + # Save diagram + print(f"Saving diagram") + with open(diagram_path, 'w', encoding='utf-8') as f: + json.dump(diagram, f, indent=2, ensure_ascii=False) + + print(f"✓ Successfully added '{icon_name}' icon to diagram") + + +def main(): + """Main entry point.""" + if len(sys.argv) < 5: + print("Usage: python add-icon-to-diagram.py [OPTIONS]") + print("\nOptions:") + print(" --library-path PATH Path to icon library directory") + print(" --label TEXT Add text label below icon") + print(" --use-edit-suffix Edit via .excalidraw.edit to avoid editor overwrite issues (enabled by default; use --no-use-edit-suffix to disable)") + print("\nExamples:") + print(" python add-icon-to-diagram.py diagram.excalidraw EC2 500 300") + print(" python add-icon-to-diagram.py diagram.excalidraw EC2 500 300 --label 'Web Server'") + sys.exit(1) + + diagram_path = Path(sys.argv[1]) + icon_name = sys.argv[2] + x = float(sys.argv[3]) + y = float(sys.argv[4]) + + # Default library path + script_dir = Path(__file__).parent + default_library_path = script_dir.parent / "libraries" / "aws-architecture-icons" + + # Parse optional arguments + library_path = default_library_path + label = None + # Default: use edit suffix to avoid editor overwrite issues + use_edit_suffix = True + + i = 5 + while i < len(sys.argv): + if sys.argv[i] == '--library-path': + if i + 1 < len(sys.argv): + library_path = Path(sys.argv[i + 1]) + i += 2 + else: + print("Error: --library-path requires a path argument") + sys.exit(1) + elif sys.argv[i] == '--label': + if i + 1 < len(sys.argv): + label = sys.argv[i + 1] + i += 2 + else: + print("Error: --label requires a text argument") + sys.exit(1) + elif sys.argv[i] == '--use-edit-suffix': + use_edit_suffix = True + i += 1 + elif sys.argv[i] == '--no-use-edit-suffix': + use_edit_suffix = False + i += 1 + else: + print(f"Error: Unknown option: {sys.argv[i]}") + sys.exit(1) + + # Validate inputs + if not diagram_path.exists(): + print(f"Error: Diagram file not found: {diagram_path}") + sys.exit(1) + + if not library_path.exists(): + print(f"Error: Library path not found: {library_path}") + sys.exit(1) + + try: + work_path, final_path = prepare_edit_path(diagram_path, use_edit_suffix) + add_icon_to_diagram(work_path, icon_name, x, y, library_path, label) + finalize_edit_path(work_path, final_path) + except Exception as e: + print(f"Error: {e}") + sys.exit(1) + + +if __name__ == '__main__': + main() + diff --git a/skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py b/skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py new file mode 100644 index 0000000..ec903dd --- /dev/null +++ b/skills/excalidraw-diagram-generator/scripts/split-excalidraw-library.py @@ -0,0 +1,183 @@ +#!/usr/bin/env python3 +""" +Excalidraw Library Splitter + +This script splits an Excalidraw library file (*.excalidrawlib) into individual +icon JSON files and generates a reference.md file for easy lookup. + +The script expects the following structure: + skills/excalidraw-diagram-generator/libraries/{icon-set-name}/ + {icon-set-name}.excalidrawlib (place this file first) + +Usage: + python split-excalidraw-library.py + +Example: + python split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/ +""" + +import json +import os +import re +import sys +from pathlib import Path + + +def sanitize_filename(name: str) -> str: + """ + Sanitize icon name to create a valid filename. + + Args: + name: Original icon name + + Returns: + Sanitized filename safe for all platforms + """ + # Replace spaces with hyphens + filename = name.replace(' ', '-') + + # Remove or replace special characters + filename = re.sub(r'[^\w\-.]', '', filename) + + # Remove multiple consecutive hyphens + filename = re.sub(r'-+', '-', filename) + + # Remove leading/trailing hyphens + filename = filename.strip('-') + + return filename + + +def find_library_file(directory: Path) -> Path: + """ + Find the .excalidrawlib file in the given directory. + + Args: + directory: Directory to search + + Returns: + Path to the library file + + Raises: + SystemExit: If no library file or multiple library files found + """ + library_files = list(directory.glob('*.excalidrawlib')) + + if len(library_files) == 0: + print(f"Error: No .excalidrawlib file found in {directory}") + print(f"Please place a .excalidrawlib file in {directory} first.") + sys.exit(1) + + if len(library_files) > 1: + print(f"Error: Multiple .excalidrawlib files found in {directory}") + print(f"Please keep only one library file in {directory}.") + sys.exit(1) + + return library_files[0] + + +def split_library(library_dir: str) -> None: + """ + Split an Excalidraw library file into individual icon files. + + Args: + library_dir: Path to the directory containing the .excalidrawlib file + """ + library_dir = Path(library_dir) + + if not library_dir.exists(): + print(f"Error: Directory not found: {library_dir}") + sys.exit(1) + + if not library_dir.is_dir(): + print(f"Error: Path is not a directory: {library_dir}") + sys.exit(1) + + # Find the library file + library_path = find_library_file(library_dir) + print(f"Found library: {library_path.name}") + + # Load library file + print(f"Loading library data...") + with open(library_path, 'r', encoding='utf-8') as f: + library_data = json.load(f) + + # Validate library structure + if 'libraryItems' not in library_data: + print("Error: Invalid library file format (missing 'libraryItems')") + sys.exit(1) + + # Create icons directory + icons_dir = library_dir / 'icons' + icons_dir.mkdir(exist_ok=True) + print(f"Output directory: {library_dir}") + + # Process each library item (icon) + library_items = library_data['libraryItems'] + icon_list = [] + + print(f"Processing {len(library_items)} icons...") + + for item in library_items: + # Get icon name + icon_name = item.get('name', 'Unnamed') + + # Create sanitized filename + filename = sanitize_filename(icon_name) + '.json' + + # Save icon data + icon_path = icons_dir / filename + with open(icon_path, 'w', encoding='utf-8') as f: + json.dump(item, f, ensure_ascii=False, indent=2) + + # Add to reference list + icon_list.append({ + 'name': icon_name, + 'filename': filename + }) + + print(f" ✓ {icon_name} → {filename}") + + # Sort icon list by name + icon_list.sort(key=lambda x: x['name']) + + # Generate reference.md + library_name = library_path.stem + reference_path = library_dir / 'reference.md' + with open(reference_path, 'w', encoding='utf-8') as f: + f.write(f"# {library_name} Reference\n\n") + f.write(f"This directory contains {len(icon_list)} icons extracted from `{library_path.name}`.\n\n") + f.write("## Available Icons\n\n") + f.write("| Icon Name | Filename |\n") + f.write("|-----------|----------|\n") + + for icon in icon_list: + f.write(f"| {icon['name']} | `icons/{icon['filename']}` |\n") + + f.write("\n## Usage\n\n") + f.write("Each icon JSON file contains the complete `elements` array needed to render that icon in Excalidraw.\n") + f.write("You can copy the elements from these files into your Excalidraw diagrams.\n") + + print(f"\n✅ Successfully split library into {len(icon_list)} icons") + print(f"📄 Reference file created: {reference_path}") + print(f"📁 Icons directory: {icons_dir}") + + +def main(): + """Main entry point.""" + if hasattr(sys.stdout, "reconfigure"): + # Ensure consistent UTF-8 output on Windows consoles. + sys.stdout.reconfigure(encoding="utf-8") + if len(sys.argv) != 2: + print("Usage: python split-excalidraw-library.py ") + print("\nExample:") + print(" python split-excalidraw-library.py skills/excalidraw-diagram-generator/libraries/aws-architecture-icons/") + print("\nNote: The directory should contain a .excalidrawlib file.") + sys.exit(1) + + library_dir = sys.argv[1] + split_library(library_dir) + + +if __name__ == '__main__': + main() diff --git a/skills/excalidraw-diagram-generator/templates/business-flow-swimlane-template.excalidraw b/skills/excalidraw-diagram-generator/templates/business-flow-swimlane-template.excalidraw new file mode 100644 index 0000000..0d0c26b --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/business-flow-swimlane-template.excalidraw @@ -0,0 +1,334 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "title", + "type": "text", + "x": 200, + "y": 50, + "width": 300, + "height": 30, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": null, + "seed": 2001001001, + "version": 1, + "versionNonce": 3002002001, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Business Process Flow", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "lane-header-1", + "type": "rectangle", + "x": 100, + "y": 120, + "width": 200, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": null, + "seed": 2001001002, + "version": 1, + "versionNonce": 3002002002, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Customer", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "lane-1", + "type": "rectangle", + "x": 100, + "y": 170, + "width": 200, + "height": 250, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": null, + "seed": 2001001003, + "version": 1, + "versionNonce": 3002002003, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "process-1", + "type": "rectangle", + "x": 130, + "y": 200, + "width": 140, + "height": 70, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": { "type": 3 }, + "seed": 2001001004, + "version": 1, + "versionNonce": 3002002004, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Submit\nRequest", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "lane-header-2", + "type": "rectangle", + "x": 300, + "y": 120, + "width": 200, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#fff3bf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": null, + "seed": 2001001005, + "version": 1, + "versionNonce": 3002002005, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Sales Team", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "lane-2", + "type": "rectangle", + "x": 300, + "y": 170, + "width": 200, + "height": 250, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 2001001006, + "version": 1, + "versionNonce": 3002002006, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "process-2", + "type": "rectangle", + "x": 330, + "y": 200, + "width": 140, + "height": 70, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd43b", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a6", + "roundness": { "type": 3 }, + "seed": 2001001007, + "version": 1, + "versionNonce": 3002002007, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Review\nRequest", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "cross-lane-arrow", + "type": "arrow", + "x": 270, + "y": 235, + "width": 60, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a7", + "roundness": { "type": 2 }, + "seed": 2001001008, + "version": 1, + "versionNonce": 3002002008, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [60, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "process-3", + "type": "rectangle", + "x": 330, + "y": 310, + "width": 140, + "height": 70, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd43b", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a8", + "roundness": { "type": 3 }, + "seed": 2001001009, + "version": 1, + "versionNonce": 3002002009, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Approve", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "within-lane-arrow", + "type": "arrow", + "x": 400, + "y": 270, + "width": 0, + "height": 40, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a9", + "roundness": { "type": 2 }, + "seed": 2001001010, + "version": 1, + "versionNonce": 3002002010, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 40] + ], + "startBinding": null, + "endBinding": null + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/skills/excalidraw-diagram-generator/templates/class-diagram-template.excalidraw b/skills/excalidraw-diagram-generator/templates/class-diagram-template.excalidraw new file mode 100644 index 0000000..aae28df --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/class-diagram-template.excalidraw @@ -0,0 +1,558 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor", + "elements": [ + { + "id": "class-1", + "type": "rectangle", + "x": 100, + "y": 100, + "width": 200, + "height": 180, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": null, + "seed": 3001001001, + "version": 1, + "versionNonce": 4002002001, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "class-name-1", + "type": "text", + "x": 150, + "y": 110, + "width": 100, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": null, + "seed": 3001001002, + "version": 1, + "versionNonce": 4002002002, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "User", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "User", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "separator-1", + "type": "line", + "x": 100, + "y": 145, + "width": 200, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": null, + "seed": 3001001003, + "version": 1, + "versionNonce": 4002002003, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 200, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "attributes-1", + "type": "text", + "x": 110, + "y": 155, + "width": 180, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": null, + "seed": 3001001004, + "version": 1, + "versionNonce": 4002002004, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "- id: number\n- name: string\n- email: string", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- id: number\n- name: string\n- email: string", + "autoResize": true, + "lineHeight": 1.1904761904761905 + }, + { + "id": "separator-2", + "type": "line", + "x": 100, + "y": 215, + "width": 200, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": null, + "seed": 3001001005, + "version": 1, + "versionNonce": 4002002005, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 200, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "methods-1", + "type": "text", + "x": 110, + "y": 225, + "width": 180, + "height": 45, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 3001001006, + "version": 3, + "versionNonce": 1660402375, + "isDeleted": false, + "boundElements": [], + "updated": 1769755991910, + "link": null, + "locked": false, + "text": "+ login(): void\n+ logout(): void\n+ updateProfile(): void", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "+ login(): void\n+ logout(): void\n+ updateProfile(): void", + "autoResize": true, + "lineHeight": 1.0714285714285714 + }, + { + "id": "class-2", + "type": "rectangle", + "x": 400, + "y": 100, + "width": 200, + "height": 180, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#fff3bf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a6", + "roundness": null, + "seed": 3001001007, + "version": 1, + "versionNonce": 4002002007, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "class-name-2", + "type": "text", + "x": 430, + "y": 110, + "width": 140, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a7", + "roundness": null, + "seed": 3001001008, + "version": 1, + "versionNonce": 4002002008, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "AdminUser", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top", + "containerId": null, + "originalText": "AdminUser", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "separator-3", + "type": "line", + "x": 400, + "y": 145, + "width": 200, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a8", + "roundness": null, + "seed": 3001001009, + "version": 1, + "versionNonce": 4002002009, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 200, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "attributes-2", + "type": "text", + "x": 410, + "y": 155, + "width": 180, + "height": 35, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a9", + "roundness": null, + "seed": 3001001010, + "version": 1, + "versionNonce": 4002002010, + "isDeleted": false, + "boundElements": [], + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "- role: string\n- permissions: string[]", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- role: string\n- permissions: string[]", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "separator-4", + "type": "line", + "x": 400, + "y": 200, + "width": 200, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aA", + "roundness": null, + "seed": 3001001011, + "version": 2, + "versionNonce": 873024679, + "isDeleted": false, + "boundElements": [], + "updated": 1769755880046, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 200, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "methods-2", + "type": "text", + "x": 410, + "y": 210, + "width": 180, + "height": 60, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aB", + "roundness": null, + "seed": 3001001012, + "version": 2, + "versionNonce": 1702655305, + "isDeleted": false, + "boundElements": [], + "updated": 1769755880046, + "link": null, + "locked": false, + "text": "+ manageUsers(): void\n+ assignRole(): void\n+ revokePermission(): void", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "+ manageUsers(): void\n+ assignRole(): void\n+ revokePermission(): void", + "autoResize": true, + "lineHeight": 1.4285714285714286 + }, + { + "id": "inheritance-line", + "type": "line", + "x": 400, + "y": 190, + "width": 100, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aC", + "roundness": null, + "seed": 3001001013, + "version": 18, + "versionNonce": 1139021225, + "isDeleted": false, + "boundElements": [], + "updated": 1769755989350, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -100, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null + }, + { + "id": "inheritance-triangle", + "type": "line", + "x": 314.1999816894531, + "y": 181.5, + "width": 15, + "height": 15, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aD", + "roundness": null, + "seed": 3001001014, + "version": 21, + "versionNonce": 1468657767, + "isDeleted": false, + "boundElements": [], + "updated": 1769756005117, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -15, + 15 + ], + [ + 0, + 15 + ], + [ + 0, + 0 + ] + ], + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/skills/excalidraw-diagram-generator/templates/data-flow-diagram-template.excalidraw b/skills/excalidraw-diagram-generator/templates/data-flow-diagram-template.excalidraw new file mode 100644 index 0000000..baea839 --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/data-flow-diagram-template.excalidraw @@ -0,0 +1,279 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "external-entity-1", + "type": "rectangle", + "x": 100, + "y": 200, + "width": 120, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffc9c9", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": { "type": 3 }, + "seed": 1001001001, + "version": 1, + "versionNonce": 2002002002, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "User", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "data-flow-1", + "type": "arrow", + "x": 220, + "y": 240, + "width": 80, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": { "type": 2 }, + "seed": 1001001002, + "version": 1, + "versionNonce": 2002002003, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [80, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "flow-label-1", + "type": "text", + "x": 230, + "y": 220, + "width": 80, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": null, + "seed": 1001001003, + "version": 1, + "versionNonce": 2002002004, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "input data", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "process-1", + "type": "ellipse", + "x": 300, + "y": 200, + "width": 120, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": null, + "seed": 1001001004, + "version": 1, + "versionNonce": 2002002005, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Process\nData", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "data-flow-2", + "type": "arrow", + "x": 420, + "y": 240, + "width": 80, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": { "type": 2 }, + "seed": 1001001005, + "version": 1, + "versionNonce": 2002002006, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [80, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "flow-label-2", + "type": "text", + "x": 425, + "y": 220, + "width": 100, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 1001001006, + "version": 1, + "versionNonce": 2002002007, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "processed data", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "data-store-1", + "type": "rectangle", + "x": 500, + "y": 200, + "width": 150, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a6", + "roundness": null, + "seed": 1001001007, + "version": 1, + "versionNonce": 2002002008, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Data Store\n(Database)", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "data-store-line", + "type": "line", + "x": 500, + "y": 225, + "width": 150, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a7", + "roundness": null, + "seed": 1001001008, + "version": 1, + "versionNonce": 2002002009, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [150, 0] + ], + "startBinding": null, + "endBinding": null + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/skills/excalidraw-diagram-generator/templates/er-diagram-template.excalidraw b/skills/excalidraw-diagram-generator/templates/er-diagram-template.excalidraw new file mode 100644 index 0000000..a023522 --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/er-diagram-template.excalidraw @@ -0,0 +1,662 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "entity-1", + "type": "rectangle", + "x": 100, + "y": 150, + "width": 180, + "height": 150, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": null, + "seed": 5001001001, + "version": 1, + "versionNonce": 6002002001, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "entity-name-1", + "type": "text", + "x": 150, + "y": 160, + "width": 80, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": null, + "seed": 5001001002, + "version": 1, + "versionNonce": 6002002002, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "User", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "entity-separator-1", + "type": "line", + "x": 100, + "y": 195, + "width": 180, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": null, + "seed": 5001001003, + "version": 1, + "versionNonce": 6002002003, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [180, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "attributes-1", + "type": "text", + "x": 110, + "y": 205, + "width": 160, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": null, + "seed": 5001001004, + "version": 1, + "versionNonce": 6002002004, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "PK: user_id\nname\nemail\ncreated_at", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "entity-2", + "type": "rectangle", + "x": 450, + "y": 150, + "width": 180, + "height": 150, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#fff3bf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": null, + "seed": 5001001005, + "version": 1, + "versionNonce": 6002002005, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "entity-name-2", + "type": "text", + "x": 500, + "y": 160, + "width": 80, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 5001001006, + "version": 1, + "versionNonce": 6002002006, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Order", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "entity-separator-2", + "type": "line", + "x": 450, + "y": 195, + "width": 180, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a6", + "roundness": null, + "seed": 5001001007, + "version": 1, + "versionNonce": 6002002007, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [180, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "attributes-2", + "type": "text", + "x": 460, + "y": 205, + "width": 160, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a7", + "roundness": null, + "seed": 5001001008, + "version": 1, + "versionNonce": 6002002008, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "PK: order_id\nFK: user_id\ntotal_amount\norder_date", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "relationship-line", + "type": "line", + "x": 280, + "y": 225, + "width": 170, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a8", + "roundness": null, + "seed": 5001001009, + "version": 1, + "versionNonce": 6002002009, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [170, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "cardinality-1", + "type": "text", + "x": 290, + "y": 205, + "width": 20, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a9", + "roundness": null, + "seed": 5001001010, + "version": 1, + "versionNonce": 6002002010, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "1", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "cardinality-2", + "type": "text", + "x": 420, + "y": 205, + "width": 20, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a10", + "roundness": null, + "seed": 5001001011, + "version": 1, + "versionNonce": 6002002011, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "N", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "relationship-label", + "type": "text", + "x": 330, + "y": 200, + "width": 80, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a11", + "roundness": null, + "seed": 5001001012, + "version": 1, + "versionNonce": 6002002012, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "places", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "entity-3", + "type": "rectangle", + "x": 450, + "y": 380, + "width": 180, + "height": 120, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#d0f0c0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a12", + "roundness": null, + "seed": 5001001013, + "version": 1, + "versionNonce": 6002002013, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "entity-name-3", + "type": "text", + "x": 480, + "y": 390, + "width": 120, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a13", + "roundness": null, + "seed": 5001001014, + "version": 1, + "versionNonce": 6002002014, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Product", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top" + }, + { + "id": "entity-separator-3", + "type": "line", + "x": 450, + "y": 425, + "width": 180, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a14", + "roundness": null, + "seed": 5001001015, + "version": 1, + "versionNonce": 6002002015, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [180, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "attributes-3", + "type": "text", + "x": 460, + "y": 435, + "width": 160, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a15", + "roundness": null, + "seed": 5001001016, + "version": 1, + "versionNonce": 6002002016, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "PK: product_id\nname\nprice", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "relationship-line-2", + "type": "line", + "x": 540, + "y": 300, + "width": 0, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a16", + "roundness": null, + "seed": 5001001017, + "version": 1, + "versionNonce": 6002002017, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 80] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "cardinality-3", + "type": "text", + "x": 550, + "y": 310, + "width": 20, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a17", + "roundness": null, + "seed": 5001001018, + "version": 1, + "versionNonce": 6002002018, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "N", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "cardinality-4", + "type": "text", + "x": 550, + "y": 350, + "width": 20, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a18", + "roundness": null, + "seed": 5001001019, + "version": 1, + "versionNonce": 6002002019, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "M", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "relationship-label-2", + "type": "text", + "x": 490, + "y": 330, + "width": 80, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a19", + "roundness": null, + "seed": 5001001020, + "version": 1, + "versionNonce": 6002002020, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "contains", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "top" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/skills/excalidraw-diagram-generator/templates/flowchart-template.excalidraw b/skills/excalidraw-diagram-generator/templates/flowchart-template.excalidraw new file mode 100644 index 0000000..965a3f9 --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/flowchart-template.excalidraw @@ -0,0 +1,179 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "step1", + "type": "rectangle", + "x": 400, + "y": 200, + "width": 200, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": { "type": 3 }, + "seed": 1234567890, + "version": 1, + "versionNonce": 987654321, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Step 1", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "arrow1", + "type": "arrow", + "x": 500, + "y": 280, + "width": 0, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": { "type": 2 }, + "seed": 1234567891, + "version": 1, + "versionNonce": 987654322, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 100] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "step2", + "type": "rectangle", + "x": 400, + "y": 380, + "width": 200, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": { "type": 3 }, + "seed": 1234567892, + "version": 1, + "versionNonce": 987654323, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Step 2", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "arrow2", + "type": "arrow", + "x": 500, + "y": 460, + "width": 0, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": { "type": 2 }, + "seed": 1234567893, + "version": 1, + "versionNonce": 987654324, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 100] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "step3", + "type": "rectangle", + "x": 400, + "y": 560, + "width": 200, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#b2f2bb", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": { "type": 3 }, + "seed": 1234567894, + "version": 1, + "versionNonce": 987654325, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Step 3", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/skills/excalidraw-diagram-generator/templates/mindmap-template.excalidraw b/skills/excalidraw-diagram-generator/templates/mindmap-template.excalidraw new file mode 100644 index 0000000..53e382c --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/mindmap-template.excalidraw @@ -0,0 +1,244 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://marketplace.visualstudio.com/items?itemName=pomdtr.excalidraw-editor", + "elements": [ + { + "id": "center", + "type": "rectangle", + "x": 500, + "y": 350, + "width": 200, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd43b", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": { + "type": 3 + }, + "seed": 3333333333, + "version": 3, + "versionNonce": 641024845, + "isDeleted": false, + "boundElements": [ + { + "id": "arrow1", + "type": "arrow" + }, + { + "id": "arrow2", + "type": "arrow" + } + ], + "updated": 1769755916717, + "link": null, + "locked": false, + "text": "Central Topic", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "branch1", + "type": "rectangle", + "x": 250, + "y": 150, + "width": 150, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": { + "type": 3 + }, + "seed": 3333333334, + "version": 2, + "versionNonce": 2040232045, + "isDeleted": false, + "boundElements": [ + { + "id": "arrow1", + "type": "arrow" + } + ], + "updated": 1769755912840, + "link": null, + "locked": false, + "text": "Branch 1", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "arrow1", + "type": "arrow", + "x": 600, + "y": 350, + "width": 246.39999389648438, + "height": 111.20001220703125, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": { + "type": 2 + }, + "seed": 3333333335, + "version": 23, + "versionNonce": 308894189, + "isDeleted": false, + "boundElements": [], + "updated": 1769755914127, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -246.39999389648438, + -111.20001220703125 + ] + ], + "startBinding": { + "elementId": "center", + "focus": 0.5255972360761778, + "gap": 1 + }, + "endBinding": { + "elementId": "branch1", + "focus": 0.48604063201707415, + "gap": 8.79998779296875 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + }, + { + "id": "branch2", + "type": "rectangle", + "x": 750, + "y": 150, + "width": 150, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#96f2d7", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": { + "type": 3 + }, + "seed": 3333333336, + "version": 2, + "versionNonce": 1459929741, + "isDeleted": false, + "boundElements": [ + { + "id": "arrow2", + "type": "arrow" + } + ], + "updated": 1769755916716, + "link": null, + "locked": false, + "text": "Branch 2", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "arrow2", + "type": "arrow", + "x": 600, + "y": 350, + "width": 216, + "height": 112.80001831054688, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": { + "type": 2 + }, + "seed": 3333333337, + "version": 41, + "versionNonce": 1447859213, + "isDeleted": false, + "boundElements": [], + "updated": 1769756030188, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 216, + -112.80001831054688 + ] + ], + "startBinding": { + "elementId": "center", + "focus": -0.48913039421990545, + "gap": 1 + }, + "endBinding": { + "elementId": "branch2", + "focus": -0.5368418212214556, + "gap": 7.199981689453125 + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow" + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/skills/excalidraw-diagram-generator/templates/relationship-template.excalidraw b/skills/excalidraw-diagram-generator/templates/relationship-template.excalidraw new file mode 100644 index 0000000..b2ea0b6 --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/relationship-template.excalidraw @@ -0,0 +1,145 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "entity1", + "type": "rectangle", + "x": 300, + "y": 300, + "width": 180, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": { "type": 3 }, + "seed": 1111111111, + "version": 1, + "versionNonce": 2222222222, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Entity A", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "entity2", + "type": "rectangle", + "x": 600, + "y": 300, + "width": 180, + "height": 100, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#a5d8ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": { "type": 3 }, + "seed": 1111111112, + "version": 1, + "versionNonce": 2222222223, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Entity B", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "relationship", + "type": "arrow", + "x": 480, + "y": 350, + "width": 120, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": { "type": 2 }, + "seed": 1111111113, + "version": 1, + "versionNonce": 2222222224, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [120, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "label", + "type": "text", + "x": 510, + "y": 325, + "width": 60, + "height": 24, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": null, + "seed": 1111111114, + "version": 1, + "versionNonce": 2222222225, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "relates to", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/skills/excalidraw-diagram-generator/templates/sequence-diagram-template.excalidraw b/skills/excalidraw-diagram-generator/templates/sequence-diagram-template.excalidraw new file mode 100644 index 0000000..6602ae2 --- /dev/null +++ b/skills/excalidraw-diagram-generator/templates/sequence-diagram-template.excalidraw @@ -0,0 +1,509 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "object-1", + "type": "rectangle", + "x": 150, + "y": 100, + "width": 120, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#e7f5ff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": null, + "seed": 4001001001, + "version": 1, + "versionNonce": 5002002001, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Client", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "lifeline-1", + "type": "line", + "x": 210, + "y": 150, + "width": 0, + "height": 300, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": null, + "seed": 4001001002, + "version": 1, + "versionNonce": 5002002002, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 300] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "object-2", + "type": "rectangle", + "x": 350, + "y": 100, + "width": 120, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#fff3bf", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": null, + "seed": 4001001003, + "version": 1, + "versionNonce": 5002002003, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Server", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "lifeline-2", + "type": "line", + "x": 410, + "y": 150, + "width": 0, + "height": 300, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": null, + "seed": 4001001004, + "version": 1, + "versionNonce": 5002002004, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 300] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "object-3", + "type": "rectangle", + "x": 550, + "y": 100, + "width": 120, + "height": 50, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#d0f0c0", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a4", + "roundness": null, + "seed": 4001001005, + "version": 1, + "versionNonce": 5002002005, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "Database", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle" + }, + { + "id": "lifeline-3", + "type": "line", + "x": 610, + "y": 150, + "width": 0, + "height": 300, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 4001001006, + "version": 1, + "versionNonce": 5002002006, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [0, 300] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "message-1", + "type": "arrow", + "x": 210, + "y": 200, + "width": 200, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a6", + "roundness": { "type": 2 }, + "seed": 4001001007, + "version": 1, + "versionNonce": 5002002007, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [200, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "message-label-1", + "type": "text", + "x": 250, + "y": 180, + "width": 120, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a7", + "roundness": null, + "seed": 4001001008, + "version": 1, + "versionNonce": 5002002008, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "1: request()", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "activation-1", + "type": "rectangle", + "x": 405, + "y": 200, + "width": 10, + "height": 80, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "#ffd43b", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a8", + "roundness": null, + "seed": 4001001009, + "version": 1, + "versionNonce": 5002002009, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false + }, + { + "id": "message-2", + "type": "arrow", + "x": 415, + "y": 230, + "width": 195, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a9", + "roundness": { "type": 2 }, + "seed": 4001001010, + "version": 1, + "versionNonce": 5002002010, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [195, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "message-label-2", + "type": "text", + "x": 450, + "y": 210, + "width": 120, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a10", + "roundness": null, + "seed": 4001001011, + "version": 1, + "versionNonce": 5002002011, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "2: query()", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "return-message-1", + "type": "arrow", + "x": 610, + "y": 250, + "width": 195, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a11", + "roundness": { "type": 2 }, + "seed": 4001001012, + "version": 1, + "versionNonce": 5002002012, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [-195, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "return-label-1", + "type": "text", + "x": 450, + "y": 255, + "width": 120, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a12", + "roundness": null, + "seed": 4001001013, + "version": 1, + "versionNonce": 5002002013, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "3: result", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + }, + { + "id": "return-message-2", + "type": "arrow", + "x": 410, + "y": 280, + "width": 200, + "height": 0, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a13", + "roundness": { "type": 2 }, + "seed": 4001001014, + "version": 1, + "versionNonce": 5002002014, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "points": [ + [0, 0], + [-200, 0] + ], + "startBinding": null, + "endBinding": null + }, + { + "id": "return-label-2", + "type": "text", + "x": 250, + "y": 285, + "width": 120, + "height": 20, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a14", + "roundness": null, + "seed": 4001001015, + "version": 1, + "versionNonce": 5002002015, + "isDeleted": false, + "boundElements": null, + "updated": 1706659200000, + "link": null, + "locked": false, + "text": "4: response", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top" + } + ], + "appState": { + "viewBackgroundColor": "#ffffff", + "gridSize": 20 + }, + "files": {} +} diff --git a/skills/meoo-cloud/.skill-version b/skills/meoo-cloud/.skill-version new file mode 100644 index 0000000..c2c027f --- /dev/null +++ b/skills/meoo-cloud/.skill-version @@ -0,0 +1 @@ +local \ No newline at end of file diff --git a/skills/meoo-cloud/SKILL.md b/skills/meoo-cloud/SKILL.md new file mode 100644 index 0000000..629c707 --- /dev/null +++ b/skills/meoo-cloud/SKILL.md @@ -0,0 +1,194 @@ +--- +name: meoo-cloud +description: 提供云服务后端能力(数据库、认证、文件存储、Edge Functions)。任何需要数据存储、用户认证、文件上传、实时功能、服务端逻辑的开发都必须先加载此技能进行初始化。例如:开发 CRM 系统、博客网站、聊天应用、文件管理器、AI 应用等。 +--- + +# Meoo Cloud 云服务开发技能 + +## 技能概述 + +Meoo Cloud 基于 Supabase 提供后端云服务(数据库、认证、文件存储、Edge Functions),通过 meoo-cli 命令行工具进行管理和操作。 + +## 快速开始 + +### 必读文档策略(BLOCKING — 不可跳过) + +BLOCKING: 在执行任何 meoo-cli 命令(包括 cloud init)或编写任何云服务代码之前,MUST 先用 Read 工具读取对应的参考文档。违反此规则将导致错误的 RLS 策略、遗漏必传参数、数据库结构不规范等严重问题。 + +| 开发需求 | 必读文档(MUST Read) | 主要CLI命令 | 适用场景 | +|----------|----------|-------------|----------| +| **数据库操作** | `skills/meoo-cloud/references/database.md` | `cloud migrate/query/tables` | 任何项目的基础数据存储 | +| **文件上传存储** | `skills/meoo-cloud/references/storage.md` | `cloud migrate` + 前端代码 | 图片、文档、媒体文件处理 | +| **实时功能聊天** | `skills/meoo-cloud/references/realtime.md` | `cloud migrate` + 订阅代码 | 即时通讯、协作应用 | +| **API 接口、云函数开发** | `skills/meoo-cloud/references/edge-functions.md` | `cloud deploy-function/delete-function` | 自定义后端逻辑、第三方集成 | +| **用户认证系统** | `skills/meoo-cloud/references/authentication.md` | `cloud migrate` + 认证代码 | 登录注册、权限管理、管理员、管理后台 | +| **遇到错误问题** | `skills/meoo-cloud/references/troubleshooting.md` | 各种诊断命令 | 调试和问题解决 | + +### 标准执行流程(严格按顺序,不可跳步) + +1. **分析需求** → 确定属于上表哪个功能模块 +2. **读取文档** → 使用 Read 工具读取对应的参考文档(BLOCKING:此步完成前,严禁执行后续任何步骤) +3. **初始化服务** → `meoo-cli cloud init -d "项目描述"` +4. **按文档实施** → 设计数据结构 → 执行 migrate → 编写业务代码 + +NEVER: 严禁跳过第 2 步直接执行 cloud init 或任何其他 CLI 命令。你的第一个工具调用必须是 Read 参考文档,不是 Bash。 + +## 开发约束与注意事项 + +### 文档优先原则(强制) +- ✅ **正确流程**:Read 参考文档 → cloud init → 按文档编写代码 +- ❌ **严重违规**:cloud init → cloud migrate(跳过文档) +- ❌ **严重违规**:cloud init → 直接编写代码(跳过文档) +- ❌ **严重违规**:直接执行任何 meoo-cli 命令(跳过文档) + +### 技术约束 +- **CLI 操作**:所有云服务管理通过 meoo-cli 命令执行 +- **禁止编辑**:初始化后 `src/supabase/client.ts` 和 `src/supabase/types.ts` 由系统自动生成 +- **保留 Schema**:不得修改 auth/storage/realtime/supabase_functions/vault 等系统表 +### 使用原则 +- **匿名优先**:所有功能默认支持匿名用户操作 +- **禁止Mock**:所有功能基于云服务使用真实的数据,禁止 mock 数据 +- 前端所有云服务操作都必须在 UI 上给用户明确的成功/失败反馈。 + +### AI 服务集成 +如果项目需要 AI 功能,必须先加载对应的技能: +- **文本 AI**:`meoo-llm-ai` - 聊天对话、文本生成、总结翻译等 +- **应用内图片生成**:`meoo-image-gen-ai` - 给用户应用接入文生图、图片编辑等能力;对话中或构建态生成页面素材走 Bash `meoo-cli image-generate` +- **视觉理解**:`meoo-vision-ai` - 图片识别等 + +--- + +## CLI 命令完整参考 +**cloud 命令必须单独调用,不支持与其他命令在一行一起调用** +### cloud init - 初始化云服务 + +**前置条件**:MUST 先用 Read 工具读取与当前需求相关的参考文档(如 `database.md`、`edge-functions.md` 等),理解数据结构和约束后再执行初始化。 +**说明**:初始化 Meoo Cloud 服务,自动安装依赖,生成客户端文件 + +**参数说明**: +- `-d, --description <描述>` (必需):项目功能描述,用于标识项目用途和功能范围,32 字以内 + +**示例**: +```bash +meoo-cli cloud init -d "项目描述" +``` + +### cloud migrate - 数据库结构变更 + +**前置条件**:MUST 先 Read `skills/meoo-cloud/references/database.md`,了解 RLS 策略模板和字段规范后再执行。 + +**说明**:执行数据库结构变更,包括创建表、修改结构、RLS 策略等。--name、--changes 和 --sql 参数都是必传的,缺一不可。 +变更说明中要用 markdown 格式详细说明一下改动点。 + +**SQL 编写建议**: +- 单次 `--sql` 不要过长,复杂变更(建表 / 索引 / RLS / 种子数据)拆成多次 migrate 分别执行,便于定位失败和重试。 +- RLS 策略名用**英文 snake_case 无引号**,格式 `__
`(如 `anon_select_posts`、`users_update_own_data`)。禁止中文或带空格策略名,否则会和外层 `--sql "..."` 双引号冲突。 + +**参数说明**: +- `--sql ` (必需):要执行的 SQL DDL 语句,包括 CREATE TABLE、ALTER TABLE、RLS 策略等 +- `--name <迁移名称>` (必需):如 create_messages_table +- `--changes <变更说明>` (必需):Markdown 格式阅读友好的描述本次变更内容,应包含表结构、字段说明等 + +**示例**: +```bash +# 创建消息表 +meoo-cli cloud migrate --sql " +CREATE TABLE messages ( + id SERIAL PRIMARY KEY, + content TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +);" --name "create_messages_table" --changes "创建 **messages** 表 + +| 字段 | 类型 | 说明 | +|------|------|------| +| id | SERIAL | 主键,自增 | +| content | TEXT | 消息内容 | +| created_at | TIMESTAMPTZ | 创建时间 |" + +# 2. 启用 RLS 并添加访问策略 +meoo-cli cloud migrate --sql " +ALTER TABLE messages ENABLE ROW LEVEL SECURITY; + +CREATE POLICY anon_select_messages ON messages + FOR SELECT + USING (true); + +CREATE POLICY anon_insert_messages ON messages + FOR INSERT + WITH CHECK (true); +" --name "add_messages_rls" --changes "为 **messages** 表启用 RLS 并添加匿名访问策略 + +| 策略名 | 操作 | 条件 | 说明 | +|--------|------|------|------| +| anon_select_messages | SELECT | USING (true) | 任何人都能读取消息 | +| anon_insert_messages | INSERT | WITH CHECK (true) | 任何人都能发送消息 | + +``` + +### cloud query - 数据查询操作 + +**说明**:执行 SELECT 查询、INSERT/UPDATE/DELETE 数据操作、数据验证和调试,不可用于 DDL + +**参数说明**: +- `--sql ` (必需):要执行的 SQL DML 语句,支持 SELECT、INSERT、UPDATE、DELETE 操作 + +**示例**: +```bash +meoo-cli cloud query --sql "SELECT * FROM users LIMIT 10" +meoo-cli cloud query --sql "INSERT INTO users (name) VALUES ('张三')" +``` + +### cloud tables - 查看表结构 + +**说明**:列出所有表及其结构、字段定义和约束 + +**示例**: +```bash +meoo-cli cloud tables +``` + +### cloud status - 查看云服务状态 + +**说明**:查询当前项目云服务实例状态和 Supabase 的基本信息,确认服务是否正常运行 + +**示例**: +```bash +meoo-cli cloud status +``` + +### deploy-function - 部署函数 + +**前置条件**:MUST 先 Read `skills/meoo-cloud/references/edge-functions.md`,了解函数结构和环境变量后再部署。 + +**说明**:部署 Edge Function 到 Meoo Cloud,每次代码修改后必须重新部署 + +**参数说明**: +- `-n, --name <函数名称>` (必需):要部署的 Edge Function 名称,需与 functions/ 目录下的文件夹名称一致 +- `-j, --jwt <布尔值>` (可选):是否启用 JWT 身份验证,默认为 true。设置为 false 时允许匿名访问 + +**示例**: +```bash +meoo-cli cloud deploy-function -n my-function +meoo-cli cloud deploy-function -n my-function -j false +``` + +### delete-function - 删除函数 + +**说明**:删除已部署的 Edge Function + +**参数说明**: +- `-n, --name <函数名称>` (必需):要删除的 Edge Function 名称 + +**示例**: +```bash +meoo-cli cloud delete-function -n my-function +``` + +### list-functions - 查看函数和环境变量 + +**说明**:列出已部署的 Edge Functions,显示可用的环境变量和 secrets + +**示例**: +```bash +meoo-cli cloud list-functions +``` diff --git a/skills/meoo-cloud/references/authentication.md b/skills/meoo-cloud/references/authentication.md new file mode 100644 index 0000000..e8f8f9b --- /dev/null +++ b/skills/meoo-cloud/references/authentication.md @@ -0,0 +1,203 @@ +# 用户认证 + +> 仅在用户明确要求登录功能时才实现认证。默认设计应支持匿名访问。 + +## 认证方法 + +### 用户名登录(默认方式) +**这是 Meoo 项目的标准登录方式**,用户使用用户名登录,要自动生成 `{username}@meoo.local` 虚拟邮箱以兼容 Supabase 认证系统。 + +### 手机号登录 +使用手机号作为用户名,同样自动生成 `{username}@meoo.local` 虚拟邮箱。 + +### 邮箱登录 +仅在用户明确要求使用真实邮箱时才使用的标准邮箱/密码认证。 + +## ❌ 不支持的登录方式 + +以下登录方式 **不支持**,如果用户要求请明确告知无法实现: + +- **手机验证码登录**:不支持短信验证码 +- **第三方登录**:不支持微信、QQ、支付宝、GitHub 等第三方登录 +- **扫码登录**:不支持二维码扫码登录 +- **指纹/面部识别**:不支持生物识别登录 +- **无密码登录**:不支持魔法链接等无密码方式 + +**如果用户要求以上登录方式,请明确说明 Meoo Cloud 目前仅支持用户名/密码的登录方式。** + +## 实现要点 + +### 1. 存储完整会话对象 +```typescript +const [session, setSession] = useState(null); +``` + +### 2. 用户名认证示例(标准实现) +**必须使用此方式实现登录注册**,除非用户明确要求使用真实邮箱: +```typescript +// 注册 +await supabase.auth.signUp({ + email: `${username}@meoo.local`, + password, + options: { data: { username } } +}); + +// 登录 +await supabase.auth.signInWithPassword({ + email: `${username}@meoo.local`, + password, +}); +``` + +### 3. 认证状态监听 +```typescript +// ✅ 正确:同步状态更新 +supabase.auth.onAuthStateChange((event, session) => { + setSession(session); + setUser(session?.user ?? null); + + // 延迟任何 Supabase 调用 + if (session?.user) { + setTimeout(() => { + fetchUserProfile(session.user.id); + }, 0); + } +}); + +// ❌ 错误:会导致死锁 +supabase.auth.onAuthStateChange(async (event, session) => { + const profile = await supabase.from('profiles').select()... // 死锁 +}); +``` + +## 用户数据表设计 + +### 用户配置文件表 + +**重要**:只有当用户明确要求注册和登录功能时,才创建 `public.profiles` 表与 `auth.users` 表关联。 + +```sql +CREATE TABLE public.profiles ( + id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, + username TEXT UNIQUE NOT NULL, + avatar_url TEXT, + created_at TIMESTAMP DEFAULT NOW() +); + +ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY; + +-- 用户只能查看和修改自己的配置文件 +CREATE POLICY users_select_own_profile ON public.profiles +FOR SELECT USING (auth.uid() = id); + +CREATE POLICY users_update_own_profile ON public.profiles +FOR UPDATE USING (auth.uid() = id); +``` + +### 自动创建配置文件触发器 + +设置触发器在用户注册时自动创建配置文件: + +```sql +-- 插入 public.profiles 的函数 +CREATE OR REPLACE FUNCTION public.handle_new_user() +RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public +AS $$ +BEGIN + INSERT INTO public.profiles (id, username, avatar_url) + VALUES ( + NEW.id, + NEW.raw_user_meta_data ->> 'username', + NEW.raw_user_meta_data ->> 'avatar_url' + ); + RETURN NEW; +END; +$$; + +-- 创建触发器 +CREATE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); +``` + +> 不要忘记 `SECURITY DEFINER`! + +### 业务表与用户关联 + +如果需要在业务表中引用用户信息,请引用 profiles 表的 `id`: + +```sql +CREATE TABLE public.user_posts ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES public.profiles(id) ON DELETE CASCADE, + title TEXT NOT NULL, + content TEXT NOT NULL, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +ALTER TABLE public.user_posts ENABLE ROW LEVEL SECURITY; + +-- 用户只能查看和操作自己的帖子 +CREATE POLICY users_select_own_posts ON public.user_posts +FOR SELECT USING (user_id = auth.uid()); + +CREATE POLICY users_insert_own_posts ON public.user_posts +FOR INSERT WITH CHECK (user_id = auth.uid()); + +CREATE POLICY users_update_own_posts ON public.user_posts +FOR UPDATE USING (user_id = auth.uid()); +``` + +### 角色和权限设计 + +角色**必须**存储在独立表中,不要在 profiles 表中存储角色: + +```sql +CREATE TYPE public.app_role AS ENUM ('admin', 'moderator', 'user'); + +CREATE TABLE public.user_roles ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID REFERENCES auth.users(id) ON DELETE CASCADE NOT NULL, + role app_role NOT NULL, + UNIQUE (user_id, role) +); + +ALTER TABLE public.user_roles ENABLE ROW LEVEL SECURITY; + +-- 辅助函数检查用户角色 +CREATE OR REPLACE FUNCTION public.has_role(_user_id UUID, _role app_role) +RETURNS BOOLEAN LANGUAGE SQL STABLE SECURITY DEFINER SET search_path = public +AS $$ + SELECT EXISTS ( + SELECT 1 FROM public.user_roles + WHERE user_id = _user_id AND role = _role + ) +$$; +``` + +## 创建测试用户 + +```sql +INSERT INTO auth.users ( + instance_id, id, aud, role, email, + encrypted_password, email_confirmed_at, + created_at, updated_at, + confirmation_token, recovery_token, + email_change_token_new, email_change, + raw_app_meta_data, raw_user_meta_data, + is_super_admin +) VALUES ( + '00000000-0000-0000-0000-000000000000', + gen_random_uuid(), 'authenticated', 'authenticated', + 'testuser@meoo.local', + crypt('password123', gen_salt('bf')), + NOW(), NOW(), NOW(), + '', '', '', '', + '{"provider":"email","providers":["email"]}'::jsonb, + '{"username":"testuser"}'::jsonb, + false +); +``` + +必需字段:`instance_id`、`::jsonb` 类型转换、空字符串令牌(非 NULL) \ No newline at end of file diff --git a/skills/meoo-cloud/references/database.md b/skills/meoo-cloud/references/database.md new file mode 100644 index 0000000..bc39548 --- /dev/null +++ b/skills/meoo-cloud/references/database.md @@ -0,0 +1,184 @@ +# 数据库设计指南 + +## 设计原则 + +### 匿名优先 +默认支持匿名访问。用户应能在不认证的情况下操作数据,登录仅在明确需要时才实现。 + +## 表设计规范 + +- 考虑列的**默认值**和**可空性**,避免插入数据时出错 +- 与现有数据兼容,迭代时考虑现有数据结构 +- Supabase 默认限制每次查询 **1000 行**,大数据集需要分页 +- 所有表都需要启用 RLS,且 RLS 权限设计要与功能实现保持一致 +- 使用 UUID 作为 id,在实现功能代码时要注意字段格式与数据库字段类型一致 + +## 约束和验证 + +### 使用触发器而非 CHECK 约束 +```sql +-- ✅ 推荐:验证触发器 +CREATE OR REPLACE FUNCTION validate_expire_at() +RETURNS TRIGGER LANGUAGE plpgsql AS $$ +BEGIN + IF NEW.expire_at <= now() THEN + RAISE EXCEPTION 'expire_at must be in the future'; + END IF; + RETURN NEW; +END; +$$; + +CREATE TRIGGER validate_expire_at_trigger + BEFORE INSERT OR UPDATE ON bookings + FOR EACH ROW EXECUTE FUNCTION validate_expire_at(); +``` + +## RLS 策略设计 + +### 基本 RLS 配置 +```sql +ALTER TABLE your_table ENABLE ROW LEVEL SECURITY; +``` + +### 策略命名规范 + +用**英文 snake_case 无引号**,格式 `__
`,例:`anon_select_posts`、`users_update_own_data`、`admins_select_profiles`。不要用中文或带空格的名字(会和 `--sql "..."` 外层引号冲突)。 + +### 匿名友好的策略 +```sql +-- 所有人可查看 +CREATE POLICY anon_select_posts ON posts FOR SELECT USING (true); + +-- 所有人可创建 +CREATE POLICY anon_insert_posts ON posts FOR INSERT WITH CHECK (true); +``` + +### 用户数据策略(需要登录功能时) +```sql +-- 用户只能查看自己的数据 +CREATE POLICY users_select_own_data ON user_data +FOR SELECT USING (user_id = auth.uid()); + +-- 用户只能修改自己的数据 +CREATE POLICY users_update_own_data ON user_data +FOR UPDATE USING (user_id = auth.uid()); +``` + +### 避免 RLS 权限冲突 + +**重要**:点赞、评论等功能必须创建独立表,不能在原表中直接添加字段,因为会导致 RLS 权限冲突。 + +```sql +-- 假设原表有严格的 UPDATE 权限 +CREATE POLICY users_update_own_messages ON messages +FOR UPDATE USING (user_id = auth.uid()); + +-- ❌ 错误:在原表中添加点赞字段 +-- 点赞操作变成 UPDATE messages,受限于上面的策略 +ALTER TABLE messages ADD COLUMN likes INTEGER DEFAULT 0; + +-- ✅ 正确:创建独立表 +-- 点赞操作是 INSERT message_likes,可以设置不同的权限 +CREATE TABLE message_likes ( + message_id UUID REFERENCES messages(id) ON DELETE CASCADE, + user_id UUID, + created_at TIMESTAMP DEFAULT NOW() +); + +-- 任何人都能点赞 +CREATE POLICY anon_insert_message_likes ON message_likes FOR INSERT WITH CHECK (true); +``` + +## 用户配置文件(需要登录功能时) + +```sql +CREATE TABLE public.profiles ( + id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE, + username TEXT UNIQUE NOT NULL, + avatar_url TEXT, + created_at TIMESTAMP DEFAULT NOW() +); + +ALTER TABLE public.profiles ENABLE ROW LEVEL SECURITY; + +-- 自动创建配置文件的触发器 +CREATE OR REPLACE FUNCTION public.handle_new_user() +RETURNS TRIGGER LANGUAGE plpgsql SECURITY DEFINER SET search_path = public +AS $$ +BEGIN + INSERT INTO public.profiles (id, username, avatar_url) + VALUES ( + NEW.id, + NEW.raw_user_meta_data ->> 'username', + NEW.raw_user_meta_data ->> 'avatar_url' + ); + RETURN NEW; +END; +$$; + +CREATE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); +``` + +## 避免修改保留模式 +不要操作这些 Supabase 保留模式: +- `auth` · `storage` · `realtime` · `supabase_functions` · `vault` + +可以引用 `auth.users(id)` 作为外键,但不能修改模式结构。 + +## 查询最佳实践 +**注意**:必须通过友好的 Toast 交互提醒用户数据操作结果,特别是操作失败的情况。 +```typescript +// ✅ 正确:必须检查 error 对象 +const { data, error } = await supabase + .from('profiles') + .select() + .eq('id', id) + .maybeSingle(); + +if (error) { + console.error('数据库错误:', error.message); + throw new Error(`查询失败: ${error.message}`); +} + +if (!data) { /* 处理未找到的情况 */ } + +// ✅ 分页查询 +const { data, error } = await supabase + .from('posts') + .select() + .range(0, 49); // 前50条记录 + +if (error) throw new Error(error.message); + +// ❌ 错误:忽略 error 对象会导致静默失败 +const { data } = await supabase.from('profiles').select(); +``` + +## 常见问题 + +### RLS 策略必须匹配功能 +如果应用没有登录功能,数据应该公开可见。策略名称应反映实际功能需求。 + +### 避免 RLS 无限递归 +RLS 无限递归通常发生在策略互相引用时,必须避免会导致无限递归的错误情况: +```sql +-- ❌ 错误:导致无限递归 +CREATE POLICY admins_select_profiles ON profiles +FOR SELECT USING ( + (SELECT role FROM profiles WHERE id = auth.uid()) = 'admin' +); + +-- ✅ 正确:使用 SECURITY DEFINER 函数 +CREATE FUNCTION public.has_role(_user_id UUID, _role TEXT) +RETURNS BOOLEAN LANGUAGE SQL STABLE SECURITY DEFINER SET search_path = public +AS $$ + SELECT EXISTS (SELECT 1 FROM public.user_roles WHERE user_id = _user_id AND role = _role) +$$; + +CREATE POLICY admins_select_profiles ON profiles +FOR SELECT USING (public.has_role(auth.uid(), 'admin')); +``` + +**关键**:权限函数必须使用 `SECURITY DEFINER`,基础权限表策略保持简单。 \ No newline at end of file diff --git a/skills/meoo-cloud/references/edge-functions.md b/skills/meoo-cloud/references/edge-functions.md new file mode 100644 index 0000000..ce50cc1 --- /dev/null +++ b/skills/meoo-cloud/references/edge-functions.md @@ -0,0 +1,149 @@ +# Edge Functions + +Edge Functions 用于服务端 Deno TypeScript 逻辑,如 API 端点、Webhook 处理、第三方集成等需要后端支持的场景。 + +## 函数开发流程 + +1. **编写代码**:在 `/functions/{functionName}/index.ts` 中编写 Deno TypeScript 代码 +2. **部署函数**:执行 `meoo-cli cloud deploy-function -n {functionName}` 命令部署函数 +3. **重新部署**:每次代码修改后必须重新运行 deploy 命令 + +## 函数结构 + +```typescript +Deno.serve(async (req) => { + // CORS 预检 + if (req.method === 'OPTIONS') { + return new Response('ok', { + headers: { + 'Access-Control-Allow-Origin': '*', + 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS', + 'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type', + }, + }); + } + + const corsHeaders = { + 'Content-Type': 'application/json', + 'Access-Control-Allow-Origin': '*', + }; + + try { + const body = await req.json(); + // 业务逻辑 + return new Response(JSON.stringify({ data: body }), { headers: corsHeaders }); + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + status: 400, + headers: corsHeaders, + }); + } +}); +``` + +## 环境变量 + +**系统变量**(始终可用): +- `SUPABASE_URL` — Supabase API URL(私有网络 URL) +- `SUPABASE_ANON_KEY` — 公共匿名密钥 +- `SUPABASE_SERVICE_ROLE_KEY` — 服务角色密钥(保密) +- `SUPABASE_DB_URL` — PostgreSQL 连接 URL + +**自定义变量**:项目可能配置了额外的密钥。使用 `meoo-cli cloud list-functions` 查看完整的环境变量列表。 + +**重要**:只使用实际存在的环境变量,引用不存在的变量会导致运行时错误。 + +## Supabase 客户端 + +```typescript +import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'; + +// 管理员客户端(绕过 RLS) +const supabaseAdmin = createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, +); + +// 用户客户端(遵守 RLS,使用前端 Auth header) +const authHeader = req.headers.get('Authorization')!; +const supabase = createClient( + Deno.env.get('SUPABASE_URL')!, + Deno.env.get('SUPABASE_SERVICE_ROLE_KEY')!, + { global: { headers: { Authorization: authHeader } } }, +); +``` + +## 前端调用 + +**推荐使用 `fetch` 调用** — fetch 能提供完整的错误信息和更好的调试能力,非必要不建议使用 sdk invoke 调用。 +**注意**:必须通过友好的 Toast 交互提醒用户数据操作结果,特别是操作失败的情况。 + +```typescript +import { supabase, getSupabaseUrl } from 'src/supabase/client.ts'; + +// 获取认证 headers +const session = (await supabase.auth.getSession()).data.session; +const authHeaders = session ? { Authorization: `Bearer ${session.access_token}` } : {}; + +// 基本函数调用 +const response = await fetch(`${getSupabaseUrl()}/functions/v1/hello-world`, { + method: 'POST', + headers: { 'Content-Type': 'application/json', ...authHeaders }, + body: JSON.stringify({ name: 'world' }), +}); + +const result = await response.json(); +if (!response.ok) { + console.error('Function error:', result); +} +``` + +> 使用 `fetch` 时,如果函数需要用户身份,**必须**手动附加 `Authorization` header。 + +## 部署管理 + +1. **部署**:`meoo-cli cloud deploy-function -n {functionName}` +2. **删除**:`meoo-cli cloud delete-function -n {functionName}` + +> `/functions/{functionName}/` 下的代码修改是本地的,直到再次运行 deploy 命令才会更新在线函数。 + +# 适用场景 + +- 需要服务器端逻辑(如第三方 API 调用、密钥保护) +- **浏览器真实 CORS 阻止前端 fetch/XHR 调用第三方或不可控外部 API,需要改为受限服务端代理** +- API 端点 / Webhook 处理 +- 定时任务 / 后台数据处理 +- 需要使用 Service Role Key 绕过 RLS 的操作 + +### 何时使用 Edge Function 做 CORS 代理 + +以下场景应使用 Edge Function 做受限服务端代理: +- 预览中确认 CORS 错误来自前端 fetch/XHR 调用第三方或不可控外部 API +- 用户要求 "把这个 API 调用移到后端,避免浏览器 CORS 拦截" +- 用户要求 "用 Edge Function 代理这个外部 API,同时把 API key 留在后端" + +**不要** 为以下场景生成代理: +- 静态资源 CDN(图片、字体、CSS)— 低价值,应直接用 `` 或 CSS +- 脚本类 CDN(`cdn.jsdelivr.net`、`unpkg.com`)— 应用 `