diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a3548cf --- /dev/null +++ b/.editorconfig @@ -0,0 +1,10 @@ +root = true + +[*.cs] +indent_style = space +indent_size = 4 +dotnet_sort_system_directives_first = true + +[*.{ts,tsx,js,jsx,json}] +indent_style = space +indent_size = 2 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c0a58e8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,57 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + dotnet-tests: + name: dotnet-tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + # The MAUI host project (src/AppName.Maui) is intentionally not built here: + # it requires the MAUI workload + platform SDKs. The platform-neutral layers + # (Domain / Application / Infrastructure) carry the unit tests and build on + # a plain runner. + - name: Test Domain + run: dotnet test tests/AppName.Domain.Tests/AppName.Domain.Tests.csproj --configuration Release + + - name: Test Application + run: dotnet test tests/AppName.Application.Tests/AppName.Application.Tests.csproj --configuration Release + + - name: Test Infrastructure + run: dotnet test tests/AppName.Infrastructure.Tests/AppName.Infrastructure.Tests.csproj --configuration Release + + web-tests: + name: web-tests + runs-on: ubuntu-latest + defaults: + run: + working-directory: src/web + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '24' + cache: 'npm' + cache-dependency-path: src/web/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Run tests + run: npm test + + - name: Build + run: npm run build diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3064557 --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# .NET +bin/ +obj/ +*.user +.vs/ + +# MAUI / mobile +*.binlog + +# Node / Vite +node_modules/ +dist/ +src/AppName.Maui/Resources/Raw/web/ + +# OS +.DS_Store diff --git a/AppName.sln b/AppName.sln new file mode 100644 index 0000000..ce64bfc --- /dev/null +++ b/AppName.sln @@ -0,0 +1,131 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppName.Domain", "src\AppName.Domain\AppName.Domain.csproj", "{BE21E412-1CC0-4DDD-9604-733B75B00B26}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{0AB3BF05-4346-4AA6-1389-037BE0695223}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppName.Domain.Tests", "tests\AppName.Domain.Tests\AppName.Domain.Tests.csproj", "{CA24E7F9-72FD-4649-8133-66E21452E65C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppName.Application", "src\AppName.Application\AppName.Application.csproj", "{8BE7AB9B-9344-4252-AA1C-825487314E58}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppName.Application.Tests", "tests\AppName.Application.Tests\AppName.Application.Tests.csproj", "{7F395C90-4E7B-4827-BD62-762E4AA60AE4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppName.Infrastructure", "src\AppName.Infrastructure\AppName.Infrastructure.csproj", "{B2CEEB9C-E4D3-4578-ABF3-261A249E0611}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppName.Infrastructure.Tests", "tests\AppName.Infrastructure.Tests\AppName.Infrastructure.Tests.csproj", "{0F95507E-D2E4-48DD-AB43-70A0E26195E9}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AppName.Maui", "src\AppName.Maui\AppName.Maui.csproj", "{89E211BB-FCAF-4A7D-8252-314AE63C4930}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BE21E412-1CC0-4DDD-9604-733B75B00B26}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE21E412-1CC0-4DDD-9604-733B75B00B26}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE21E412-1CC0-4DDD-9604-733B75B00B26}.Debug|x64.ActiveCfg = Debug|Any CPU + {BE21E412-1CC0-4DDD-9604-733B75B00B26}.Debug|x64.Build.0 = Debug|Any CPU + {BE21E412-1CC0-4DDD-9604-733B75B00B26}.Debug|x86.ActiveCfg = Debug|Any CPU + {BE21E412-1CC0-4DDD-9604-733B75B00B26}.Debug|x86.Build.0 = Debug|Any CPU + {BE21E412-1CC0-4DDD-9604-733B75B00B26}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE21E412-1CC0-4DDD-9604-733B75B00B26}.Release|Any CPU.Build.0 = Release|Any CPU + {BE21E412-1CC0-4DDD-9604-733B75B00B26}.Release|x64.ActiveCfg = Release|Any CPU + {BE21E412-1CC0-4DDD-9604-733B75B00B26}.Release|x64.Build.0 = Release|Any CPU + {BE21E412-1CC0-4DDD-9604-733B75B00B26}.Release|x86.ActiveCfg = Release|Any CPU + {BE21E412-1CC0-4DDD-9604-733B75B00B26}.Release|x86.Build.0 = Release|Any CPU + {CA24E7F9-72FD-4649-8133-66E21452E65C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA24E7F9-72FD-4649-8133-66E21452E65C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA24E7F9-72FD-4649-8133-66E21452E65C}.Debug|x64.ActiveCfg = Debug|Any CPU + {CA24E7F9-72FD-4649-8133-66E21452E65C}.Debug|x64.Build.0 = Debug|Any CPU + {CA24E7F9-72FD-4649-8133-66E21452E65C}.Debug|x86.ActiveCfg = Debug|Any CPU + {CA24E7F9-72FD-4649-8133-66E21452E65C}.Debug|x86.Build.0 = Debug|Any CPU + {CA24E7F9-72FD-4649-8133-66E21452E65C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA24E7F9-72FD-4649-8133-66E21452E65C}.Release|Any CPU.Build.0 = Release|Any CPU + {CA24E7F9-72FD-4649-8133-66E21452E65C}.Release|x64.ActiveCfg = Release|Any CPU + {CA24E7F9-72FD-4649-8133-66E21452E65C}.Release|x64.Build.0 = Release|Any CPU + {CA24E7F9-72FD-4649-8133-66E21452E65C}.Release|x86.ActiveCfg = Release|Any CPU + {CA24E7F9-72FD-4649-8133-66E21452E65C}.Release|x86.Build.0 = Release|Any CPU + {8BE7AB9B-9344-4252-AA1C-825487314E58}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BE7AB9B-9344-4252-AA1C-825487314E58}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BE7AB9B-9344-4252-AA1C-825487314E58}.Debug|x64.ActiveCfg = Debug|Any CPU + {8BE7AB9B-9344-4252-AA1C-825487314E58}.Debug|x64.Build.0 = Debug|Any CPU + {8BE7AB9B-9344-4252-AA1C-825487314E58}.Debug|x86.ActiveCfg = Debug|Any CPU + {8BE7AB9B-9344-4252-AA1C-825487314E58}.Debug|x86.Build.0 = Debug|Any CPU + {8BE7AB9B-9344-4252-AA1C-825487314E58}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BE7AB9B-9344-4252-AA1C-825487314E58}.Release|Any CPU.Build.0 = Release|Any CPU + {8BE7AB9B-9344-4252-AA1C-825487314E58}.Release|x64.ActiveCfg = Release|Any CPU + {8BE7AB9B-9344-4252-AA1C-825487314E58}.Release|x64.Build.0 = Release|Any CPU + {8BE7AB9B-9344-4252-AA1C-825487314E58}.Release|x86.ActiveCfg = Release|Any CPU + {8BE7AB9B-9344-4252-AA1C-825487314E58}.Release|x86.Build.0 = Release|Any CPU + {7F395C90-4E7B-4827-BD62-762E4AA60AE4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7F395C90-4E7B-4827-BD62-762E4AA60AE4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7F395C90-4E7B-4827-BD62-762E4AA60AE4}.Debug|x64.ActiveCfg = Debug|Any CPU + {7F395C90-4E7B-4827-BD62-762E4AA60AE4}.Debug|x64.Build.0 = Debug|Any CPU + {7F395C90-4E7B-4827-BD62-762E4AA60AE4}.Debug|x86.ActiveCfg = Debug|Any CPU + {7F395C90-4E7B-4827-BD62-762E4AA60AE4}.Debug|x86.Build.0 = Debug|Any CPU + {7F395C90-4E7B-4827-BD62-762E4AA60AE4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7F395C90-4E7B-4827-BD62-762E4AA60AE4}.Release|Any CPU.Build.0 = Release|Any CPU + {7F395C90-4E7B-4827-BD62-762E4AA60AE4}.Release|x64.ActiveCfg = Release|Any CPU + {7F395C90-4E7B-4827-BD62-762E4AA60AE4}.Release|x64.Build.0 = Release|Any CPU + {7F395C90-4E7B-4827-BD62-762E4AA60AE4}.Release|x86.ActiveCfg = Release|Any CPU + {7F395C90-4E7B-4827-BD62-762E4AA60AE4}.Release|x86.Build.0 = Release|Any CPU + {B2CEEB9C-E4D3-4578-ABF3-261A249E0611}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2CEEB9C-E4D3-4578-ABF3-261A249E0611}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2CEEB9C-E4D3-4578-ABF3-261A249E0611}.Debug|x64.ActiveCfg = Debug|Any CPU + {B2CEEB9C-E4D3-4578-ABF3-261A249E0611}.Debug|x64.Build.0 = Debug|Any CPU + {B2CEEB9C-E4D3-4578-ABF3-261A249E0611}.Debug|x86.ActiveCfg = Debug|Any CPU + {B2CEEB9C-E4D3-4578-ABF3-261A249E0611}.Debug|x86.Build.0 = Debug|Any CPU + {B2CEEB9C-E4D3-4578-ABF3-261A249E0611}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2CEEB9C-E4D3-4578-ABF3-261A249E0611}.Release|Any CPU.Build.0 = Release|Any CPU + {B2CEEB9C-E4D3-4578-ABF3-261A249E0611}.Release|x64.ActiveCfg = Release|Any CPU + {B2CEEB9C-E4D3-4578-ABF3-261A249E0611}.Release|x64.Build.0 = Release|Any CPU + {B2CEEB9C-E4D3-4578-ABF3-261A249E0611}.Release|x86.ActiveCfg = Release|Any CPU + {B2CEEB9C-E4D3-4578-ABF3-261A249E0611}.Release|x86.Build.0 = Release|Any CPU + {0F95507E-D2E4-48DD-AB43-70A0E26195E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0F95507E-D2E4-48DD-AB43-70A0E26195E9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F95507E-D2E4-48DD-AB43-70A0E26195E9}.Debug|x64.ActiveCfg = Debug|Any CPU + {0F95507E-D2E4-48DD-AB43-70A0E26195E9}.Debug|x64.Build.0 = Debug|Any CPU + {0F95507E-D2E4-48DD-AB43-70A0E26195E9}.Debug|x86.ActiveCfg = Debug|Any CPU + {0F95507E-D2E4-48DD-AB43-70A0E26195E9}.Debug|x86.Build.0 = Debug|Any CPU + {0F95507E-D2E4-48DD-AB43-70A0E26195E9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0F95507E-D2E4-48DD-AB43-70A0E26195E9}.Release|Any CPU.Build.0 = Release|Any CPU + {0F95507E-D2E4-48DD-AB43-70A0E26195E9}.Release|x64.ActiveCfg = Release|Any CPU + {0F95507E-D2E4-48DD-AB43-70A0E26195E9}.Release|x64.Build.0 = Release|Any CPU + {0F95507E-D2E4-48DD-AB43-70A0E26195E9}.Release|x86.ActiveCfg = Release|Any CPU + {0F95507E-D2E4-48DD-AB43-70A0E26195E9}.Release|x86.Build.0 = Release|Any CPU + {89E211BB-FCAF-4A7D-8252-314AE63C4930}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {89E211BB-FCAF-4A7D-8252-314AE63C4930}.Debug|Any CPU.Build.0 = Debug|Any CPU + {89E211BB-FCAF-4A7D-8252-314AE63C4930}.Debug|x64.ActiveCfg = Debug|Any CPU + {89E211BB-FCAF-4A7D-8252-314AE63C4930}.Debug|x64.Build.0 = Debug|Any CPU + {89E211BB-FCAF-4A7D-8252-314AE63C4930}.Debug|x86.ActiveCfg = Debug|Any CPU + {89E211BB-FCAF-4A7D-8252-314AE63C4930}.Debug|x86.Build.0 = Debug|Any CPU + {89E211BB-FCAF-4A7D-8252-314AE63C4930}.Release|Any CPU.ActiveCfg = Release|Any CPU + {89E211BB-FCAF-4A7D-8252-314AE63C4930}.Release|Any CPU.Build.0 = Release|Any CPU + {89E211BB-FCAF-4A7D-8252-314AE63C4930}.Release|x64.ActiveCfg = Release|Any CPU + {89E211BB-FCAF-4A7D-8252-314AE63C4930}.Release|x64.Build.0 = Release|Any CPU + {89E211BB-FCAF-4A7D-8252-314AE63C4930}.Release|x86.ActiveCfg = Release|Any CPU + {89E211BB-FCAF-4A7D-8252-314AE63C4930}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {BE21E412-1CC0-4DDD-9604-733B75B00B26} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {CA24E7F9-72FD-4649-8133-66E21452E65C} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {8BE7AB9B-9344-4252-AA1C-825487314E58} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {7F395C90-4E7B-4827-BD62-762E4AA60AE4} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {B2CEEB9C-E4D3-4578-ABF3-261A249E0611} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {0F95507E-D2E4-48DD-AB43-70A0E26195E9} = {0AB3BF05-4346-4AA6-1389-037BE0695223} + {89E211BB-FCAF-4A7D-8252-314AE63C4930} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + EndGlobalSection +EndGlobal diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..3c1cb80 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,13 @@ + + + enable + enable + latest + false + true + + false + + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..82be5c6 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/README.md b/README.md new file mode 100644 index 0000000..72bd15c --- /dev/null +++ b/README.md @@ -0,0 +1,159 @@ +# AppName + +.NET MAUI app hosting a React (Vite + TypeScript) UI via `HybridWebView`, +structured with clean architecture. + +## Layout + +``` +src/ + AppName.Domain/ — entities, value objects, domain interfaces (no dependencies) + AppName.Application/ — use cases, DTOs, ports (depends on Domain only) + AppName.Infrastructure/ — EF Core/SQLite repository, DI wiring (implements ports) + AppName.Maui/ — MAUI host, HybridWebView, JS↔C# bridge, DI composition root + web/ — React + Vite + TypeScript UI (builds into AppName.Maui/Resources/Raw/web) +tests/ + AppName.Domain.Tests/ + AppName.Application.Tests/ + AppName.Infrastructure.Tests/ +``` + +The `src/AppName.Maui/Resources/Raw/web` directory is gitignored; it is produced +by `npm run build` in `src/web`. + +## Prerequisites + +- **.NET 10 SDK** (`dotnet --version` → `10.x`) +- **MAUI workload:** + - Linux: `dotnet workload install maui-android` + (full `maui` workload is not supported on Linux; only `net10.0-android` builds on Linux) + - Windows/macOS: `dotnet workload install maui` + (installs all targets; iOS/Mac Catalyst require macOS; Windows target requires Windows) +- **Node 24+** and npm (for the React front-end) +- **Platform SDKs** for the targets you want to deploy to: + - Android: Android SDK (required even on Linux) + - iOS / Mac Catalyst: macOS + Xcode + - Windows: Windows 10/11 + Windows App SDK + +## Platform build matrix + +| Target | Linux | macOS | Windows | +|-----------------------|-------|-------|---------| +| `net10.0-android` | yes | yes | yes | +| `net10.0-ios` | — | yes | — | +| `net10.0-maccatalyst` | — | yes | — | +| `net10.0-windows*` | — | — | yes | + +The `.csproj` gates `ios`/`maccatalyst` behind `!IsOSPlatform('linux')` and +the Windows target behind `IsOSPlatform('windows')`. + +## Run (bundled assets — production-like) + +```bash +# Build the React app first (or let the MSBuild target do it automatically): +cd src/web && npm install && npm run build && cd ../.. + +# Then build and deploy the MAUI app for Android: +dotnet build src/AppName.Maui/AppName.Maui.csproj -f net10.0-android +# Deploy/run via your IDE, or: +dotnet build src/AppName.Maui/AppName.Maui.csproj -f net10.0-android -t:Run +``` + +The MAUI build automatically runs `npm install && npm run build` in `src/web` +via the `BuildReactApp` MSBuild target (`BeforeTargets="BeforeBuild"`). +Skip this step when using the Vite dev server: `-p:SkipReactBuild=true`. + +## Develop the UI with hot reload (Vite dev server) + +```bash +cd src/web && npm run dev # serves at http://localhost:5173 +``` + +Build MAUI with `-p:SkipReactBuild=true` to skip the Vite build step during +development. To point the `HybridWebView` at the dev server, add a dev-time +URL override in `MainPage` (see the comment stub there). + +## Test + +```bash +# C# — build each test project then run (single-process to avoid OOM on constrained hosts): +export DOTNET_CLI_TELEMETRY_OPTOUT=1 MSBUILDDISABLENODEREUSE=1 DOTNET_CLI_USE_MSBUILD_SERVER=0 +dotnet build tests/AppName.Domain.Tests -m:1 +dotnet test tests/AppName.Domain.Tests --no-build -m:1 # 4 tests + +dotnet build tests/AppName.Application.Tests -m:1 +dotnet test tests/AppName.Application.Tests --no-build -m:1 # 2 tests + +dotnet build tests/AppName.Infrastructure.Tests -m:1 +dotnet test tests/AppName.Infrastructure.Tests --no-build -m:1 # 1 test + +# React + bridge client +cd src/web && npm test # 2 tests (Vitest) +``` + +## The JS↔C# bridge + +`HybridWebView` injects a `HybridWebView.InvokeDotNet(methodName, args)` global +into the web page. The bridge target is `UsersBridge` (registered as a transient +in `MauiProgram`), set via `HybridView.SetInvokeJavaScriptTarget(usersBridge)` in +`MainPage`. + +**C# side** — `src/AppName.Maui/Bridge/UsersBridge.cs`: + +| Method | Signature | Description | +|--------|-----------|-------------| +| `GetUsers` | `Task> GetUsers()` | Returns all users from the SQLite repository | +| `AddUser` | `Task AddUser(string name, string email)` | Creates and persists a new user | + +**TypeScript side** — `src/web/src/bridge/`: + +- `types.ts` — mirrors `AppName.Application.Dtos.UserDto` as a TS interface + (`id: string`, `name: string`, `email: string` — camelCase) +- `client.ts` — typed wrapper around `InvokeDotNet`: + - `bridge.users.getAll()` → invokes `"GetUsers"` + - `bridge.users.add(name, email)` → invokes `"AddUser"` + +**Keeping types in sync:** C# `UserDto(Guid Id, string Name, string Email)` (PascalCase) +maps to TS `UserDto { id, name, email }` (camelCase). When you add new DTOs or +bridge methods, update both `AppName.Application/Dtos/` and `src/web/src/bridge/types.ts` +by hand. + +## Known issues + +### NU1903 — SQLitePCLRaw.lib.e_sqlite3 vulnerability advisory + +Restoring any project that transitively pulls in EF Core SQLite will surface: + +``` +warning NU1903: Package 'SQLitePCLRaw.lib.e_sqlite3' 2.1.10 has a known high severity +vulnerability, https://github.com/advisories/GHSA-2m69-gcr7-jv3q +``` + +This advisory (GHSA-2m69-gcr7-jv3q) currently affects **all published versions** of +`SQLitePCLRaw.lib.e_sqlite3` — there is no upstream patch. It is an inherent +consequence of using EF Core SQLite today. Options: + +1. **Accept for now** — treat it as a known advisory and monitor for an upstream release. +2. **Suppress the audit warning** — add `` to `Directory.Packages.props` + once you have assessed the risk for your deployment context. +3. **Switch DB provider** — replace EF Core SQLite with another provider + (e.g. EF Core InMemory for development/testing, or a different SQLite binding). + +## Build tips for memory-constrained machines + +MSBuild can OOM on machines with limited RAM when it spawns multiple worker nodes. +Use single-process mode for all builds: + +```bash +export DOTNET_CLI_USE_MSBUILD_SERVER=0 +export MSBUILDDISABLENODEREUSE=1 +dotnet build -m:1 +``` + +If you hit `MSB1025` (exit code 137 / OOM), also run: + +```bash +dotnet build-server shutdown +``` + +then retry with the flags above. diff --git a/docs/superpowers/plans/2026-06-29-maui-react-hybrid-scaffold.md b/docs/superpowers/plans/2026-06-29-maui-react-hybrid-scaffold.md index ed05ef8..fac1880 100644 --- a/docs/superpowers/plans/2026-06-29-maui-react-hybrid-scaffold.md +++ b/docs/superpowers/plans/2026-06-29-maui-react-hybrid-scaffold.md @@ -17,7 +17,8 @@ - Dependency rule: no inner layer references an outer one. Domain references nothing. Application references Domain only. Infrastructure references Application (+ Domain). Maui references Application + Infrastructure. - Target frameworks for `AppName.Maui`: `net10.0-android;net10.0-ios;net10.0-maccatalyst;net10.0-windows10.0.19041.0` (Windows TFM only built on Windows). - Central NuGet versions via `Directory.Packages.props` (no inline `Version=` on `PackageReference`). -- **Host-platform note:** This dev machine is Linux. On Linux, only the `net10.0-android` target builds/runs; iOS/Mac Catalyst require macOS, Windows target requires Windows. Build/run verification steps below use Android (or `dotnet build` of the non-Maui projects, which are platform-neutral). Other targets are expected to compile on their respective hosts. +- **Host-platform note:** This dev machine is Linux **with no JDK and no Android SDK installed**, and no macOS/Windows host. Consequence: the platform-neutral projects (Domain, Application, Infrastructure + their tests) and the React app build and test fully here, but **no MAUI target framework can compile to completion on this machine** (android needs JDK + Android SDK; iOS/Mac Catalyst need macOS; the Windows target needs Windows). Per user decision, MAUI target builds are deferred to a configured machine. +- **MAUI-task verification protocol (Tasks 5, 6, 8, 9):** Do NOT expect a successful MAUI target build here. Verify instead by: (1) `dotnet workload install maui` succeeds; (2) `dotnet restore src/AppName.Maui/AppName.Maui.csproj` succeeds (proves project + package + project references resolve); (3) a build attempt `dotnet build src/AppName.Maui/AppName.Maui.csproj -f net10.0-android` is allowed to FAIL, but ONLY at the missing-Android-SDK/JDK step — it must NOT fail with C# compile errors (`CS####`), missing-type, or unresolved-reference errors. If you see `CS####` or reference errors, that is a real defect to fix; failures mentioning Android SDK / `JAVA_HOME` / `aapt` / `Xamarin.Android` tooling are the expected environmental stop and count as "C# verified." Report the exact failure reason so the reviewer can confirm it is environmental. - TypeScript bridge DTOs mirror Application DTOs by hand; keep them in sync. --- @@ -774,10 +775,13 @@ Note: `.gitignore` ignores `Resources/Raw/web/`. Force-add this placeholder so t git add -f src/AppName.Maui/Resources/Raw/web/index.html ``` -- [ ] **Step 6: Build the Android target to verify (Linux-compatible target)** +- [ ] **Step 6: Verify per the MAUI-task verification protocol (see Global Constraints)** -Run: `dotnet build src/AppName.Maui/AppName.Maui.csproj -f net10.0-android` -Expected: BUILD SUCCEEDED. (If Android SDK is missing, install via `dotnet workload` / Android SDK manager, or build a non-Maui-restricted check by confirming the solution restores: `dotnet restore`.) +Run: `dotnet restore src/AppName.Maui/AppName.Maui.csproj` +Expected: restore succeeds (project, package, and project references resolve). + +Then run: `dotnet build src/AppName.Maui/AppName.Maui.csproj -f net10.0-android` +Expected: build attempt fails ONLY at the missing Android SDK / JDK step (e.g. message about Android SDK, `JAVA_HOME`, or Xamarin.Android tooling). It must NOT fail with `CS####` compile errors or unresolved references — those indicate real defects. Capture and report the exact failure reason. - [ ] **Step 7: Commit** @@ -856,10 +860,10 @@ In `src/AppName.Maui/MauiProgram.cs`, add after `AddInfrastructure(...)`: builder.Services.AddTransient(); ``` -- [ ] **Step 4: Build to verify the bridge compiles** +- [ ] **Step 4: Verify the bridge compiles (MAUI-task verification protocol)** Run: `dotnet build src/AppName.Maui/AppName.Maui.csproj -f net10.0-android` -Expected: BUILD SUCCEEDED. +Expected: per the protocol in Global Constraints — failure ONLY at the missing Android SDK / JDK step, with NO `CS####` compile errors (which would mean the bridge code is wrong). Report the exact failure reason. - [ ] **Step 5: Commit** @@ -1129,15 +1133,15 @@ In `src/AppName.Maui/AppName.Maui.csproj`, add before ``: ``` -- [ ] **Step 2: Verify the target runs on build** +- [ ] **Step 2: Verify the target runs on build (MAUI-task verification protocol)** Run: `dotnet build src/AppName.Maui/AppName.Maui.csproj -f net10.0-android` -Expected: log shows "Building React app in .../web"; BUILD SUCCEEDED; `Resources/Raw/web/index.html` is the React-built file. +Expected: log shows "Building React app in .../web" and the npm build runs (proving the target fires before the build), then the build reaches the expected environmental stop at the missing Android SDK / JDK step (no `CS####` errors). Confirm `src/AppName.Maui/Resources/Raw/web/index.html` was (re)generated by the React build. Report the exact failure reason. - [ ] **Step 3: Verify the skip switch works** Run: `dotnet build src/AppName.Maui/AppName.Maui.csproj -f net10.0-android -p:SkipReactBuild=true` -Expected: log does NOT show "Building React app"; BUILD SUCCEEDED. +Expected: log does NOT show "Building React app" (the npm build is skipped); the build then reaches the same expected environmental Android-SDK/JDK stop. Report the exact failure reason. - [ ] **Step 4: Commit** @@ -1224,10 +1228,13 @@ Expected: all test projects PASS. Run: `cd src/web && npm test && npm run build` Expected: tests PASS; build emits to `src/AppName.Maui/Resources/Raw/web`. -- [ ] **Step 4: Full verification — MAUI build** +- [ ] **Step 4: Full verification — MAUI restore + C# compile check (MAUI-task verification protocol)** -Run: `dotnet build src/AppName.Maui/AppName.Maui.csproj -f net10.0-android` -Expected: BUILD SUCCEEDED. +Run: `dotnet restore src/AppName.Maui/AppName.Maui.csproj` +Expected: restore succeeds. + +Then run: `dotnet build src/AppName.Maui/AppName.Maui.csproj -f net10.0-android` +Expected: per protocol — failure ONLY at the missing Android SDK / JDK step, no `CS####` errors. This confirms all C# across the MAUI host is consistent. Note in the README/report that completing an actual MAUI target build requires a configured Android/macOS/Windows host. - [ ] **Step 5: Commit** diff --git a/global.json b/global.json new file mode 100644 index 0000000..512142d --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "10.0.100", + "rollForward": "latestFeature" + } +} diff --git a/src/AppName.Application/AppName.Application.csproj b/src/AppName.Application/AppName.Application.csproj new file mode 100644 index 0000000..dde67e6 --- /dev/null +++ b/src/AppName.Application/AppName.Application.csproj @@ -0,0 +1,15 @@ + + + + net10.0 + + + + + + + + + + + diff --git a/src/AppName.Application/DependencyInjection.cs b/src/AppName.Application/DependencyInjection.cs new file mode 100644 index 0000000..9e173e3 --- /dev/null +++ b/src/AppName.Application/DependencyInjection.cs @@ -0,0 +1,14 @@ +using AppName.Application.UseCases.Users; +using Microsoft.Extensions.DependencyInjection; + +namespace AppName.Application; + +public static class DependencyInjection +{ + public static IServiceCollection AddApplication(this IServiceCollection services) + { + services.AddTransient(); + services.AddTransient(); + return services; + } +} diff --git a/src/AppName.Application/Dtos/UserDto.cs b/src/AppName.Application/Dtos/UserDto.cs new file mode 100644 index 0000000..13fba65 --- /dev/null +++ b/src/AppName.Application/Dtos/UserDto.cs @@ -0,0 +1,3 @@ +namespace AppName.Application.Dtos; + +public record UserDto(Guid Id, string Name, string Email); diff --git a/src/AppName.Application/UseCases/Users/AddUserUseCase.cs b/src/AppName.Application/UseCases/Users/AddUserUseCase.cs new file mode 100644 index 0000000..1aad139 --- /dev/null +++ b/src/AppName.Application/UseCases/Users/AddUserUseCase.cs @@ -0,0 +1,18 @@ +using AppName.Application.Dtos; +using AppName.Domain.Abstractions; +using AppName.Domain.Entities; + +namespace AppName.Application.UseCases.Users; + +public sealed class AddUserUseCase +{ + private readonly IUserRepository _repo; + public AddUserUseCase(IUserRepository repo) => _repo = repo; + + public async Task ExecuteAsync(string name, string email, CancellationToken ct = default) + { + var user = new User(Guid.NewGuid(), name, email); + await _repo.AddAsync(user, ct); + return new UserDto(user.Id, user.Name, user.Email); + } +} diff --git a/src/AppName.Application/UseCases/Users/GetUsersUseCase.cs b/src/AppName.Application/UseCases/Users/GetUsersUseCase.cs new file mode 100644 index 0000000..dcb2594 --- /dev/null +++ b/src/AppName.Application/UseCases/Users/GetUsersUseCase.cs @@ -0,0 +1,16 @@ +using AppName.Application.Dtos; +using AppName.Domain.Abstractions; + +namespace AppName.Application.UseCases.Users; + +public sealed class GetUsersUseCase +{ + private readonly IUserRepository _repo; + public GetUsersUseCase(IUserRepository repo) => _repo = repo; + + public async Task> ExecuteAsync(CancellationToken ct = default) + { + var users = await _repo.GetAllAsync(ct); + return users.Select(u => new UserDto(u.Id, u.Name, u.Email)).ToList(); + } +} diff --git a/src/AppName.Domain/Abstractions/IUserRepository.cs b/src/AppName.Domain/Abstractions/IUserRepository.cs new file mode 100644 index 0000000..830520e --- /dev/null +++ b/src/AppName.Domain/Abstractions/IUserRepository.cs @@ -0,0 +1,9 @@ +using AppName.Domain.Entities; + +namespace AppName.Domain.Abstractions; + +public interface IUserRepository +{ + Task> GetAllAsync(CancellationToken ct = default); + Task AddAsync(User user, CancellationToken ct = default); +} diff --git a/src/AppName.Domain/AppName.Domain.csproj b/src/AppName.Domain/AppName.Domain.csproj new file mode 100644 index 0000000..00b9359 --- /dev/null +++ b/src/AppName.Domain/AppName.Domain.csproj @@ -0,0 +1,7 @@ + + + + net10.0 + + + diff --git a/src/AppName.Domain/Entities/User.cs b/src/AppName.Domain/Entities/User.cs new file mode 100644 index 0000000..b74d05a --- /dev/null +++ b/src/AppName.Domain/Entities/User.cs @@ -0,0 +1,22 @@ +namespace AppName.Domain.Entities; + +public sealed class User +{ + public User(Guid id, string name, string email) + { + Id = id; + Name = name; + Email = email; + } + + public Guid Id { get; } + public string Name { get; private set; } + public string Email { get; } + + public void Rename(string newName) + { + if (string.IsNullOrWhiteSpace(newName)) + throw new ArgumentException("Name cannot be blank.", nameof(newName)); + Name = newName; + } +} diff --git a/src/AppName.Infrastructure/AppName.Infrastructure.csproj b/src/AppName.Infrastructure/AppName.Infrastructure.csproj new file mode 100644 index 0000000..84e47d2 --- /dev/null +++ b/src/AppName.Infrastructure/AppName.Infrastructure.csproj @@ -0,0 +1,16 @@ + + + + net10.0 + + + + + + + + + + + + diff --git a/src/AppName.Infrastructure/DependencyInjection.cs b/src/AppName.Infrastructure/DependencyInjection.cs new file mode 100644 index 0000000..6fb4536 --- /dev/null +++ b/src/AppName.Infrastructure/DependencyInjection.cs @@ -0,0 +1,16 @@ +using AppName.Domain.Abstractions; +using AppName.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.DependencyInjection; + +namespace AppName.Infrastructure; + +public static class DependencyInjection +{ + public static IServiceCollection AddInfrastructure(this IServiceCollection services, string dbPath) + { + services.AddDbContextFactory(options => options.UseSqlite($"Data Source={dbPath}")); + services.AddScoped(); + return services; + } +} diff --git a/src/AppName.Infrastructure/Persistence/AppDbContext.cs b/src/AppName.Infrastructure/Persistence/AppDbContext.cs new file mode 100644 index 0000000..ce91ca3 --- /dev/null +++ b/src/AppName.Infrastructure/Persistence/AppDbContext.cs @@ -0,0 +1,19 @@ +using AppName.Domain.Entities; +using Microsoft.EntityFrameworkCore; + +namespace AppName.Infrastructure.Persistence; + +public sealed class AppDbContext : DbContext +{ + public AppDbContext(DbContextOptions options) : base(options) { } + + public DbSet Users => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + var user = modelBuilder.Entity(); + user.HasKey(u => u.Id); + user.Property(u => u.Name).IsRequired(); + user.Property(u => u.Email).IsRequired(); + } +} diff --git a/src/AppName.Infrastructure/Persistence/UserRepository.cs b/src/AppName.Infrastructure/Persistence/UserRepository.cs new file mode 100644 index 0000000..2624e10 --- /dev/null +++ b/src/AppName.Infrastructure/Persistence/UserRepository.cs @@ -0,0 +1,24 @@ +using AppName.Domain.Abstractions; +using AppName.Domain.Entities; +using Microsoft.EntityFrameworkCore; + +namespace AppName.Infrastructure.Persistence; + +public sealed class UserRepository : IUserRepository +{ + private readonly IDbContextFactory _factory; + public UserRepository(IDbContextFactory factory) => _factory = factory; + + public async Task AddAsync(User user, CancellationToken ct = default) + { + await using var db = _factory.CreateDbContext(); + db.Users.Add(user); + await db.SaveChangesAsync(ct); + } + + public async Task> GetAllAsync(CancellationToken ct = default) + { + await using var db = _factory.CreateDbContext(); + return await db.Users.AsNoTracking().ToListAsync(ct); + } +} diff --git a/src/AppName.Maui/App.xaml b/src/AppName.Maui/App.xaml new file mode 100644 index 0000000..98c0924 --- /dev/null +++ b/src/AppName.Maui/App.xaml @@ -0,0 +1,14 @@ + + + + + + + + + + + diff --git a/src/AppName.Maui/App.xaml.cs b/src/AppName.Maui/App.xaml.cs new file mode 100644 index 0000000..a05b95f --- /dev/null +++ b/src/AppName.Maui/App.xaml.cs @@ -0,0 +1,15 @@ +namespace AppName.Maui; + +public partial class App : Application +{ + private readonly MainPage _mainPage; + + public App(MainPage mainPage) + { + InitializeComponent(); + _mainPage = mainPage; + } + + protected override Window CreateWindow(IActivationState? activationState) + => new Window(_mainPage); +} diff --git a/src/AppName.Maui/AppName.Maui.csproj b/src/AppName.Maui/AppName.Maui.csproj new file mode 100644 index 0000000..be9bc18 --- /dev/null +++ b/src/AppName.Maui/AppName.Maui.csproj @@ -0,0 +1,93 @@ + + + + net10.0-android + $(TargetFrameworks);net10.0-ios;net10.0-maccatalyst + $(TargetFrameworks);net10.0-windows10.0.19041.0 + + + + + Exe + AppName.Maui + true + true + enable + enable + + + SourceGen + + + AppName.Maui + + + com.companyname.appname.maui + + + 1.0 + 1 + + + None + + 15.0 + 15.0 + 21.0 + 10.0.17763.0 + 10.0.17763.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + false + $(MSBuildProjectDirectory)/../web + + + + + + + + + + diff --git a/src/AppName.Maui/Bridge/UsersBridge.cs b/src/AppName.Maui/Bridge/UsersBridge.cs new file mode 100644 index 0000000..68e38e1 --- /dev/null +++ b/src/AppName.Maui/Bridge/UsersBridge.cs @@ -0,0 +1,21 @@ +using AppName.Application.Dtos; +using AppName.Application.UseCases.Users; + +namespace AppName.Maui.Bridge; + +// Methods on this object are invoked from JavaScript via HybridWebView. +public sealed class UsersBridge +{ + private readonly GetUsersUseCase _getUsers; + private readonly AddUserUseCase _addUser; + + public UsersBridge(GetUsersUseCase getUsers, AddUserUseCase addUser) + { + _getUsers = getUsers; + _addUser = addUser; + } + + public Task> GetUsers() => _getUsers.ExecuteAsync(); + + public Task AddUser(string name, string email) => _addUser.ExecuteAsync(name, email); +} diff --git a/src/AppName.Maui/MainPage.xaml b/src/AppName.Maui/MainPage.xaml new file mode 100644 index 0000000..39371d9 --- /dev/null +++ b/src/AppName.Maui/MainPage.xaml @@ -0,0 +1,8 @@ + + + + diff --git a/src/AppName.Maui/MainPage.xaml.cs b/src/AppName.Maui/MainPage.xaml.cs new file mode 100644 index 0000000..bd38dfc --- /dev/null +++ b/src/AppName.Maui/MainPage.xaml.cs @@ -0,0 +1,12 @@ +using AppName.Maui.Bridge; + +namespace AppName.Maui; + +public partial class MainPage : ContentPage +{ + public MainPage(UsersBridge usersBridge) + { + InitializeComponent(); + HybridView.SetInvokeJavaScriptTarget(usersBridge); + } +} diff --git a/src/AppName.Maui/MauiProgram.cs b/src/AppName.Maui/MauiProgram.cs new file mode 100644 index 0000000..a17ce32 --- /dev/null +++ b/src/AppName.Maui/MauiProgram.cs @@ -0,0 +1,31 @@ +using AppName.Application; +using AppName.Infrastructure; +using Microsoft.Extensions.Logging; + +namespace AppName.Maui; + +public static class MauiProgram +{ + public static MauiApp CreateMauiApp() + { + var builder = MauiApp.CreateBuilder(); + builder + .UseMauiApp() + .ConfigureFonts(fonts => + { + fonts.AddFont("OpenSans-Regular.ttf", "OpenSansRegular"); + }); + + var dbPath = Path.Combine(FileSystem.AppDataDirectory, "app.db"); + builder.Services.AddApplication(); + builder.Services.AddInfrastructure(dbPath); + + builder.Services.AddTransient(); + builder.Services.AddTransient(); + +#if DEBUG + builder.Logging.AddDebug(); +#endif + return builder.Build(); + } +} diff --git a/src/AppName.Maui/Platforms/Android/AndroidManifest.xml b/src/AppName.Maui/Platforms/Android/AndroidManifest.xml new file mode 100644 index 0000000..bdec9b5 --- /dev/null +++ b/src/AppName.Maui/Platforms/Android/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/AppName.Maui/Platforms/Android/MainActivity.cs b/src/AppName.Maui/Platforms/Android/MainActivity.cs new file mode 100644 index 0000000..50e6521 --- /dev/null +++ b/src/AppName.Maui/Platforms/Android/MainActivity.cs @@ -0,0 +1,10 @@ +using Android.App; +using Android.Content.PM; +using Android.OS; + +namespace AppName.Maui; + +[Activity(Theme = "@style/Maui.SplashTheme", MainLauncher = true, LaunchMode = LaunchMode.SingleTop, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation | ConfigChanges.UiMode | ConfigChanges.ScreenLayout | ConfigChanges.SmallestScreenSize | ConfigChanges.Density)] +public class MainActivity : MauiAppCompatActivity +{ +} diff --git a/src/AppName.Maui/Platforms/Android/MainApplication.cs b/src/AppName.Maui/Platforms/Android/MainApplication.cs new file mode 100644 index 0000000..13afafa --- /dev/null +++ b/src/AppName.Maui/Platforms/Android/MainApplication.cs @@ -0,0 +1,15 @@ +using Android.App; +using Android.Runtime; + +namespace AppName.Maui; + +[Application] +public class MainApplication : MauiApplication +{ + public MainApplication(IntPtr handle, JniHandleOwnership ownership) + : base(handle, ownership) + { + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/src/AppName.Maui/Platforms/Android/Resources/values/colors.xml b/src/AppName.Maui/Platforms/Android/Resources/values/colors.xml new file mode 100644 index 0000000..5cd1604 --- /dev/null +++ b/src/AppName.Maui/Platforms/Android/Resources/values/colors.xml @@ -0,0 +1,6 @@ + + + #512BD4 + #2B0B98 + #2B0B98 + \ No newline at end of file diff --git a/src/AppName.Maui/Platforms/MacCatalyst/AppDelegate.cs b/src/AppName.Maui/Platforms/MacCatalyst/AppDelegate.cs new file mode 100644 index 0000000..6657c17 --- /dev/null +++ b/src/AppName.Maui/Platforms/MacCatalyst/AppDelegate.cs @@ -0,0 +1,9 @@ +using Foundation; + +namespace AppName.Maui; + +[Register("AppDelegate")] +public class AppDelegate : MauiUIApplicationDelegate +{ + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/src/AppName.Maui/Platforms/MacCatalyst/Entitlements.plist b/src/AppName.Maui/Platforms/MacCatalyst/Entitlements.plist new file mode 100644 index 0000000..8e87c0c --- /dev/null +++ b/src/AppName.Maui/Platforms/MacCatalyst/Entitlements.plist @@ -0,0 +1,14 @@ + + + + + + + com.apple.security.app-sandbox + + + com.apple.security.network.client + + + + diff --git a/src/AppName.Maui/Platforms/MacCatalyst/Info.plist b/src/AppName.Maui/Platforms/MacCatalyst/Info.plist new file mode 100644 index 0000000..cfd1c83 --- /dev/null +++ b/src/AppName.Maui/Platforms/MacCatalyst/Info.plist @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + UIDeviceFamily + + 2 + + LSApplicationCategoryType + public.app-category.lifestyle + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/appicon.appiconset + + diff --git a/src/AppName.Maui/Platforms/MacCatalyst/Program.cs b/src/AppName.Maui/Platforms/MacCatalyst/Program.cs new file mode 100644 index 0000000..a789cd6 --- /dev/null +++ b/src/AppName.Maui/Platforms/MacCatalyst/Program.cs @@ -0,0 +1,15 @@ +using ObjCRuntime; +using UIKit; + +namespace AppName.Maui; + +public class Program +{ + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } +} diff --git a/src/AppName.Maui/Platforms/Windows/App.xaml b/src/AppName.Maui/Platforms/Windows/App.xaml new file mode 100644 index 0000000..8b0f8e9 --- /dev/null +++ b/src/AppName.Maui/Platforms/Windows/App.xaml @@ -0,0 +1,8 @@ + + + diff --git a/src/AppName.Maui/Platforms/Windows/App.xaml.cs b/src/AppName.Maui/Platforms/Windows/App.xaml.cs new file mode 100644 index 0000000..154eca7 --- /dev/null +++ b/src/AppName.Maui/Platforms/Windows/App.xaml.cs @@ -0,0 +1,24 @@ +using Microsoft.UI.Xaml; + +// To learn more about WinUI, the WinUI project structure, +// and more about our project templates, see: http://aka.ms/winui-project-info. + +namespace AppName.Maui.WinUI; + +/// +/// Provides application-specific behavior to supplement the default Application class. +/// +public partial class App : MauiWinUIApplication +{ + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + } + + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} + diff --git a/src/AppName.Maui/Platforms/Windows/Package.appxmanifest b/src/AppName.Maui/Platforms/Windows/Package.appxmanifest new file mode 100644 index 0000000..bff8395 --- /dev/null +++ b/src/AppName.Maui/Platforms/Windows/Package.appxmanifest @@ -0,0 +1,46 @@ + + + + + + + + + $placeholder$ + User Name + $placeholder$.png + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/AppName.Maui/Platforms/Windows/app.manifest b/src/AppName.Maui/Platforms/Windows/app.manifest new file mode 100644 index 0000000..e6570d9 --- /dev/null +++ b/src/AppName.Maui/Platforms/Windows/app.manifest @@ -0,0 +1,17 @@ + + + + + + + + true/PM + PerMonitorV2, PerMonitor + + true + + + diff --git a/src/AppName.Maui/Platforms/iOS/AppDelegate.cs b/src/AppName.Maui/Platforms/iOS/AppDelegate.cs new file mode 100644 index 0000000..6657c17 --- /dev/null +++ b/src/AppName.Maui/Platforms/iOS/AppDelegate.cs @@ -0,0 +1,9 @@ +using Foundation; + +namespace AppName.Maui; + +[Register("AppDelegate")] +public class AppDelegate : MauiUIApplicationDelegate +{ + protected override MauiApp CreateMauiApp() => MauiProgram.CreateMauiApp(); +} diff --git a/src/AppName.Maui/Platforms/iOS/Info.plist b/src/AppName.Maui/Platforms/iOS/Info.plist new file mode 100644 index 0000000..358337b --- /dev/null +++ b/src/AppName.Maui/Platforms/iOS/Info.plist @@ -0,0 +1,32 @@ + + + + + LSRequiresIPhoneOS + + UIDeviceFamily + + 1 + 2 + + UIRequiredDeviceCapabilities + + arm64 + + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + XSAppIconAssets + Assets.xcassets/appicon.appiconset + + diff --git a/src/AppName.Maui/Platforms/iOS/Program.cs b/src/AppName.Maui/Platforms/iOS/Program.cs new file mode 100644 index 0000000..a789cd6 --- /dev/null +++ b/src/AppName.Maui/Platforms/iOS/Program.cs @@ -0,0 +1,15 @@ +using ObjCRuntime; +using UIKit; + +namespace AppName.Maui; + +public class Program +{ + // This is the main entry point of the application. + static void Main(string[] args) + { + // if you want to use a different Application Delegate class from "AppDelegate" + // you can specify it here. + UIApplication.Main(args, null, typeof(AppDelegate)); + } +} diff --git a/src/AppName.Maui/Platforms/iOS/Resources/PrivacyInfo.xcprivacy b/src/AppName.Maui/Platforms/iOS/Resources/PrivacyInfo.xcprivacy new file mode 100644 index 0000000..1ea3a5d --- /dev/null +++ b/src/AppName.Maui/Platforms/iOS/Resources/PrivacyInfo.xcprivacy @@ -0,0 +1,51 @@ + + + + + + NSPrivacyAccessedAPITypes + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryFileTimestamp + NSPrivacyAccessedAPITypeReasons + + C617.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategorySystemBootTime + NSPrivacyAccessedAPITypeReasons + + 35F9.1 + + + + NSPrivacyAccessedAPIType + NSPrivacyAccessedAPICategoryDiskSpace + NSPrivacyAccessedAPITypeReasons + + E174.1 + + + + + + diff --git a/src/AppName.Maui/Properties/launchSettings.json b/src/AppName.Maui/Properties/launchSettings.json new file mode 100644 index 0000000..f4c6c8d --- /dev/null +++ b/src/AppName.Maui/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "Windows Machine": { + "commandName": "Project", + "nativeDebugging": false + } + } +} \ No newline at end of file diff --git a/src/AppName.Maui/Resources/AppIcon/appicon.svg b/src/AppName.Maui/Resources/AppIcon/appicon.svg new file mode 100644 index 0000000..5f04fcf --- /dev/null +++ b/src/AppName.Maui/Resources/AppIcon/appicon.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/src/AppName.Maui/Resources/AppIcon/appiconfg.svg b/src/AppName.Maui/Resources/AppIcon/appiconfg.svg new file mode 100644 index 0000000..62d66d7 --- /dev/null +++ b/src/AppName.Maui/Resources/AppIcon/appiconfg.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/AppName.Maui/Resources/Fonts/OpenSans-Regular.ttf b/src/AppName.Maui/Resources/Fonts/OpenSans-Regular.ttf new file mode 100644 index 0000000..9b09396 Binary files /dev/null and b/src/AppName.Maui/Resources/Fonts/OpenSans-Regular.ttf differ diff --git a/src/AppName.Maui/Resources/Fonts/OpenSans-Semibold.ttf b/src/AppName.Maui/Resources/Fonts/OpenSans-Semibold.ttf new file mode 100644 index 0000000..ca26718 Binary files /dev/null and b/src/AppName.Maui/Resources/Fonts/OpenSans-Semibold.ttf differ diff --git a/src/AppName.Maui/Resources/Images/dotnet_bot.png b/src/AppName.Maui/Resources/Images/dotnet_bot.png new file mode 100644 index 0000000..054167e Binary files /dev/null and b/src/AppName.Maui/Resources/Images/dotnet_bot.png differ diff --git a/src/AppName.Maui/Resources/Raw/AboutAssets.txt b/src/AppName.Maui/Resources/Raw/AboutAssets.txt new file mode 100644 index 0000000..f22d3bf --- /dev/null +++ b/src/AppName.Maui/Resources/Raw/AboutAssets.txt @@ -0,0 +1,15 @@ +Any raw assets you want to be deployed with your application can be placed in +this directory (and child directories). Deployment of the asset to your application +is automatically handled by the following `MauiAsset` Build Action within your `.csproj`. + + + +These files will be deployed with your package and will be accessible using Essentials: + + async Task LoadMauiAsset() + { + using var stream = await FileSystem.OpenAppPackageFileAsync("AboutAssets.txt"); + using var reader = new StreamReader(stream); + + var contents = reader.ReadToEnd(); + } diff --git a/src/AppName.Maui/Resources/Splash/splash.svg b/src/AppName.Maui/Resources/Splash/splash.svg new file mode 100644 index 0000000..62d66d7 --- /dev/null +++ b/src/AppName.Maui/Resources/Splash/splash.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/src/AppName.Maui/Resources/Styles/Colors.xaml b/src/AppName.Maui/Resources/Styles/Colors.xaml new file mode 100644 index 0000000..daae3bd --- /dev/null +++ b/src/AppName.Maui/Resources/Styles/Colors.xaml @@ -0,0 +1,44 @@ + + + + + + #512BD4 + #ac99ea + #242424 + #DFD8F7 + #9880e5 + #2B0B98 + + White + Black + #D600AA + #190649 + #1f1f1f + + #E1E1E1 + #C8C8C8 + #ACACAC + #919191 + #6E6E6E + #404040 + #212121 + #141414 + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/AppName.Maui/Resources/Styles/Styles.xaml b/src/AppName.Maui/Resources/Styles/Styles.xaml new file mode 100644 index 0000000..0dce374 --- /dev/null +++ b/src/AppName.Maui/Resources/Styles/Styles.xaml @@ -0,0 +1,434 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/web/.gitignore b/src/web/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/src/web/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/src/web/.oxlintrc.json b/src/web/.oxlintrc.json new file mode 100644 index 0000000..6fa991d --- /dev/null +++ b/src/web/.oxlintrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/src/web/README.md b/src/web/README.md new file mode 100644 index 0000000..d6af7e3 --- /dev/null +++ b/src/web/README.md @@ -0,0 +1,32 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). + +## Expanding the Oxlint configuration + +If you are developing a production application, we recommend enabling type-aware lint rules by installing `oxlint-tsgolint` and editing `.oxlintrc.json`: + +```json +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "options": { + "typeAware": true + }, + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} +``` + +See the [Oxlint rules documentation](https://oxc.rs/docs/guide/usage/linter/rules) for the full list of rules and categories. diff --git a/src/web/index.html b/src/web/index.html new file mode 100644 index 0000000..5e3836a --- /dev/null +++ b/src/web/index.html @@ -0,0 +1,13 @@ + + + + + + + web + + +
+ + + diff --git a/src/web/package-lock.json b/src/web/package-lock.json new file mode 100644 index 0000000..d4a9e45 --- /dev/null +++ b/src/web/package-lock.json @@ -0,0 +1,2625 @@ +{ + "name": "web", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "web", + "version": "0.0.0", + "dependencies": { + "react": "^19.2.7", + "react-dom": "^19.2.7" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@types/node": "^24.13.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "jsdom": "^29.1.1", + "oxlint": "^1.71.0", + "typescript": "~6.0.2", + "vite": "^8.1.1", + "vitest": "^4.1.9" + } + }, + "node_modules/@adobe/css-tools": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.5.0.tgz", + "integrity": "sha512-6OzddxPio9UiWTCemp4N8cYLV2ZN1ncRnV1cVGtve7dhPOtRkleRyx32GQCYSwDYgaHU3USMm84tNsvKzRCa1Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@asamuzakjp/css-color": { + "version": "5.1.11", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", + "integrity": "sha512-KVw6qIiCTUQhByfTd78h2yD1/00waTmm9uy/R7Ck/ctUyAPj+AEDLkQIdJW0T8+qGgj3j5bpNKK7Q3G+LedJWg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@csstools/css-calc": "^3.2.0", + "@csstools/css-color-parser": "^4.1.0", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.1.1.tgz", + "integrity": "sha512-67RZDnYRc8H/8MLDgQCDE//zoqVFwajkepHZgmXrbwybzXOEwOWGPYGmALYl9J2DOLfFPPs6kKCqmbzV895hTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/generational-cache": "^1.0.1", + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.2.1", + "is-potential-custom-element-name": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/generational-cache": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/generational-cache/-/generational-cache-1.0.1.tgz", + "integrity": "sha512-wajfB8KqzMCN2KGNFdLkReeHncd0AslUSrvHVvvYWuU8ghncRJoA50kT3zP9MVL0+9g4/67H+cdvBskj9THPzg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@bramus/specificity": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz", + "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "css-tree": "^3.0.0" + }, + "bin": { + "specificity": "bin/cli.js" + } + }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.2.1.tgz", + "integrity": "sha512-DtdHlgXh5ZkA43cwBcAm+huzgJiwx3ZTWVjBs94kwz2xKqSimDA3lBgCjphYgwgVUMWatSM0pDd8TILB1yrVVg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.9.tgz", + "integrity": "sha512-paQcIaOO53Rk5+YrBaBjm/SgrV4INImjo2BT1DtQRYr+XeTRbeAYlS+jxXp9drqvKmtFnWRJKIalDLhZZDu42A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.2.1" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.6.tgz", + "integrity": "sha512-TcJCWFbXLPpJYq6z7bfOyjWYJDiDg2/I4gyUC9pqPNqHFRIey0EB0q0L5cSnQDfWJg8Jd6VadakxdIez/3zkqQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@exodus/bytes": { + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.1.tgz", + "integrity": "sha512-S6mL0yNB/Abt9Ei4tq8gDhcczc4S3+vQ4ra7vxnAf+YHC02srtqxKKZghx2Dq6p0e66THKwR6r8N6P95wEty7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + }, + "peerDependencies": { + "@noble/hashes": "^1.8.0 || ^2.0.0" + }, + "peerDependenciesMeta": { + "@noble/hashes": { + "optional": true + } + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.137.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", + "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + } + }, + "node_modules/@oxlint/binding-android-arm-eabi": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm-eabi/-/binding-android-arm-eabi-1.72.0.tgz", + "integrity": "sha512-zhCmvn+1Mj3UchAc/90i99S0t7jJUsHmFVSPg4UWrjO8b8eaSGwscgO6QAUtvHBstkjQwBttQNswEnAF1mIQdA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-android-arm64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-android-arm64/-/binding-android-arm64-1.72.0.tgz", + "integrity": "sha512-mtH+aY/ozv1eZoCUC2owjFAtyNBKHpJHygKeEu9zXXnQGW1Q2/qOpvx+I+Lf23+TvTz66F4iiXUbl2cGvoLPCQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-arm64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-arm64/-/binding-darwin-arm64-1.72.0.tgz", + "integrity": "sha512-EvnajNPDtfknB3ZieeOOyDTwJn9QXDiwfnF4ZDQqART6RG6hjY4WigQcZdGoK2dkB3e1vrmEzN9aYbQCUkh/gQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-darwin-x64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-darwin-x64/-/binding-darwin-x64-1.72.0.tgz", + "integrity": "sha512-ZkCdEa/G80A7vEHfeCDz/+L3m33DE73v32mDKhgOIgz8Uwf0DFcK7+uu6qC+7LEhmz5fpOe1osWKyjSNMydFIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-freebsd-x64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-freebsd-x64/-/binding-freebsd-x64-1.72.0.tgz", + "integrity": "sha512-NroXv2vh+sxVY1uya/rM5pjhx1hm8BzlYpx9q67QP0Xhw5MH2bf5GJylpvLEC+781p1Xli/317EoV9AlGwViag==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-gnueabihf": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.72.0.tgz", + "integrity": "sha512-0NDywYgfj279Ou/BcQuCYSj7NJwBfmWn5qc5uGO/Ny7fUWmXyIpvawqX/8acQlWG6IXelJsJhj+JAy6sjsKj0A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm-musleabihf": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-1.72.0.tgz", + "integrity": "sha512-4vpXB06h65Ezsy4hRyrGjGrfa1SkVPii09yaajiYhmVpgsFiLD+KNxIx/BNAY+XiO+i1yqp9HHdwqM8VTqa5XQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.72.0.tgz", + "integrity": "sha512-immaN4g2ZGFiOkKrvRX9LvzZdd2GkQM5wR+UyzYyUuyhUTXGQ4HKUJH18xp4G8OfhCVaVAJfKZxwE1r8+4hhaQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-arm64-musl": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.72.0.tgz", + "integrity": "sha512-JGHS9Mnr7iWyyLDxgCv1MhzVpAckgptg00F2gnxt/GD7lQ2SW1BRcxHqhSTaSdDpjWRrBkBxMMh4+Hn3aVtExg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-ppc64-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.72.0.tgz", + "integrity": "sha512-AOYgBZqxNshrg83P9v0RYv+m8s10Cqkj4/PxXFDhcS3k7FqsIG5+CxErshZCIN7G8iy4Y+VGfAsuEdar8AcbBg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-1.72.0.tgz", + "integrity": "sha512-QMybPS5ij3/vrKG67mqzHwW++91sYxK/PPUVi6SBtNCEzW4niS52fVBdXbQ6nou0wWbUPEpx8Sl/ZjtgE3clXA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-riscv64-musl": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-1.72.0.tgz", + "integrity": "sha512-gOc3W7JV0PXRpIL7stUlLe3Wa9Gp0Kdlup87IT3gHDvPKck2xNgMIl/Gs2lldYY2lyXZDC4rWi3hmoLUobkgbQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-s390x-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.72.0.tgz", + "integrity": "sha512-rpGxph+FjjHcYI5q6uxB3Az+tnfmEnDbSA8+PK9ZE/VzyUAkvBOMeuY7ZQMhu5mpZH7YQDsTdW6Cx4kV/msc6w==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-gnu": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.72.0.tgz", + "integrity": "sha512-WND+uhf/Ko13SLqQMWQUgsZuLvYYEvL0ZKgg0tgGYfLqxG7l8Ju123fHDMJyYSDl5E3bUbpFUuii/OvMreFQzw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-linux-x64-musl": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-linux-x64-musl/-/binding-linux-x64-musl-1.72.0.tgz", + "integrity": "sha512-SrpbrUL70nG9vh6zP4/oKHWgLuHquwsr7MW9XOn0olBVgh10Uqr8qscKhQoBGEn6olK/IUpn5GSKcdQ5AjUhGA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-openharmony-arm64": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-openharmony-arm64/-/binding-openharmony-arm64-1.72.0.tgz", + "integrity": "sha512-qkrsEn6NmgFKr7U/QnezQMb+q/vzAy0Dd9Y95gQGQTyjzDLN+HRZMuM5u70iyH4nBLCfKBzhjMsYCehKay2jyg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-arm64-msvc": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.72.0.tgz", + "integrity": "sha512-LWR6ZlFZph+KPjXv8opgZsXRDCdrdQe8VL8Cg9zxCoBS73h6znzZpydVgmdnwj8mB9AuSM5jxEgDJDpQkjboeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-ia32-msvc": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-1.72.0.tgz", + "integrity": "sha512-yt6HEh7IsHvtjRWtmeZRX134eaXKHq5Gnqlf1xBJdJl1JtdoRUEJw3nAxpZoUDS860cX/foKbztO441anVBtVQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@oxlint/binding-win32-x64-msvc": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/@oxlint/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.72.0.tgz", + "integrity": "sha512-b2eKFD2hX7tIwmo/cyH6TDq8vzWRZ2qNHrzoGntUTmq0h3zQh/uX3eTSHCwI8OB/ADQfJCRelLItK8BsxuucDA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", + "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", + "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", + "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", + "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", + "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", + "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", + "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", + "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", + "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", + "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", + "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", + "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", + "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", + "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", + "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/dom": { + "version": "10.4.1", + "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz", + "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@babel/code-frame": "^7.10.4", + "@babel/runtime": "^7.12.5", + "@types/aria-query": "^5.0.1", + "aria-query": "5.3.0", + "dom-accessibility-api": "^0.5.9", + "lz-string": "^1.5.0", + "picocolors": "1.1.1", + "pretty-format": "^27.0.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@testing-library/jest-dom": { + "version": "6.9.1", + "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz", + "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@adobe/css-tools": "^4.4.0", + "aria-query": "^5.0.0", + "css.escape": "^1.5.1", + "dom-accessibility-api": "^0.6.3", + "picocolors": "^1.1.1", + "redent": "^3.0.0" + }, + "engines": { + "node": ">=14", + "npm": ">=6", + "yarn": ">=1" + } + }, + "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz", + "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@testing-library/react": { + "version": "16.3.2", + "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz", + "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@testing-library/dom": "^10.0.0", + "@types/react": "^18.0.0 || ^19.0.0", + "@types/react-dom": "^18.0.0 || ^19.0.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@types/aria-query": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", + "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", + "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.18.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.17", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.17.tgz", + "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.3.tgz", + "integrity": "sha512-vmFvco5/QuC2f9Oj+wTk0+9XeDFkHxSamwZKYc7MxYwKICfvUvlMhqKI0VuICPltGqh1neqBKDvO4kes1ya8vg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rolldown/pluginutils": "^1.0.1" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "peerDependencies": { + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } + } + }, + "node_modules/@vitest/expect": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", + "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", + "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "4.1.9", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", + "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", + "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "4.1.9", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", + "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.9", + "@vitest/utils": "4.1.9", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", + "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", + "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "4.1.9", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/aria-query": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "dequal": "^2.0.3" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/css.escape": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz", + "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/data-urls": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz", + "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dom-accessibility-api": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz", + "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-module-lexer": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.2.0.tgz", + "integrity": "sha512-3lGxdTXCLfe1MYfTz1y2ksAAUM4NAOP6rPEjxGJVKO7TZ5+tvHCaQWGpC4Y3IXvW3ece0Cz1cIP4FWBxOnGCTQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/html-encoding-sniffer": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz", + "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.6.0" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/jsdom": { + "version": "29.1.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.1.1.tgz", + "integrity": "sha512-ECi4Fi2f7BdJtUKTflYRTiaMxIB0O6zfR1fX0GXpUrf6flp8QIYn1UT20YQqdSOfk2dfkCwS8LAFoJDEppNK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^5.1.11", + "@asamuzakjp/dom-selector": "^7.1.1", + "@bramus/specificity": "^2.4.2", + "@csstools/css-syntax-patches-for-csstree": "^1.1.3", + "@exodus/bytes": "^1.15.0", + "css-tree": "^3.2.1", + "data-urls": "^7.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^6.0.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.3.5", + "parse5": "^8.0.1", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.1", + "undici": "^7.25.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.1", + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^16.0.1", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lru-cache": { + "version": "11.5.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.1.tgz", + "integrity": "sha512-RPimw/7aMdv2oqRrxKwvZXcPfwBrn/JZ2xYcY9Hus/6LaS3VOAKVWKWgNLCFSiOm1ESXinjsDlidVU7JlnCN2A==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/lz-string": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz", + "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==", + "dev": true, + "license": "MIT", + "peer": true, + "bin": { + "lz-string": "bin/bin.js" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/min-indent": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz", + "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/obug": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", + "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/oxlint": { + "version": "1.72.0", + "resolved": "https://registry.npmjs.org/oxlint/-/oxlint-1.72.0.tgz", + "integrity": "sha512-1rhdZIP/EvoI91ABIwNU5Q8+bWf8mjrS5UzIOZld4d4bXxJvtlUhlQvaoTogIGin/qdErMOrwaIJvCSIAKTLhA==", + "dev": true, + "license": "MIT", + "bin": { + "oxlint": "bin/oxlint" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxlint/binding-android-arm-eabi": "1.72.0", + "@oxlint/binding-android-arm64": "1.72.0", + "@oxlint/binding-darwin-arm64": "1.72.0", + "@oxlint/binding-darwin-x64": "1.72.0", + "@oxlint/binding-freebsd-x64": "1.72.0", + "@oxlint/binding-linux-arm-gnueabihf": "1.72.0", + "@oxlint/binding-linux-arm-musleabihf": "1.72.0", + "@oxlint/binding-linux-arm64-gnu": "1.72.0", + "@oxlint/binding-linux-arm64-musl": "1.72.0", + "@oxlint/binding-linux-ppc64-gnu": "1.72.0", + "@oxlint/binding-linux-riscv64-gnu": "1.72.0", + "@oxlint/binding-linux-riscv64-musl": "1.72.0", + "@oxlint/binding-linux-s390x-gnu": "1.72.0", + "@oxlint/binding-linux-x64-gnu": "1.72.0", + "@oxlint/binding-linux-x64-musl": "1.72.0", + "@oxlint/binding-openharmony-arm64": "1.72.0", + "@oxlint/binding-win32-arm64-msvc": "1.72.0", + "@oxlint/binding-win32-ia32-msvc": "1.72.0", + "@oxlint/binding-win32-x64-msvc": "1.72.0" + }, + "peerDependencies": { + "oxlint-tsgolint": ">=0.22.1", + "vite-plus": "*" + }, + "peerDependenciesMeta": { + "oxlint-tsgolint": { + "optional": true + }, + "vite-plus": { + "optional": true + } + } + }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.16.tgz", + "integrity": "sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz", + "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1", + "ansi-styles": "^5.0.0", + "react-is": "^17.0.1" + }, + "engines": { + "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/react": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", + "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.2.7", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", + "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.27.0" + }, + "peerDependencies": { + "react": "^19.2.7" + } + }, + "node_modules/react-is": { + "version": "17.0.2", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", + "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/redent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz", + "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "indent-string": "^4.0.0", + "strip-indent": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rolldown": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", + "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@oxc-project/types": "=0.137.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm64": "1.1.3", + "@rolldown/binding-darwin-arm64": "1.1.3", + "@rolldown/binding-darwin-x64": "1.1.3", + "@rolldown/binding-freebsd-x64": "1.1.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", + "@rolldown/binding-linux-arm64-gnu": "1.1.3", + "@rolldown/binding-linux-arm64-musl": "1.1.3", + "@rolldown/binding-linux-ppc64-gnu": "1.1.3", + "@rolldown/binding-linux-s390x-gnu": "1.1.3", + "@rolldown/binding-linux-x64-gnu": "1.1.3", + "@rolldown/binding-linux-x64-musl": "1.1.3", + "@rolldown/binding-openharmony-arm64": "1.1.3", + "@rolldown/binding-wasm32-wasi": "1.1.3", + "@rolldown/binding-win32-arm64-msvc": "1.1.3", + "@rolldown/binding-win32-x64-msvc": "1.1.3" + } + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, + "node_modules/scheduler": { + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", + "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-indent": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz", + "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "min-indent": "^1.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyrainbow": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", + "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.5.tgz", + "integrity": "sha512-RfEzKWcq5fHUOFq7J3rl3Oz6ylKGtcHqUznzj4EcXsxLSIjJcvpbXAQtWGeJQ0xKnimR5e0Cn+cn9TssfMzm+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.5" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.5", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.5.tgz", + "integrity": "sha512-pGrwzZDvPwKe+7NNUqAunb6rqTfynr0VOUhCMdqbu5xlvNiszsAJygRzwvpVycdzejlbpY+SWJOn+s75Og7FEA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", + "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD", + "optional": true + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.18.1" + } + }, + "node_modules/undici-types": { + "version": "7.18.2", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.18.2.tgz", + "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.2.tgz", + "integrity": "sha512-6YYPbRXTxx6bRXmOn7XdnQAy5DQNHhDgtjhDHI13oe4pY93kkcdGJWxpGwOm++/Wh0QpQhDrpIoVMrmrsI5AGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.16", + "rolldown": "~1.1.3", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.3.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vitest": { + "version": "4.1.9", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", + "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "4.1.9", + "@vitest/mocker": "4.1.9", + "@vitest/pretty-format": "4.1.9", + "@vitest/runner": "4.1.9", + "@vitest/snapshot": "4.1.9", + "@vitest/spy": "4.1.9", + "@vitest/utils": "4.1.9", + "es-module-lexer": "^2.0.0", + "expect-type": "^1.3.0", + "magic-string": "^0.30.21", + "obug": "^2.1.1", + "pathe": "^2.0.3", + "picomatch": "^4.0.3", + "std-env": "^4.0.0-rc.1", + "tinybench": "^2.9.0", + "tinyexec": "^1.0.2", + "tinyglobby": "^0.2.15", + "tinyrainbow": "^3.1.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^20.0.0 || ^22.0.0 || >=24.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "4.1.9", + "@vitest/browser-preview": "4.1.9", + "@vitest/browser-webdriverio": "4.1.9", + "@vitest/coverage-istanbul": "4.1.9", + "@vitest/coverage-v8": "4.1.9", + "@vitest/ui": "4.1.9", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + }, + "vite": { + "optional": false + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/whatwg-url": { + "version": "16.0.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz", + "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@exodus/bytes": "^1.11.0", + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.1" + }, + "engines": { + "node": "^20.19.0 || ^22.12.0 || >=24.0.0" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" + } + } +} diff --git a/src/web/package.json b/src/web/package.json new file mode 100644 index 0000000..fd192aa --- /dev/null +++ b/src/web/package.json @@ -0,0 +1,31 @@ +{ + "name": "web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "oxlint", + "preview": "vite preview", + "test": "vitest run", + "test:watch": "vitest" + }, + "dependencies": { + "react": "^19.2.7", + "react-dom": "^19.2.7" + }, + "devDependencies": { + "@testing-library/jest-dom": "^6.9.1", + "@testing-library/react": "^16.3.2", + "@types/node": "^24.13.2", + "@types/react": "^19.2.17", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.3", + "jsdom": "^29.1.1", + "oxlint": "^1.71.0", + "typescript": "~6.0.2", + "vite": "^8.1.1", + "vitest": "^4.1.9" + } +} diff --git a/src/web/public/favicon.svg b/src/web/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/src/web/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/web/public/icons.svg b/src/web/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/src/web/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/web/src/App.tsx b/src/web/src/App.tsx new file mode 100644 index 0000000..f3a76d9 --- /dev/null +++ b/src/web/src/App.tsx @@ -0,0 +1,10 @@ +import { UsersList } from "./features/users/UsersList"; + +export default function App() { + return ( +
+

AppName

+ +
+ ); +} diff --git a/src/web/src/bridge/client.test.ts b/src/web/src/bridge/client.test.ts new file mode 100644 index 0000000..415339e --- /dev/null +++ b/src/web/src/bridge/client.test.ts @@ -0,0 +1,34 @@ +import { describe, it, expect, vi, beforeEach } from "vitest"; +import { bridge } from "./client"; +import type { UserDto } from "./types"; + +declare global { + // eslint-disable-next-line no-var + var HybridWebView: { InvokeDotNet: (name: string, args?: unknown[]) => Promise }; +} + +describe("bridge.users", () => { + beforeEach(() => { + globalThis.HybridWebView = { InvokeDotNet: vi.fn() }; + }); + + it("getAll calls GetUsers and returns the result", async () => { + const fake: UserDto[] = [{ id: "1", name: "Ada", email: "ada@x.com" }]; + (globalThis.HybridWebView.InvokeDotNet as ReturnType).mockResolvedValue(fake); + + const result = await bridge.users.getAll(); + + expect(globalThis.HybridWebView.InvokeDotNet).toHaveBeenCalledWith("GetUsers"); + expect(result).toEqual(fake); + }); + + it("add calls AddUser with name and email", async () => { + const created: UserDto = { id: "2", name: "Bob", email: "bob@x.com" }; + (globalThis.HybridWebView.InvokeDotNet as ReturnType).mockResolvedValue(created); + + const result = await bridge.users.add("Bob", "bob@x.com"); + + expect(globalThis.HybridWebView.InvokeDotNet).toHaveBeenCalledWith("AddUser", ["Bob", "bob@x.com"]); + expect(result).toEqual(created); + }); +}); diff --git a/src/web/src/bridge/client.ts b/src/web/src/bridge/client.ts new file mode 100644 index 0000000..43122e4 --- /dev/null +++ b/src/web/src/bridge/client.ts @@ -0,0 +1,20 @@ +import type { UserDto } from "./types"; + +// HybridWebView injects this global at runtime. +declare const HybridWebView: { + InvokeDotNet: (methodName: string, args?: unknown[]) => Promise; +}; + +function invoke(method: string, args?: unknown[]): Promise { + return (args !== undefined + ? HybridWebView.InvokeDotNet(method, args) + : HybridWebView.InvokeDotNet(method)) as Promise; +} + +export const bridge = { + users: { + getAll: (): Promise => invoke("GetUsers"), + add: (name: string, email: string): Promise => + invoke("AddUser", [name, email]), + }, +}; diff --git a/src/web/src/bridge/types.ts b/src/web/src/bridge/types.ts new file mode 100644 index 0000000..1bdbf0f --- /dev/null +++ b/src/web/src/bridge/types.ts @@ -0,0 +1,6 @@ +// Mirrors AppName.Application.Dtos.UserDto — keep in sync by hand. +export interface UserDto { + id: string; + name: string; + email: string; +} diff --git a/src/web/src/features/users/UsersList.tsx b/src/web/src/features/users/UsersList.tsx new file mode 100644 index 0000000..bb9dabc --- /dev/null +++ b/src/web/src/features/users/UsersList.tsx @@ -0,0 +1,26 @@ +import { useEffect, useState } from "react"; +import { bridge } from "../../bridge/client"; +import type { UserDto } from "../../bridge/types"; + +export function UsersList() { + const [users, setUsers] = useState([]); + const [error, setError] = useState(null); + + useEffect(() => { + bridge.users + .getAll() + .then(setUsers) + .catch((e: unknown) => setError(String(e))); + }, []); + + if (error) return

Bridge error: {error}

; + return ( +
    + {users.map((u) => ( +
  • + {u.name} — {u.email} +
  • + ))} +
+ ); +} diff --git a/src/web/src/index.css b/src/web/src/index.css new file mode 100644 index 0000000..f6633a8 --- /dev/null +++ b/src/web/src/index.css @@ -0,0 +1,110 @@ +:root { + --text: #6b6375; + --text-h: #08060d; + --bg: #fff; + --border: #e5e4e7; + --code-bg: #f4f3ec; + --accent: #aa3bff; + --accent-bg: rgba(170, 59, 255, 0.1); + --accent-border: rgba(170, 59, 255, 0.5); + --social-bg: rgba(244, 243, 236, 0.5); + --shadow: + rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; + + --sans: system-ui, 'Segoe UI', Roboto, sans-serif; + --heading: system-ui, 'Segoe UI', Roboto, sans-serif; + --mono: ui-monospace, Consolas, monospace; + + font: 18px/145% var(--sans); + letter-spacing: 0.18px; + color-scheme: light dark; + color: var(--text); + background: var(--bg); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + @media (max-width: 1024px) { + font-size: 16px; + } +} + +@media (prefers-color-scheme: dark) { + :root { + --text: #9ca3af; + --text-h: #f3f4f6; + --bg: #16171d; + --border: #2e303a; + --code-bg: #1f2028; + --accent: #c084fc; + --accent-bg: rgba(192, 132, 252, 0.15); + --accent-border: rgba(192, 132, 252, 0.5); + --social-bg: rgba(47, 48, 58, 0.5); + --shadow: + rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; + } + + #social .button-icon { + filter: invert(1) brightness(2); + } +} + +#root { + width: 1126px; + max-width: 100%; + margin: 0 auto; + text-align: center; + border-inline: 1px solid var(--border); + min-height: 100svh; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +body { + margin: 0; +} + +h1, +h2 { + font-family: var(--heading); + font-weight: 500; + color: var(--text-h); +} + +h1 { + font-size: 56px; + letter-spacing: -1.68px; + margin: 32px 0; + @media (max-width: 1024px) { + font-size: 36px; + margin: 20px 0; + } +} +h2 { + font-size: 24px; + line-height: 118%; + letter-spacing: -0.24px; + margin: 0 0 8px; + @media (max-width: 1024px) { + font-size: 20px; + } +} +p { + margin: 0; +} + +code { + font-family: var(--mono); + display: inline-flex; + border-radius: 4px; + color: var(--text-h); +} + +code { + font-size: 15px; + line-height: 135%; + padding: 4px 8px; + background: var(--code-bg); +} diff --git a/src/web/src/main.tsx b/src/web/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/src/web/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/src/web/src/test/setup.ts b/src/web/src/test/setup.ts new file mode 100644 index 0000000..d0de870 --- /dev/null +++ b/src/web/src/test/setup.ts @@ -0,0 +1 @@ +import "@testing-library/jest-dom"; diff --git a/src/web/tsconfig.app.json b/src/web/tsconfig.app.json new file mode 100644 index 0000000..6830b6f --- /dev/null +++ b/src/web/tsconfig.app.json @@ -0,0 +1,26 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023", "DOM"], + "module": "esnext", + "types": ["vite/client"], + "allowArbitraryExtensions": true, + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/src/web/tsconfig.json b/src/web/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/src/web/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/src/web/tsconfig.node.json b/src/web/tsconfig.node.json new file mode 100644 index 0000000..8455dcb --- /dev/null +++ b/src/web/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "module": "nodenext", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/src/web/vite.config.ts b/src/web/vite.config.ts new file mode 100644 index 0000000..804be73 --- /dev/null +++ b/src/web/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; + +// HybridWebView serves from a virtual root; assets must use relative paths. +export default defineConfig({ + plugins: [react()], + base: "./", + build: { + outDir: "../AppName.Maui/Resources/Raw/web", + emptyOutDir: true, + }, +}); diff --git a/src/web/vitest.config.ts b/src/web/vitest.config.ts new file mode 100644 index 0000000..f409aba --- /dev/null +++ b/src/web/vitest.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from "vitest/config"; +import react from "@vitejs/plugin-react"; + +export default defineConfig({ + plugins: [react()], + test: { + environment: "jsdom", + globals: true, + setupFiles: ["./src/test/setup.ts"], + }, +}); diff --git a/tests/AppName.Application.Tests/AppName.Application.Tests.csproj b/tests/AppName.Application.Tests/AppName.Application.Tests.csproj new file mode 100644 index 0000000..1eb6285 --- /dev/null +++ b/tests/AppName.Application.Tests/AppName.Application.Tests.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + false + + + + + + + + + + + + + + + + + + + diff --git a/tests/AppName.Application.Tests/GetUsersUseCaseTests.cs b/tests/AppName.Application.Tests/GetUsersUseCaseTests.cs new file mode 100644 index 0000000..97435ee --- /dev/null +++ b/tests/AppName.Application.Tests/GetUsersUseCaseTests.cs @@ -0,0 +1,51 @@ +using AppName.Application.UseCases.Users; +using AppName.Domain.Abstractions; +using AppName.Domain.Entities; + +namespace AppName.Application.Tests; + +public class GetUsersUseCaseTests +{ + private sealed class FakeUserRepository : IUserRepository + { + private readonly List _users = new(); + public Task AddAsync(User user, CancellationToken ct = default) + { + _users.Add(user); + return Task.CompletedTask; + } + public Task> GetAllAsync(CancellationToken ct = default) + => Task.FromResult>(_users); + } + + [Fact] + public async Task ExecuteAsync_MapsEntitiesToDtos() + { + var repo = new FakeUserRepository(); + await repo.AddAsync(new User(Guid.NewGuid(), "Ada", "ada@x.com")); + var sut = new GetUsersUseCase(repo); + + var result = await sut.ExecuteAsync(); + + var dto = Assert.Single(result); + Assert.Equal("Ada", dto.Name); + Assert.Equal("ada@x.com", dto.Email); + } + + [Fact] + public async Task AddUserUseCase_AddsUserAndReturnsMappedDto() + { + var repo = new FakeUserRepository(); + var sut = new AddUserUseCase(repo); + + var dto = await sut.ExecuteAsync("Bob", "bob@x.com"); + + Assert.Equal("Bob", dto.Name); + Assert.Equal("bob@x.com", dto.Email); + Assert.NotEqual(Guid.Empty, dto.Id); + + var all = await repo.GetAllAsync(); + var saved = Assert.Single(all); + Assert.Equal(dto.Id, saved.Id); + } +} diff --git a/tests/AppName.Domain.Tests/AppName.Domain.Tests.csproj b/tests/AppName.Domain.Tests/AppName.Domain.Tests.csproj new file mode 100644 index 0000000..27e69b5 --- /dev/null +++ b/tests/AppName.Domain.Tests/AppName.Domain.Tests.csproj @@ -0,0 +1,23 @@ + + + + net10.0 + false + + + + + + + + + + + + + + + + + + diff --git a/tests/AppName.Domain.Tests/UserTests.cs b/tests/AppName.Domain.Tests/UserTests.cs new file mode 100644 index 0000000..b65d7a6 --- /dev/null +++ b/tests/AppName.Domain.Tests/UserTests.cs @@ -0,0 +1,28 @@ +using AppName.Domain.Entities; + +namespace AppName.Domain.Tests; + +public class UserTests +{ + [Fact] + public void Rename_WithValidName_UpdatesName() + { + var user = new User(Guid.NewGuid(), "Old Name", "a@b.com"); + + user.Rename("New Name"); + + Assert.Equal("New Name", user.Name); + } + + [Theory] + [InlineData("")] + [InlineData(" ")] + [InlineData(null)] + public void Rename_WithBlankName_Throws(string? blank) + { + var user = new User(Guid.NewGuid(), "Old Name", "a@b.com"); + + // blank! — deliberately passing null/blank to verify the guard rejects it + Assert.Throws(() => user.Rename(blank!)); + } +} diff --git a/tests/AppName.Infrastructure.Tests/AppName.Infrastructure.Tests.csproj b/tests/AppName.Infrastructure.Tests/AppName.Infrastructure.Tests.csproj new file mode 100644 index 0000000..5a0f504 --- /dev/null +++ b/tests/AppName.Infrastructure.Tests/AppName.Infrastructure.Tests.csproj @@ -0,0 +1,24 @@ + + + + net10.0 + false + + + + + + + + + + + + + + + + + + + diff --git a/tests/AppName.Infrastructure.Tests/UserRepositoryTests.cs b/tests/AppName.Infrastructure.Tests/UserRepositoryTests.cs new file mode 100644 index 0000000..5fbf5f7 --- /dev/null +++ b/tests/AppName.Infrastructure.Tests/UserRepositoryTests.cs @@ -0,0 +1,42 @@ +using AppName.Domain.Entities; +using AppName.Infrastructure.Persistence; +using Microsoft.EntityFrameworkCore; + +namespace AppName.Infrastructure.Tests; + +public class UserRepositoryTests +{ + private sealed class TestDbContextFactory : IDbContextFactory + { + private readonly DbContextOptions _options; + public TestDbContextFactory(string dbPath) + => _options = new DbContextOptionsBuilder() + .UseSqlite($"Data Source={dbPath}").Options; + public AppDbContext CreateDbContext() => new AppDbContext(_options); + } + + [Fact] + public async Task AddAsync_ThenGetAllAsync_ReturnsPersistedUser() + { + var dbPath = Path.Combine(Path.GetTempPath(), $"{Guid.NewGuid()}.db"); + try + { + var factory = new TestDbContextFactory(dbPath); + + using (var ctx = factory.CreateDbContext()) + ctx.Database.EnsureCreated(); + + var repo = new UserRepository(factory); + + await repo.AddAsync(new User(Guid.NewGuid(), "Ada", "ada@x.com")); + var all = await repo.GetAllAsync(); + + var user = Assert.Single(all); + Assert.Equal("Ada", user.Name); + } + finally + { + if (File.Exists(dbPath)) File.Delete(dbPath); + } + } +}