Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "10.0.10",
"commands": [
"dotnet-ef"
]
}
}
}
3 changes: 0 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -193,9 +193,6 @@ docs/**
!docs/lab/
!docs/lab/**

# Local-only polluted/autogen solutions
src/src.sln

# Local pack / smoke artifacts
artifacts/
*.nupkg
Expand Down
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

Public **.NET lab** plus extracted MIT **BuildingBlocks**. The application is the laboratory. A package is a reusable result that earned a boundary.

Open **`FeatureFusion.sln`** from the repository root. It is the single authoritative solution (Domain projects included). Do not recreate an inner `src/src.sln`.

Do not treat FeatureFusion as a framework. Install a BuildingBlock only when it solves a problem the host actually has.

## Lab vs package
Expand All @@ -10,7 +12,7 @@ Do not treat FeatureFusion as a framework. Install a BuildingBlock only when it
|------|------------|------------|
| **Lab** | Runnable API + Aspire AppHost (`src/Lab/`) | This file + [docs/lab](docs/lab/README.md) + root README |
| **Packable BuildingBlock** | NuGet on nuget.org | `src/BuildingBlocks/<Name>/AGENTS.md` + `PACKAGE_README.md` |
| **In-repo sibling** | Pagination IR / Dappernot packed | `Pagination/AGENTS.md`, `Pagination.Dapper/AGENTS.md` |
| **In-repo sibling** | Pagination IR / Dapper; Domain + Domain.EF (not packed yet) | `Pagination/AGENTS.md`, `Pagination.Dapper/AGENTS.md`, [Domain](src/BuildingBlocks/Domain/AGENTS.md), [Domain.EF](src/BuildingBlocks/Domain.EntityFrameworkCore/AGENTS.md) |
| **Lab-only** | EventBus, YARP rate limit, feature flags | Code + [linkedin catalog](docs/linkedin-posts.md) — no package `AGENTS.md` |

**Lab charter (evidence families, extraction rules, roadmap):** [docs/lab/README.md](docs/lab/README.md). Behavioral experiment catalog: [Experiments README](tests/Lab/IntegrationTests/Experiments/README.md) (Exp 1–18 + MAF prototype).
Expand All @@ -28,6 +30,8 @@ Do not treat FeatureFusion as a framework. Install a BuildingBlock only when it

Pagination IR (`BuildingBlocks.Pagination`) is bundled into the EF Core nupkg. Do not pack it. Dapper pagination is a lab project.

**In-repo (not nuget.org):** `BuildingBlocks.Domain` and `BuildingBlocks.Domain.EntityFrameworkCore` — project-reference only. Do not pack or publish them.

## Human docs

- [README](README.md) — lab + package overview
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ Formerly published as `Maxofpower/FeatureManagement`; GitHub redirects the old U

## Development

1. Clone [FeatureFusion](https://github.com/Maxofpower/FeatureFusion).
1. Clone [FeatureFusion](https://github.com/Maxofpower/FeatureFusion) and open **`FeatureFusion.sln`** (the only solution).
2. `dotnet restore FeatureFusion.sln`
3. Package tests (multi-TFM where applicable):
- `dotnet test tests/BuildingBlocks/Domain.Tests`
- `dotnet test tests/BuildingBlocks/Mediator.Tests`
- `dotnet test tests/BuildingBlocks/Mediator.Analyzers.Tests`
- `dotnet test tests/BuildingBlocks/Mcp.Tests`
Expand Down
45 changes: 45 additions & 0 deletions FeatureFusion.sln
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Idempotency.Tests", "Idempo
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildingBlocks.Idempotency.Tests", "tests\BuildingBlocks\Idempotency.Tests\BuildingBlocks.Idempotency.Tests.csproj", "{2DBA7F51-AC3B-4464-8348-0374FD62FDA0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildingBlocks.Domain", "src\BuildingBlocks\Domain\BuildingBlocks.Domain.csproj", "{DBD6F42C-E841-46F5-AEA0-487310C1AFE7}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildingBlocks.Domain.EntityFrameworkCore", "src\BuildingBlocks\Domain.EntityFrameworkCore\BuildingBlocks.Domain.EntityFrameworkCore.csproj", "{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildingBlocks.Domain.Tests", "tests\BuildingBlocks\Domain.Tests\BuildingBlocks.Domain.Tests.csproj", "{E09652D8-319A-498A-978A-308D55E08ECB}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -511,6 +517,42 @@ Global
{2DBA7F51-AC3B-4464-8348-0374FD62FDA0}.Release|x64.Build.0 = Release|Any CPU
{2DBA7F51-AC3B-4464-8348-0374FD62FDA0}.Release|x86.ActiveCfg = Release|Any CPU
{2DBA7F51-AC3B-4464-8348-0374FD62FDA0}.Release|x86.Build.0 = Release|Any CPU
{DBD6F42C-E841-46F5-AEA0-487310C1AFE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DBD6F42C-E841-46F5-AEA0-487310C1AFE7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DBD6F42C-E841-46F5-AEA0-487310C1AFE7}.Debug|x64.ActiveCfg = Debug|Any CPU
{DBD6F42C-E841-46F5-AEA0-487310C1AFE7}.Debug|x64.Build.0 = Debug|Any CPU
{DBD6F42C-E841-46F5-AEA0-487310C1AFE7}.Debug|x86.ActiveCfg = Debug|Any CPU
{DBD6F42C-E841-46F5-AEA0-487310C1AFE7}.Debug|x86.Build.0 = Debug|Any CPU
{DBD6F42C-E841-46F5-AEA0-487310C1AFE7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DBD6F42C-E841-46F5-AEA0-487310C1AFE7}.Release|Any CPU.Build.0 = Release|Any CPU
{DBD6F42C-E841-46F5-AEA0-487310C1AFE7}.Release|x64.ActiveCfg = Release|Any CPU
{DBD6F42C-E841-46F5-AEA0-487310C1AFE7}.Release|x64.Build.0 = Release|Any CPU
{DBD6F42C-E841-46F5-AEA0-487310C1AFE7}.Release|x86.ActiveCfg = Release|Any CPU
{DBD6F42C-E841-46F5-AEA0-487310C1AFE7}.Release|x86.Build.0 = Release|Any CPU
{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE}.Debug|x64.ActiveCfg = Debug|Any CPU
{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE}.Debug|x64.Build.0 = Debug|Any CPU
{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE}.Debug|x86.ActiveCfg = Debug|Any CPU
{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE}.Debug|x86.Build.0 = Debug|Any CPU
{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE}.Release|Any CPU.Build.0 = Release|Any CPU
{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE}.Release|x64.ActiveCfg = Release|Any CPU
{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE}.Release|x64.Build.0 = Release|Any CPU
{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE}.Release|x86.ActiveCfg = Release|Any CPU
{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE}.Release|x86.Build.0 = Release|Any CPU
{E09652D8-319A-498A-978A-308D55E08ECB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{E09652D8-319A-498A-978A-308D55E08ECB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E09652D8-319A-498A-978A-308D55E08ECB}.Debug|x64.ActiveCfg = Debug|Any CPU
{E09652D8-319A-498A-978A-308D55E08ECB}.Debug|x64.Build.0 = Debug|Any CPU
{E09652D8-319A-498A-978A-308D55E08ECB}.Debug|x86.ActiveCfg = Debug|Any CPU
{E09652D8-319A-498A-978A-308D55E08ECB}.Debug|x86.Build.0 = Debug|Any CPU
{E09652D8-319A-498A-978A-308D55E08ECB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E09652D8-319A-498A-978A-308D55E08ECB}.Release|Any CPU.Build.0 = Release|Any CPU
{E09652D8-319A-498A-978A-308D55E08ECB}.Release|x64.ActiveCfg = Release|Any CPU
{E09652D8-319A-498A-978A-308D55E08ECB}.Release|x64.Build.0 = Release|Any CPU
{E09652D8-319A-498A-978A-308D55E08ECB}.Release|x86.ActiveCfg = Release|Any CPU
{E09652D8-319A-498A-978A-308D55E08ECB}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -564,5 +606,8 @@ Global
{C0B899A7-8C72-4D54-8A9A-E5C65A43F108} = {3B6DFA28-E423-47BD-1DC7-709D0430AE1C}
{E409DBCD-B9E3-14BD-B95F-04CE755E3F82} = {8A1B2C3D-4E5F-6789-ABCD-333333333333}
{2DBA7F51-AC3B-4464-8348-0374FD62FDA0} = {E409DBCD-B9E3-14BD-B95F-04CE755E3F82}
{DBD6F42C-E841-46F5-AEA0-487310C1AFE7} = {8A1B2C3D-4E5F-6789-ABCD-111111111111}
{D77790E8-FD2E-4DDE-AF8F-8FF3A0ABACDE} = {8A1B2C3D-4E5F-6789-ABCD-111111111111}
{E09652D8-319A-498A-978A-308D55E08ECB} = {8A1B2C3D-4E5F-6789-ABCD-333333333333}
EndGlobalSection
EndGlobal
37 changes: 20 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -386,7 +386,7 @@ app.MapPost("/orders", CreateAsync).WithIdempotency(useLock: true);

- Package README: [`src/BuildingBlocks/Idempotency/PACKAGE_README.md`](src/BuildingBlocks/Idempotency/PACKAGE_README.md) · agent notes: [`AGENTS.md`](src/BuildingBlocks/Idempotency/AGENTS.md)
- Docs: [`docs/building-blocks/idempotency.md`](docs/building-blocks/idempotency.md)
- Lab: MVC `POST /api/v2/Order/order`, Minimal API smoke `POST /api/v2/idempotency-smoke`. Provenance: Experiments **3**, **4**, **12** ([catalog](tests/Lab/IntegrationTests/Experiments/README.md)).
- Lab: Minimal API `POST /api/v1/Order/order` (WithIdempotency), smoke `POST /api/v1/idempotency-smoke`. Provenance: Experiments **3**, **4**, **12** ([catalog](tests/Lab/IntegrationTests/Experiments/README.md)).

---

Expand Down Expand Up @@ -451,7 +451,7 @@ dotnet run -c Release --project benchmarks/BuildingBlocks/Pagination.EntityFrame

- Package README: [`Pagination.EntityFrameworkCore`](src/BuildingBlocks/Pagination.EntityFrameworkCore/PACKAGE_README.md) (includes the table)
- Docs: [`docs/building-blocks/pagination.md`](docs/building-blocks/pagination.md) · ADR [`0003`](docs/adr/0003-pagination-keyset.md) · [test matrix](docs/building-blocks/PAGINATION_TEST_MATRIX.md)
- Lab: FeatureFusion PostgreSQL catalog — `GET /api/v2/products-page` (Minimal API EF; POST kept) · `POST /api/v2/Product/products` (MVC EF) · `POST /api/v2/Product/products-dapper` (Dapper **project** showcase) · MCP `products.list` — same `GetProductsQuery`. See [Pagination showcase](#pagination-showcase).
- Lab: FeatureFusion PostgreSQL catalog — `GET /api/v1/products-page` (Minimal API EF; POST kept) · `POST /api/v1/Product/products` (Minimal API EF) · `POST /api/v1/Product/products-dapper` (Dapper **project** showcase) · MCP `products.list` — same `GetProductsQuery`. See [Pagination showcase](#pagination-showcase).
- Catalog: `docs/linkedin-posts.md` → `cursor-pagination`

---
Expand Down Expand Up @@ -718,12 +718,12 @@ Install the packages above in your own hosts, **or** clone this repo and run **F
| Telemetry | **`BuildingBlocks.Telemetry`** in ServiceDefaults; **`BuildingBlocks.Aspire.Hosting.SigNoz`** on AppHost |
| Event bus | RabbitMQ + transactional outbox/inbox, DLQ, dedup hooks |
| Aspire lab | AppHost orchestration for Postgres, Redis, RabbitMQ, Memcached, SigNoz |
| HTTP idempotency | **`BuildingBlocks.Idempotency` 1.0.1** — MVC + Minimal API, 2xx envelope replay, System.Text.Json, optional Redis lock (`POST /api/v2/Order/order`) |
| HTTP idempotency | **`BuildingBlocks.Idempotency` 1.0.1** — MVC + Minimal API, 2xx envelope replay, System.Text.Json, optional Redis lock (`POST /api/v1/Order/order`) |
| Feature flags (demo) | ASP.NET Core Feature Management + custom filters (claims / VIP) |
| API surface | Versioned controllers + Minimal APIs, FluentValidation patterns |
| Gateway | YARP reverse proxy + Memcached distributed rate limiting |
| Caching | Redis / Memcached / memory managers + middleware demos |
| Pagination | **`BuildingBlocks.Pagination.EntityFrameworkCore`** — PostgreSQL product catalog via `GET /api/v2/products-page` (same query on MVC, Dapper, MCP); Dapper is in-repo only |
| Pagination | **`BuildingBlocks.Pagination.EntityFrameworkCore`** — PostgreSQL product catalog via `GET /api/v1/products-page` (same query on Product/products, Dapper, MCP); Dapper is in-repo only |
| Design patterns | Mediator, Decorator, CoR, Strategy, and more — see below |

Also in the lab: app/DB initializers, middleware dynamic caching, Aspire AppHost integration tests, and performance-minded practices (OTel hooks, resilience).
Expand All @@ -736,22 +736,22 @@ One `GetProductsQuery` drives:

| Surface | Endpoint |
|---------|----------|
| Minimal API (EF) | **`GET /api/v2/products-page`** (POST kept for compatibility) |
| MVC (EF) | `POST /api/v2/Product/products` |
| Dapper | `POST /api/v2/Product/products-dapper` |
| Minimal API (EF) | **`GET /api/v1/products-page`** (POST kept for compatibility) |
| Minimal API (EF) | `POST /api/v1/Product/products` |
| Dapper | `POST /api/v1/Product/products-dapper` |
| MCP | `products.list` |

What that path demonstrates: typed `SortKey` / `SortKeyRegistry`, composite keyset order (Price + Id, Name + Id, CreatedAt + Id), unique Id tie-breaker, forward and backward cursors, first-page `TotalCount`, `CancellationToken`, `HasKeysetIndex`, EF Core SQL projection, and the in-repo Dapper adapter. Query names are case-insensitive (`limit` / `Limit`). `sortBy`: `Id` · `Name` · `Price` · `CreatedAt`. `sortDirection`: `Ascending` · `Descending`. Empty cursor + `pageDirection=Backward` is the last page. **Cursors are opaque** — pass `NextCursor` / `PreviousCursor` back unchanged; do not construct them. FeatureFusion is PostgreSQL: `QueryHint` stays `None`.

```http
GET /api/v2/products-page?limit=20&sortBy=Price&sortDirection=Ascending
GET /api/v1/products-page?limit=20&sortBy=Price&sortDirection=Ascending
```

Response includes `items`, `hasMore`, `nextCursor`, `previousCursor`, `hasPrevious`, and `totalCount` on this first page. Then:

```http
GET /api/v2/products-page?limit=20&sortBy=Price&sortDirection=Ascending&cursor=<NextCursor>
GET /api/v2/products-page?limit=20&sortBy=Price&sortDirection=Ascending&cursor=<PreviousCursor>
GET /api/v1/products-page?limit=20&sortBy=Price&sortDirection=Ascending&cursor=<NextCursor>
GET /api/v1/products-page?limit=20&sortBy=Price&sortDirection=Ascending&cursor=<PreviousCursor>
```

Swagger: `http://localhost:5141/swagger`. Details: [`docs/building-blocks/pagination.md`](docs/building-blocks/pagination.md) · [NuGet](https://www.nuget.org/packages/BuildingBlocks.Pagination.EntityFrameworkCore).
Expand Down Expand Up @@ -803,9 +803,11 @@ flowchart LR
## Repository layout

```text
FeatureFusion.sln # .NET only
FeatureFusion.sln # canonical solution — open from the repo root
src/ # C# only
BuildingBlocks/
Domain/ # in-repo (not packed)
Domain.EntityFrameworkCore/ # in-repo (not packed)
Mediator/ # CQRS Send + pipeline NuGet
Mediator.Analyzers/
Mcp/ # [McpTool] / MapTool → MCP tools NuGet
Expand All @@ -816,14 +818,15 @@ src/ # C# only
Telemetry/ # Config-driven OpenTelemetry NuGet
Aspire.Hosting.SigNoz/ # AddSigNoz() Aspire hosting NuGet
Lab/
FeatureFusion/ # Web API showcase (Features/, Infrastructure/, Controllers, Minimal APIs)
FeatureFusion/ # Web API showcase (Domain/, Features/, Infrastructure/, Minimal APIs)
FeatureFusion.ApiGateway/ # YARP + Memcached rate limiter
FeatureFusion.AppHost/ # Aspire AppHost (+ SigNoz stack)
FeatureFusion.ServiceDefaults/
EventBus/ # Reusable RabbitMQ event bus (namespaces stay EventBusRabbitMQ)
web/ # reserved Next.js showcase (README only; not in the .sln)
tests/
BuildingBlocks/
Domain.Tests/
Mediator.Tests/
Mediator.Analyzers.Tests/
Mcp.Tests/
Expand Down Expand Up @@ -939,26 +942,26 @@ docker compose up -d

### Feature management filters

Conditional features via Microsoft.FeatureManagement and custom filters (e.g. VIP claims). Versioned controllers and Minimal APIs under `/api/v1|v2/...`.
Conditional features via Microsoft.FeatureManagement and custom filters (e.g. VIP claims). Single Asp.Versioning version under `/api/v1/...`. Feature filter preview: `GET /api/v1/lab/feature-filter-preview`.

### HTTP idempotency (BuildingBlocks.Idempotency)

REST idempotency with `IDistributedCache` status tracking, MVC `[Idempotent]` / Minimal API `WithIdempotency`, optional Redis lock, and **System.Text.Json** cache/body serialization (`POST /api/v2/Order/order`, smoke `POST /api/v2/idempotency-smoke`). Package **1.0.1**. See [BuildingBlocks.Idempotency](#buildingblocksidempotency).
REST idempotency with `IDistributedCache` status tracking, Minimal API `WithIdempotency`, optional Redis lock, and **System.Text.Json** cache/body serialization (`POST /api/v1/Order/order`, smoke `POST /api/v1/idempotency-smoke`). Package **1.0.1** still supports MVC `[Idempotent]`. See [BuildingBlocks.Idempotency](#buildingblocksidempotency).

- [Idempotency with CQRS](https://www.linkedin.com/feed/update/urn:li:activity:7303686809891356676/)
- [IdempotentFusion project](https://www.linkedin.com/feed/update/urn:li:activity:7309149985307029504/) (historical Lab name)

### API versioning & validation

Controllers + Minimal API groups; FluentValidation via controllers, generic endpoint filters, and `WithValidation` / `MapPostWithValidation`.
Single Asp.Versioning version (`1.0`, URL `/api/v1/...`). Minimal API groups; FluentValidation via generic endpoint filters and `WithValidation` / `MapPostWithValidation`.

### Caching, middleware & pagination

Redis / Memcached / memory managers, feature-flagged recommendation cache middleware, and DB migration/seed initializers.

### Generic bidirectional cursor (keyset) pagination

See [Pagination showcase](#pagination-showcase) for the FeatureFusion catalog (`GET /api/v2/products-page`). Package API, QueryHint, and SQLite probe numbers: [`PACKAGE_README`](src/BuildingBlocks/Pagination.EntityFrameworkCore/PACKAGE_README.md).
See [Pagination showcase](#pagination-showcase) for the FeatureFusion catalog (`GET /api/v1/products-page`). Package API, QueryHint, and SQLite probe numbers: [`PACKAGE_README`](src/BuildingBlocks/Pagination.EntityFrameworkCore/PACKAGE_README.md).

- Indexes: `(Price, Id)`, `(CreatedAt, Id)`, `(Name, Id)` on `products` (ASC and DESC variants)
- LinkedIn: [Reusable Cursor (keyset) Pagination](https://www.linkedin.com/feed/update/urn:li:activity:7325068550614708225/)
Expand All @@ -977,7 +980,7 @@ See [Pagination showcase](#pagination-showcase) for the FeatureFusion catalog (`
| **Factory** | Resilience / connection helpers; gateway Memcached factory |
| **Repository / DbContext** | EF Core `CatalogDbContext` + feature handlers |
| **Unit of work** | `ResilientTransaction` spanning business write + outbox |
| **Strategy** | Feature filters & validation styles (controller vs Minimal API) |
| **Strategy** | Feature filters & validation styles (endpoint filter vs ValidationBehavior) |
| **Template method** | `BaseValidator.PostInitialize` |
| **Keyset pagination** | `BuildingBlocks.Pagination.EntityFrameworkCore` — typed bidirectional cursors |
| **Chain of Responsibility** | Feature toggle rule evaluation; mediator pipeline chain |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.15.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.10" />
<PackageReference Include="MR.EntityFrameworkCore.KeysetPagination" Version="1.5.0" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.3" />
</ItemGroup>
Expand Down
Loading
Loading