commit 072838f5c3efb35f4224c456a5496c2572ebd4a0 Author: Daniel Covington Date: Mon Aug 10 10:46:05 2026 -0400 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4843b73 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +# Build output +bin/ +obj/ + +# User-specific files +*.user +*.rsuser +*.suo + +# .NET local tooling +.vs/ + +# Local app settings overrides +appsettings.*.local + +# ASP.NET Core user secrets artifacts +secrets.json + +# Common local environment files +.env +.env.* diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b2e17b9 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,1445 @@ +# CartWise v1 — AI Agent Build Specification + +## 1. Mission + +Build **CartWise v1**, a mobile-first grocery companion web application using **ASP.NET Core MVC**, **Razor Views**, **HTML5**, **CSS3**, **jQuery**, and **vanilla JavaScript**. + +CartWise v1 is not a grocery delivery platform and is not tied to any retailer. Its first job is to become the shopper's grocery memory: + +1. Maintain shared household grocery lists. +2. Resolve generic grocery concepts such as “milk” or “peanut butter” to preferred products. +3. Scan UPC/GTIN barcodes and identify products. +4. Record what the household purchased, where, and for how much. +5. Build personal price history. +6. Show useful price intelligence such as normal price, recent price, lowest observed price, and unit price. +7. Suggest items that may be running low using purchase intervals. +8. Provide a simple, touch-friendly shopping mode that works well on a phone. + +Do **not** expand v1 into meal planning, coupon clipping, universal retailer checkout, indoor navigation, or full cross-retailer real-time price comparison unless explicitly requested. + +--- + +## 2. Non-Negotiable Technology Choices + +### Application +- ASP.NET Core MVC +- C# +- Razor Views +- HTML5 +- CSS3 +- jQuery for concise DOM manipulation, event handling, AJAX, and progressive enhancement where it improves readability +- Vanilla JavaScript for browser-native APIs, focused modules, and cases where jQuery adds no value +- Entity Framework Core +- PostgreSQL + +### Optional / Later +- PostgreSQL PostGIS when geographic querying becomes necessary +- IndexedDB + Service Worker for advanced offline/PWA functionality +- Background jobs for enrichment/sync +- Redis only if profiling demonstrates a need + +### Do Not Introduce Without Explicit Approval +- React +- Vue +- Angular +- Blazor +- .NET MAUI +- Flutter +- client-side SPA architecture +- microservices +- message brokers +- CQRS frameworks +- MediatR +- generic repository abstractions merely for pattern compliance + +Use the simplest architecture that keeps domain rules testable and controllers thin. + +--- + +## 3. Product Principles + +1. **Server rendered first.** Razor owns primary page rendering. +2. **JavaScript enhances pages; it does not become the application framework.** jQuery and vanilla JavaScript are both approved. +3. **Mobile-first UX.** Grocery shopping is primarily performed from a phone. +4. **Retailer independence.** CartWise must remain useful with no retailer integration. +5. **Never fabricate freshness.** Every observed price has source and observation time. +6. **History is append-only where possible.** Do not overwrite price observations. +7. **UPC is an identifier, not the product primary key.** +8. **A grocery concept is different from a sellable product.** “Peanut butter” is a concept; “Jif Creamy 16 oz UPC X” is a product. +9. **Keep AI optional.** v1 does not need an LLM to function. +10. **Avoid premature complexity.** Start as a modular monolith. + +--- + +## 4. Recommended Solution Structure + +```text +CartWise.sln + +src/ + CartWise.Web/ + Controllers/ + ViewModels/ + Views/ + wwwroot/ + css/ + js/ + pages/ + services/ + components/ + utils/ + images/ + Program.cs + appsettings.json + + CartWise.Application/ + Interfaces/ + Services/ + DTOs/ + Results/ + Validation/ + + CartWise.Domain/ + Entities/ + Enums/ + ValueObjects/ + Rules/ + + CartWise.Infrastructure/ + Data/ + CartWiseDbContext.cs + Configurations/ + Migrations/ + Integrations/ + OpenFoodFacts/ + USDA/ + Retailers/ + Services/ + +tests/ + CartWise.Domain.Tests/ + CartWise.Application.Tests/ + CartWise.Web.Tests/ +``` + +### Project Responsibilities + +#### CartWise.Web +Owns: +- MVC controllers +- Razor views +- ViewModels +- model binding +- anti-forgery handling +- HTML/CSS/jQuery/vanilla JS +- authentication UI +- HTTP-specific behavior + +Must not contain pricing rules, replenishment rules, or database queries scattered through controllers. + +#### CartWise.Application +Owns: +- use-case orchestration +- service interfaces +- input validation that is not purely UI validation +- application services +- DTOs/results exchanged between Web and domain/infrastructure + +#### CartWise.Domain +Owns: +- core entities +- enums +- value objects +- business invariants +- price calculation rules +- replenishment rules where they can be expressed independently of persistence + +Must not reference ASP.NET Core, EF Core, HTTP, external APIs, or Razor. + +#### CartWise.Infrastructure +Owns: +- EF Core DbContext +- entity configurations +- migrations +- PostgreSQL-specific details +- external product data providers +- retailer adapters +- persistence implementation + +--- + +## 5. Core Domain Model + +### 5.1 User / Household + +Use ASP.NET Core Identity for authentication unless the repository explicitly specifies another authentication system. + +#### ApplicationUser +Extend IdentityUser only with fields that are truly needed. + +Suggested fields: +- Id +- DisplayName +- CreatedUtc + +Do not duplicate Email, PasswordHash, etc. already supplied by Identity. + +#### Household +```text +HouseholdId UUID PK +Name varchar(120) not null +CreatedUtc timestamptz not null +CreatedByUserId FK ApplicationUser +``` + +#### HouseholdMember +```text +HouseholdId UUID PK/FK +UserId string PK/FK +Role smallint not null +JoinedUtc timestamptz not null +``` + +Roles: +- Owner +- Member + +Relationships: +- Household 1..* HouseholdMembers +- User 1..* HouseholdMembers + +Every household-scoped query must enforce membership authorization. + +--- + +## 5.2 Grocery Concepts and Products + +### GroceryConcept +Represents what the shopper means generically. + +Examples: +- Milk +- Peanut Butter +- Bananas +- Ground Beef +- Paper Towels + +Fields: +```text +GroceryConceptId UUID PK +Name varchar(160) not null +NormalizedName varchar(160) not null +CategoryId UUID nullable FK +CreatedUtc timestamptz not null +``` + +Indexes: +- unique/near-unique index on NormalizedName as appropriate +- CategoryId + +### Brand +```text +BrandId UUID PK +Name varchar(160) not null +NormalizedName varchar(160) not null +``` + +### ProductCategory +Use a simple adjacency list if hierarchy is needed. + +```text +ProductCategoryId UUID PK +Name varchar(160) not null +ParentCategoryId UUID nullable FK ProductCategory +``` + +### Product +Represents a specific sellable/package-level product. + +```text +ProductId UUID PK +GroceryConceptId UUID nullable FK +BrandId UUID nullable FK +Name varchar(240) not null +NormalizedName varchar(240) not null +SizeValue numeric(12,3) nullable +SizeUnit varchar(32) nullable +ImageUrl text nullable +SourceType smallint not null +SourceExternalId varchar(200) nullable +CreatedUtc timestamptz not null +UpdatedUtc timestamptz not null +``` + +SourceType examples: +- Manual +- OpenFoodFacts +- USDA +- Retailer + +### ProductIdentifier +Do not use UPC as Product PK. + +```text +ProductIdentifierId UUID PK +ProductId UUID FK not null +IdentifierType smallint not null +Value varchar(64) not null +NormalizedValue varchar(64) not null +``` + +IdentifierType: +- UPC_A +- UPC_E +- EAN_8 +- EAN_13 +- GTIN +- PLU +- Other + +Indexes: +- unique composite on IdentifierType + NormalizedValue +- ProductId + +Relationship: +- Product 1..* ProductIdentifiers + +--- + +## 5.3 Household Product Preferences + +### HouseholdProductPreference +Maps a generic concept to household preferences. + +```text +HouseholdProductPreferenceId UUID PK +HouseholdId UUID FK not null +GroceryConceptId UUID FK not null +PreferredProductId UUID nullable FK Product +SubstitutionLevel smallint not null +PreferredQuantity numeric(12,3) nullable +PreferredUnit varchar(32) nullable +UpdatedUtc timestamptz not null +``` + +SubstitutionLevel: +0 = ExactProductOnly +1 = SameBrand +2 = SimilarPreferredBrands +3 = AnyEquivalent +4 = CheapestAcceptable + +Unique index: +- HouseholdId + GroceryConceptId + +--- + +## 5.4 Shopping Lists + +### ShoppingList +```text +ShoppingListId UUID PK +HouseholdId UUID FK not null +Name varchar(160) not null +Status smallint not null +CreatedUtc timestamptz not null +CompletedUtc timestamptz nullable +CreatedByUserId FK ApplicationUser not null +``` + +Status: +- Active +- Completed +- Archived + +v1 rule: a household should have at most one default active list unless multi-list functionality is explicitly added. + +### ShoppingListItem +```text +ShoppingListItemId UUID PK +ShoppingListId UUID FK not null +GroceryConceptId UUID nullable FK +PreferredProductId UUID nullable FK +DisplayName varchar(240) not null +Quantity numeric(12,3) nullable +Unit varchar(32) nullable +Status smallint not null +SortOrder int not null +AddedByUserId FK ApplicationUser not null +AddedUtc timestamptz not null +PurchasedUtc timestamptz nullable +Notes varchar(500) nullable +RowVersion/version concurrency token +``` + +Status: +- Needed +- Purchased +- Skipped +- Unavailable +- Substituted + +Rules: +- DisplayName preserves what the user entered. +- Concept/Product resolution can happen later. +- Do not block list entry merely because a product cannot be resolved. + +--- + +## 5.5 Retailers and Stores + +### Retailer +```text +RetailerId UUID PK +Name varchar(160) not null +NormalizedName varchar(160) not null +WebsiteUrl text nullable +``` + +### StoreLocation +```text +StoreLocationId UUID PK +RetailerId UUID FK not null +ExternalStoreId varchar(120) nullable +Name varchar(200) not null +AddressLine1 varchar(200) nullable +AddressLine2 varchar(200) nullable +City varchar(120) nullable +Region varchar(80) nullable +PostalCode varchar(24) nullable +CountryCode char(2) nullable +Latitude numeric(9,6) nullable +Longitude numeric(9,6) nullable +CreatedUtc timestamptz not null +UpdatedUtc timestamptz not null +``` + +Do not require PostGIS in first migration. Latitude/Longitude are enough initially. + +--- + +## 5.6 Purchases and Price Observations + +### Purchase +Represents a shopping transaction. + +```text +PurchaseId UUID PK +HouseholdId UUID FK not null +StoreLocationId UUID nullable FK +PurchasedByUserId FK ApplicationUser not null +PurchasedUtc timestamptz not null +Subtotal numeric(12,2) nullable +Tax numeric(12,2) nullable +Total numeric(12,2) nullable +SourceType smallint not null +CreatedUtc timestamptz not null +``` + +Purchase SourceType: +- Manual +- ShoppingMode +- ReceiptImport +- RetailerImport + +### PurchaseItem +```text +PurchaseItemId UUID PK +PurchaseId UUID FK not null +ProductId UUID nullable FK +GroceryConceptId UUID nullable FK +ShoppingListItemId UUID nullable FK +Description varchar(240) not null +Quantity numeric(12,3) not null default 1 +Unit varchar(32) nullable +LinePrice numeric(12,2) not null +UnitPrice numeric(12,4) nullable +CreatedUtc timestamptz not null +``` + +### PriceObservation +This is append-only history. + +```text +PriceObservationId UUID PK +HouseholdId UUID nullable FK +ProductId UUID not null FK +StoreLocationId UUID nullable FK +Price numeric(12,2) not null +SalePrice numeric(12,2) nullable +UnitPrice numeric(12,4) nullable +CurrencyCode char(3) not null default 'USD' +ObservedUtc timestamptz not null +SourceType smallint not null +SourceReference varchar(200) nullable +ConfidenceScore numeric(5,4) not null +CreatedUtc timestamptz not null +``` + +SourceType: +- UserEntered +- ShoppingMode +- Receipt +- OpenFoodFacts +- RetailerApi +- Instacart +- ImportedPurchase + +Indexes: +- ProductId + ObservedUtc DESC +- ProductId + StoreLocationId + ObservedUtc DESC +- HouseholdId + ProductId + ObservedUtc DESC + +Never update an old observation merely because a newer price appears. Add another row. + +--- + +## 5.7 Running-Low / Replenishment Data + +Do not create an elaborate ML model in v1. + +A service can calculate replenishment dynamically from PurchaseItem history. If caching later becomes necessary, introduce a derived table. + +Suggested v1 calculation: +1. Gather the last N purchase dates for a household + concept/product. +2. Calculate intervals between purchases. +3. Use median interval rather than simple average when enough observations exist. +4. Require a minimum observation count before suggesting. +5. Calculate due date = last purchase + median interval. +6. Apply a configurable tolerance window. + +Never automatically add predicted items to a list in v1. Suggest them. + +--- + +## 6. Entity Relationship Summary + +```text +ApplicationUser + M:N Household through HouseholdMember + +Household + 1:N ShoppingList + 1:N Purchase + 1:N HouseholdProductPreference + +ShoppingList + 1:N ShoppingListItem + +GroceryConcept + 1:N Product + 1:N ShoppingListItem + 1:N HouseholdProductPreference + +Brand + 1:N Product + +Product + 1:N ProductIdentifier + 1:N PurchaseItem + 1:N PriceObservation + +Retailer + 1:N StoreLocation + +StoreLocation + 1:N Purchase + 1:N PriceObservation + +Purchase + 1:N PurchaseItem +``` + +Avoid cascade delete where deletion would destroy historical purchase or price data. Prefer restrict/no-action for historical relationships. + +--- + +## 7. DbContext and EF Core Rules + +Use explicit IEntityTypeConfiguration classes in `Infrastructure/Data/Configurations`. + +Examples: +```text +HouseholdConfiguration.cs +ShoppingListConfiguration.cs +ShoppingListItemConfiguration.cs +ProductConfiguration.cs +ProductIdentifierConfiguration.cs +PurchaseConfiguration.cs +PurchaseItemConfiguration.cs +PriceObservationConfiguration.cs +``` + +Guidelines: +- PostgreSQL UUIDs for domain IDs. +- Store money as decimal/numeric, never float/double. +- Use timestamptz/UTC timestamps. +- Normalize searchable product/brand/concept names. +- Use indexes intentionally. +- Use optimistic concurrency on ShoppingListItem to reduce shared-list lost updates. +- Do not use lazy loading. +- Use `AsNoTracking()` for read-only queries. +- Avoid returning EF entities directly to Razor views. + +--- + +## 8. Application Services + +Create focused services. Avoid “God services”. + +### IHouseholdService / HouseholdService +Responsibilities: +- create household +- add/remove member +- verify access +- retrieve current household + +### IShoppingListService / ShoppingListService +Responsibilities: +- get active list +- create list +- add item +- mark purchased +- skip/unskip +- delete item +- reorder if implemented +- resolve item to concept/product + +### IProductService / ProductService +Responsibilities: +- search local catalog +- get product details +- resolve barcode +- create manually entered product +- call product enrichment providers when local lookup misses + +### IProductEnrichmentService +Coordinates external data without exposing provider details to controllers. + +### IPriceService / PriceService +Responsibilities: +- record price observation +- get household price history +- calculate latest observed price +- calculate median/average household paid price +- calculate lowest recent observed price +- calculate unit price when package quantity is known +- report freshness/source/confidence + +### IPurchaseService / PurchaseService +Responsibilities: +- start purchase +- add purchase item +- complete purchase +- derive PriceObservation from recorded PurchaseItem +- link purchased item to ShoppingListItem + +### IReplenishmentService / ReplenishmentService +Responsibilities: +- calculate likely-running-low concepts/products +- use transparent deterministic statistics +- return score/reason/due estimate + +### IShoppingModeService / ShoppingModeService +Responsibilities: +- prepare shopping-mode view +- group/sort items +- record purchase/skip/unavailable/substitution transitions + +### IStoreService / StoreService +Responsibilities: +- CRUD/search store locations used by household +- retailer/store resolution + +--- + +## 9. External Product Data Abstraction + +Define: + +```csharp +public interface IProductDataProvider +{ + string Name { get; } + + Task FindByBarcodeAsync( + string barcode, + CancellationToken cancellationToken = default); + + Task> SearchAsync( + string query, + CancellationToken cancellationToken = default); +} +``` + +Potential implementations: +- `OpenFoodFactsProductDataProvider` +- `UsdaProductDataProvider` + +Provider data must be normalized into CartWise domain/application DTOs before persistence. + +Never bind Razor pages directly to provider-specific response models. + +### Lookup Flow + +```text +Barcode received + -> normalize barcode + -> local ProductIdentifier query + -> if found, return local product + -> if missing, query configured provider(s) + -> normalize result + -> persist Product + ProductIdentifier if acceptable + -> return product +``` + +Respect provider rate limits. Cache imported products locally. + +--- + +## 10. Retailer Adapter Contract — Future-Compatible v1 + +Retailer integrations are not required for initial launch, but design an interface so they can be added later. + +```csharp +[Flags] +public enum RetailerCapabilities +{ + None = 0, + StoreSearch = 1, + ProductSearch = 2, + Pricing = 4, + Inventory = 8, + Promotions = 16, + AisleLocation = 32 +} + +public interface IRetailerProvider +{ + string Name { get; } + RetailerCapabilities Capabilities { get; } +} +``` + +Do not fill v1 with fake methods that no provider can support yet. Add capability-specific interfaces when implementing real integrations. + +Never write controller/domain logic such as `if (retailer.Name == "Meijer")`. + +--- + +## 11. MVC Controllers and Responsibilities + +Controllers must be thin and delegate to services. + +### HomeController +Routes: +```text +GET / +GET /Home/Index +``` +Responsibilities: +- dashboard +- active list summary +- running-low suggestions +- recent price highlights + +### HouseholdController +Routes: +```text +GET /household +GET /household/create +POST /household/create +GET /household/members +POST /household/members/invite [future/simple v1 if desired] +POST /household/members/remove +``` + +### ShoppingListController +Prefer clean route attributes. + +Routes: +```text +GET /list +POST /list/items +POST /list/items/{id}/toggle +POST /list/items/{id}/skip +POST /list/items/{id}/delete +POST /list/items/{id}/resolve +``` + +For progressive enhancement: +- standard form posts must work where practical +- JS-enhanced endpoints may return partial HTML or JSON +- anti-forgery is mandatory on state-changing requests + +### ProductController +Routes: +```text +GET /products/{id} +GET /products/search?q=milk +GET /products/barcode/{barcode} +``` + +If live search needs JSON: +```text +GET /api/products/search?q=milk +GET /api/products/barcode/{barcode} +``` + +Only introduce `/api` endpoints for actual client-side needs. + +### ShoppingController +Routes: +```text +GET /shopping/start +POST /shopping/items/{id}/purchase +POST /shopping/items/{id}/skip +POST /shopping/items/{id}/unavailable +POST /shopping/items/{id}/substitute +POST /shopping/complete +``` + +### PriceController +Routes: +```text +GET /prices +GET /prices/product/{productId} +POST /prices/product/{productId}/observe +``` + +### PurchaseController +Routes: +```text +GET /purchases +GET /purchases/{id} +``` + +Manual purchase edit screens can be added if needed but are not core first-pass scope. + +### ScanController +Routes: +```text +GET /scan +``` + +The scan page uses camera/barcode JS and requests product resolution through the Product endpoint. + +--- + +## 12. ViewModels + +Never pass EF Core entities directly to views. + +### HomeViewModel +```text +HouseholdName +ActiveListItemCount +RunningLowSuggestions[] +RecentPriceInsights[] +``` + +### ShoppingListViewModel +```text +ShoppingListId +Name +Items[] +RunningLowSuggestions[] +``` + +### ShoppingListItemViewModel +```text +Id +DisplayName +QuantityDisplay +Status +ProductId? +ProductImageUrl? +IsResolved +PriceHint? +ConcurrencyToken +``` + +### ProductDetailsViewModel +```text +ProductId +Name +BrandName +ConceptName +PackageDisplay +BarcodeIdentifiers[] +ImageUrl +LatestPrice? +AverageHouseholdPrice? +LowestHouseholdPrice? +UnitPrice? +PriceHistory[] +``` + +### ShoppingModeViewModel +```text +ShoppingListId +StoreLocation? +RemainingItemCount +PurchasedItemCount +Groups[] +``` + +### ShoppingModeGroupViewModel +```text +Name +SortOrder +Items[] +``` + +Group names in v1 may be category-based rather than real store aisles: +- Produce +- Meat +- Dairy +- Pantry +- Frozen +- Household +- Other + +### PriceHistoryViewModel +```text +ProductId +ProductName +Observations[] +AveragePaid +MedianPaid +LowestPaid +LatestPaid +``` + +### RunningLowSuggestionViewModel +```text +GroceryConceptId +Name +LastPurchasedUtc +TypicalIntervalDays +EstimatedDueUtc +Confidence +Reason +``` + +--- + +## 13. Razor View Structure + +```text +Views/ + Shared/ + _Layout.cshtml + _MobileBottomNav.cshtml + _ValidationScriptsPartial.cshtml + _ProductImage.cshtml + Error.cshtml + + Home/ + Index.cshtml + + Household/ + Index.cshtml + Create.cshtml + Members.cshtml + + ShoppingList/ + Index.cshtml + _ListItem.cshtml + _RunningLowSuggestion.cshtml + + Product/ + Details.cshtml + Search.cshtml + _SearchResults.cshtml + + Shopping/ + Start.cshtml + _ShoppingItem.cshtml + + Price/ + Index.cshtml + Product.cshtml + + Purchase/ + Index.cshtml + Details.cshtml + + Scan/ + Index.cshtml +``` + +Use partials for server-rendered fragments that JavaScript may refresh. + +--- + +## 14. jQuery and Vanilla JavaScript Organization + +```text +wwwroot/js/ + pages/ + shopping-list.js + shopping-mode.js + product-search.js + scan.js + price-history.js + + services/ + http.js + barcode.js + offline.js # later + + components/ + toast.js + dialog.js + + utils/ + dom.js + money.js + dates.js +``` + +Rules: +- jQuery and vanilla JavaScript are both approved frontend tools +- prefer jQuery for straightforward DOM selection/manipulation, delegated events, form serialization, AJAX calls, and partial-page updates when it makes the code shorter and clearer +- prefer vanilla JavaScript for browser-native APIs such as `navigator.mediaDevices`, Service Workers, IndexedDB, Web APIs, and small focused logic where jQuery provides no benefit +- use `$.ajax`, `$.get`, or `$.post` for jQuery-oriented asynchronous requests; `fetch` is also allowed when native code is clearer +- ES modules are allowed for larger or isolated frontend features, but do not force every small script into a module +- do not mix jQuery and native DOM code arbitrarily within the same function; choose the clearest style for that unit of work +- no giant global `app.js` +- no framework-like custom abstraction layer +- do not build a homemade SPA or component framework +- preserve non-JS fallback where reasonable +- send ASP.NET Core anti-forgery tokens on all state-changing AJAX/fetch requests +- keep selectors stable by using semantic IDs, classes, and `data-*` attributes rather than brittle DOM traversal + +--- + +## 15. Primary Page Designs for v1 + +### A. Home +Show: +- active list count +- quick add +- likely-running-low suggestions +- recent “good price”/price-history insights only if enough data exists + +### B. Smart Grocery List +Show: +- quick add input +- unresolved text items allowed +- resolved product indication +- check/skip/delete +- household changes reflected after reload; near-real-time sync can come later + +### C. Product Details / Price Intelligence +Show: +- product image/name/brand/size +- identifiers +- household normal price +- lowest observed price +- latest observed price +- price history +- source/freshness on observations + +### D. Scan +Show: +- camera permission request +- barcode scanner +- resolved product result +- Add to List +- Record Purchase / Record Price + +### E. Shopping Mode +Show: +- large touch targets +- remaining count +- grouped list +- purchase action +- optional price entry +- skip/unavailable +- minimum visual noise + +### F. Prices +Show: +- products the household has purchased +- latest / average / low price +- sort/filter/search + +### G. Household +Show: +- members +- household name +- product preferences +- preferred stores later + +--- + +## 16. Price Intelligence Rules + +Implement deterministic functions first. + +### Unit Price +If price and package size are known: + +```text +unitPrice = effectivePrice / packageSize +``` + +Keep unit labels. Do not compare incompatible units without an explicit conversion service. + +### Household Average Price +Use purchase/observed history for the household and product. + +Prefer both: +- arithmetic average for reporting +- median for “typical” price because grocery promotions cause outliers + +### Good Price v1 +Only display if minimum history threshold is met. + +Example: +```text +if current <= 10th/20th percentile or materially below median + label = Good Price +``` + +Do not hardcode an arbitrary universal threshold without tests. + +### Freshness +Display: +- observation date +- source type +- confidence if useful + +Do not display “current price” for stale user observations. Prefer “last observed”. + +--- + +## 17. Security and Authorization + +Mandatory: +- ASP.NET Core Identity +- authorization checks on every household-scoped resource +- anti-forgery tokens on state changes +- server-side validation +- encode all user-entered output through Razor defaults +- do not trust ProductId/HouseholdId sent by client +- prevent IDOR by checking household membership +- secure external API keys in configuration/user secrets/environment variables +- never place provider secrets in JS +- rate limit public search/scan endpoints if exposed anonymously + +Use policies or a centralized household authorization service rather than repeating fragile checks. + +--- + +## 18. Validation Rules + +Examples: +- Shopping item display name: required, max 240 +- Quantity: positive when supplied +- Money: non-negative, realistic maximum +- Barcode: normalize digits; validate supported length/check digit when practical +- Household name: required, max 120 +- Product names: required when manual product created +- Price observation must have ProductId + price + observation date/source + +Return user-friendly validation messages. Log technical details server-side. + +--- + +## 19. Logging / Diagnostics + +Use `ILogger` structured logging. + +Log: +- external provider failures +- unresolved barcodes +- product import/enrichment outcomes +- unexpected price calculation errors +- authorization failures without sensitive detail + +Do not log: +- passwords +- auth tokens +- complete sensitive user payloads unnecessarily + +Include correlation/request IDs through normal ASP.NET logging. + +--- + +## 20. Testing Strategy + +### Domain Tests +Test: +- unit price calculations +- price statistics +- replenishment interval calculations +- substitution enum/rules if logic exists + +### Application Tests +Test: +- add list item +- resolve barcode local hit/miss +- purchase creates price observation +- household access enforcement +- running-low service + +### Web / Integration Tests +Use WebApplicationFactory where useful. + +Test: +- unauthenticated redirects +- anti-forgery behavior +- household resource isolation +- key MVC routes +- form validation + +Do not spend early effort on pixel-perfect browser automation before domain workflows stabilize. + +--- + +## 21. Seed / Development Data + +Create development-only seed data: +- one test household +- 10–20 grocery concepts +- several brands/products +- identifiers +- sample store locations +- 60–90 days of purchase/price history + +Never seed production with fake price data. + +Suggested concepts: +- Milk +- Eggs +- Bread +- Bananas +- Chicken Breast +- Ground Beef +- Peanut Butter +- Coffee +- Cereal +- Cheddar Cheese + +--- + +## 22. Exact Implementation Order + +Agents must follow this order unless a blocking dependency requires a small adjustment. + +### Phase 0 — Repository and Baseline +1. Create `CartWise.sln` and the four projects. +2. Add project references with clean direction: + - Web -> Application + - Web -> Infrastructure only for startup registration if necessary + - Application -> Domain + - Infrastructure -> Application + Domain +3. Configure nullable reference types. +4. Configure formatting/analyzers. +5. Configure PostgreSQL connection through environment/user secrets. +6. Add ASP.NET Core Identity. +7. Verify app starts and basic test project runs. + +**Exit condition:** authenticated MVC shell runs against PostgreSQL. + +### Phase 1 — Household Foundation +1. ApplicationUser. +2. Household. +3. HouseholdMember. +4. EF configurations/migration. +5. Household service. +6. Household authorization helper/policy. +7. Household create/index UI. +8. Tests. + +**Exit condition:** a user can create a household and all household data is access-controlled. + +### Phase 2 — Smart Shopping List +1. GroceryConcept. +2. ProductCategory. +3. ShoppingList. +4. ShoppingListItem. +5. migrations/configurations. +6. ShoppingListService. +7. ShoppingListController. +8. list Razor view and partial. +9. add/toggle/delete functionality. +10. minimal jQuery/vanilla JS progressive enhancement. +11. concurrency handling. +12. tests. + +**Exit condition:** household users can maintain a useful shared grocery list even with zero product catalog data. + +### Phase 3 — Product Catalog and Barcode Resolution +1. Brand. +2. Product. +3. ProductIdentifier. +4. HouseholdProductPreference. +5. database migration/indexes. +6. ProductService. +7. `IProductDataProvider`. +8. implement Open Food Facts provider first. +9. local-first barcode lookup. +10. ProductController routes. +11. product details/search views. +12. Scan page and barcode JS integration. +13. tests with provider mocked. + +**Exit condition:** scan known barcode -> local or external lookup -> product persisted -> viewable/addable to list. + +### Phase 4 — Stores, Purchases, and Price History +1. Retailer. +2. StoreLocation. +3. Purchase. +4. PurchaseItem. +5. PriceObservation. +6. migration/indexes. +7. StoreService. +8. PurchaseService. +9. PriceService. +10. guarantee a purchase item can create a price observation. +11. PriceController + price views. +12. Product details price-history section. +13. tests. + +**Exit condition:** user records a purchase price and later sees accurate personal price history. + +### Phase 5 — Shopping Mode +1. ShoppingModeViewModel. +2. ShoppingModeService. +3. category grouping. +4. mobile-first shopping view. +5. purchase/skip/unavailable actions. +6. optional price entry per purchase. +7. purchase completion flow. +8. tests. + +**Exit condition:** a shopper can use the app comfortably during a real grocery trip. + +### Phase 6 — Running-Low Suggestions +1. Replenishment service. +2. median interval algorithm. +3. confidence/minimum-history rules. +4. Home and List suggestion partials. +5. “Add suggestion to list” action. +6. tests against known purchase histories. + +**Exit condition:** app can suggest likely-needed repeat items without ML or automatic list mutation. + +### Phase 7 — Polish / PWA Preparation +1. accessibility pass. +2. responsive/mobile testing. +3. performance profiling. +4. service worker/manifest only if explicitly included in v1 release. +5. IndexedDB/offline queue only after core workflows are stable. +6. production diagnostics and health checks. + +--- + +## 23. Definition of Done for v1 + +CartWise v1 is done when an authenticated household can: + +1. Create/use a household. +2. Maintain a grocery list from a phone. +3. Add free-text grocery concepts without requiring catalog resolution. +4. Resolve and scan UPC/GTIN products. +5. Save product preferences. +6. Enter shopping mode. +7. Mark items purchased/skipped/unavailable. +8. Record purchase prices. +9. See personal price history with source/freshness. +10. See unit prices when enough data exists. +11. Receive deterministic running-low suggestions from purchase history. +12. Use the application without any retailer API integration. + +--- + +## 24. Explicitly Out of Scope for v1 + +Unless the user explicitly changes scope, do not build: +- grocery delivery +- checkout/payment processing +- retailer loyalty login +- coupon clipping +- live universal inventory +- universal retailer price scraping +- meal planning +- recipe generation +- nutrition optimization +- AI chatbot +- multi-store route optimization +- indoor store maps +- aisle crowdsourcing +- receipt OCR +- shrinkflation alerts +- push notifications +- social features + +Architectural seams may be left for later, but do not build speculative systems. + +--- + +## 25. Agent Coding Rules + +1. Read this file before changing architecture. +2. Inspect existing code before creating parallel abstractions. +3. Prefer extending existing patterns over inventing new ones. +4. Keep controllers thin. +5. Keep persistence code out of Razor views and controllers. +6. Keep external provider response types inside Infrastructure. +7. Add tests for business rules and bug fixes. +8. Run build/tests after meaningful changes. +9. Do not silently change database semantics. +10. Create migrations for schema changes; never hand-edit production schema. +11. Never fabricate retailer capabilities or data. +12. If external data is unavailable, represent it as unavailable/stale—not guessed. +13. Preserve backward-compatible URLs where practical once routes ship. +14. Use comments for why, not obvious what. +15. Prefer clear conventional C# over clever abstraction. + +--- + +## 26. Naming Conventions + +- Entities: singular nouns (`Product`, `PurchaseItem`). +- Controllers: `Controller`. +- Services: `IService` + `Service`. +- ViewModels: page/use-case specific (`ShoppingModeViewModel`). +- Async methods end with `Async`. +- UTC timestamp properties end in `Utc`. +- IDs use `NameId` except Identity's standard `Id` where appropriate. +- JavaScript files use kebab-case. +- Razor partials begin with `_`. + +--- + +## 27. First Vertical Slice + +If an agent is asked to “start building CartWise,” do not scaffold every feature simultaneously. + +Build this first vertical slice end-to-end: + +```text +Register/Login + -> Create Household + -> Open Active Grocery List + -> Add “Milk” + -> Toggle Purchased + -> Reload and verify persisted state +``` + +Then extend it in the implementation order above. + +--- + +## 28. Product North Star + +When making tradeoffs, remember: + +> CartWise is the shopper's grocery memory. + +The first release should make it effortless to remember **what the household needs, what it buys, and what it normally pays**. Retailer integrations are enhancements, not foundations. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..810d34f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,482 @@ +# CLAUDE.md — CartWise v1 Development Guide + +## Read First + +This repository builds **CartWise**, a mobile-first grocery companion implemented as a conventional **ASP.NET Core MVC** application. + +The canonical architecture and scope are defined in `AGENTS.md`. Read `AGENTS.md` completely before making architectural changes. This file gives Claude-specific operating guidance and a compact execution map. + +--- + +## Core Stack + +Use: +- ASP.NET Core MVC +- C# +- Razor Views +- HTML5 +- CSS3 +- jQuery +- vanilla JavaScript +- EF Core +- PostgreSQL +- ASP.NET Core Identity + +Do not introduce React, Vue, Angular, Blazor, MAUI, Flutter, SPA architecture, microservices, MediatR, or unnecessary repository/unit-of-work wrappers unless explicitly requested. + +--- + +## Product Goal + +CartWise v1 must help a household: + +1. Maintain a shared grocery list. +2. Add generic concepts such as “milk” without requiring exact product selection. +3. Resolve exact packaged products when useful. +4. Scan UPC/GTIN barcodes. +5. Record purchases and prices. +6. Build personal price history. +7. Show typical/low/latest/unit price intelligence. +8. Suggest repeat items that are likely running low. +9. Shop from a simple phone-friendly shopping mode. + +CartWise must still be useful with **zero retailer API access**. + +--- + +## Architectural Boundaries + +### Web +`CartWise.Web` +- Controllers +- Razor Views +- ViewModels +- HTML/CSS/JS +- ASP.NET-specific concerns + +### Application +`CartWise.Application` +- use cases +- services +- interfaces +- validation +- DTOs/results + +### Domain +`CartWise.Domain` +- entities +- enums +- value objects +- business rules + +No EF Core, HTTP, Razor, or provider SDK dependencies here. + +### Infrastructure +`CartWise.Infrastructure` +- DbContext +- EF configurations/migrations +- PostgreSQL +- Open Food Facts/USDA integrations +- future retailer adapters + +Dependency direction should remain clean. + +--- + +## Critical Domain Distinction + +Never collapse these concepts: + +```text +GroceryConcept: Peanut Butter + +Product: Jif Creamy Peanut Butter 16 oz + +ProductIdentifier: UPC/GTIN identifying that package +``` + +A shopping list item may exist with only free text / concept data. Product resolution must not be mandatory to make a useful list. + +UPC/GTIN is never the Product primary key. + +--- + +## Core Entities + +Implement in the order given in `AGENTS.md`. + +### Identity / Household +- ApplicationUser +- Household +- HouseholdMember + +### Product Catalog +- GroceryConcept +- ProductCategory +- Brand +- Product +- ProductIdentifier +- HouseholdProductPreference + +### Shopping +- ShoppingList +- ShoppingListItem + +### Retail / Location +- Retailer +- StoreLocation + +### History +- Purchase +- PurchaseItem +- PriceObservation + +See `AGENTS.md` for exact fields, indexes, enums, and relationships. + +--- + +## Required Services + +Keep controllers thin and use: + +- `IHouseholdService` +- `IShoppingListService` +- `IProductService` +- `IProductEnrichmentService` +- `IPriceService` +- `IPurchaseService` +- `IReplenishmentService` +- `IShoppingModeService` +- `IStoreService` + +Do not create one giant `CartWiseService`. + +Do not add abstractions without a real responsibility or testability benefit. + +--- + +## Product Provider Rule + +External provider models stay inside Infrastructure. + +Use a normalized contract similar to: + +```csharp +public interface IProductDataProvider +{ + string Name { get; } + + Task FindByBarcodeAsync( + string barcode, + CancellationToken cancellationToken = default); + + Task> SearchAsync( + string query, + CancellationToken cancellationToken = default); +} +``` + +Lookup is always **local first**, provider second, then normalize and cache. + +Open Food Facts should be the first packaged-product provider. USDA can enrich generic/nutrition data later. + +Never expose an external provider's API response object to a controller or Razor view. + +--- + +## Retailer API Rule + +Retailer integrations are optional. + +Do not build application logic around specific retailer names. Use capability-aware adapters. + +Never assume that price, inventory, promotions, or aisle data are available. + +When data is stale or unavailable, show that state explicitly instead of guessing. + +--- + +## MVC Routes + +Preserve these route intentions unless existing code requires a compatible adjustment. + +```text +GET / Home +GET /household Household +GET /list Active grocery list +POST /list/items Add list item +POST /list/items/{id}/toggle Toggle purchased +POST /list/items/{id}/skip +POST /list/items/{id}/delete + +GET /products/{id} +GET /products/search?q= +GET /products/barcode/{barcode} + +GET /scan + +GET /shopping/start +POST /shopping/items/{id}/purchase +POST /shopping/items/{id}/skip +POST /shopping/items/{id}/unavailable +POST /shopping/items/{id}/substitute +POST /shopping/complete + +GET /prices +GET /prices/product/{productId} +POST /prices/product/{productId}/observe + +GET /purchases +GET /purchases/{id} +``` + +Use `/api/...` only where browser JavaScript genuinely benefits from JSON. + +--- + +## Razor / JavaScript Rules + +Razor is primary rendering. jQuery and vanilla JavaScript are both approved. + +Prefer jQuery for: +- DOM selection and manipulation +- delegated event handlers +- form serialization +- AJAX requests +- lightweight partial refreshes +- shopping-mode UI interactions + +Prefer vanilla JavaScript for: +- barcode camera/scanner browser APIs +- `navigator.mediaDevices` +- IndexedDB and Service Workers +- focused browser-native functionality where jQuery adds no value + +Organize JavaScript under: + +```text +wwwroot/js/pages/ +wwwroot/js/services/ +wwwroot/js/components/ +wwwroot/js/utils/ +``` + +Do not create a global monolithic `app.js`. +Do not reinvent client-side framework patterns by hand. +Do not mix jQuery and native DOM approaches arbitrarily inside the same function. +Use `$.ajax`/`$.get`/`$.post` where jQuery is the clearer choice and `fetch` where native code is more appropriate. + +Use anti-forgery tokens for every state-changing JavaScript request. + +--- + +## Database Rules + +- PostgreSQL UUID domain keys. +- Identity's normal user key can remain its native string unless deliberately changed project-wide. +- UTC timestamps / timestamptz. +- decimal/numeric for money and quantities. +- no float/double for prices. +- explicit EF entity configurations. +- no lazy loading. +- `AsNoTracking()` for read paths. +- protect historical data from accidental cascade deletion. +- `PriceObservation` is append-only. +- add indexes described in `AGENTS.md`. + +Every schema change requires an EF Core migration. + +--- + +## Price Intelligence + +Do not call a stale observation a current price. + +Every price observation includes: +- Product +- optional Store +- Price/SalePrice +- ObservedUtc +- SourceType +- ConfidenceScore + +Display source/freshness where material. + +For a household's typical price, favor median as the robust statistic; average may also be shown. + +Only label a price “good” after enough history exists. + +Unit price comparisons must use compatible units. + +--- + +## Replenishment Algorithm v1 + +No ML or LLM required. + +Use purchase history: +1. get recent purchase dates +2. calculate intervals +3. use median interval when enough data exists +4. estimate next due date +5. return a suggestion with confidence and reason + +Never automatically add predicted groceries to the list. The user chooses. + +--- + +## Authorization + +Household isolation is critical. + +For every household-scoped resource: +- derive or validate household context server-side +- verify current user membership +- do not trust posted HouseholdId alone +- prevent IDOR + +Use centralized household authorization/service logic. + +All state-changing MVC requests require anti-forgery protection. + +--- + +## Working Style for Claude + +Before modifying code: +1. Read `AGENTS.md`. +2. Inspect the relevant existing controller/service/entity/configuration/tests. +3. Identify the smallest vertical change that satisfies the request. +4. Preserve existing architecture unless it conflicts with the specification. + +While implementing: +1. Keep methods small and intention-revealing. +2. Prefer conventional C#. +3. Use async I/O. +4. Pass `CancellationToken` through external/database-heavy application paths where appropriate. +5. Add validation close to the boundary and invariants in the domain where appropriate. +6. Avoid duplicate normalization or authorization logic. + +After implementing: +1. Build the solution. +2. Run relevant tests. +3. Run the full test suite when practical. +4. Check migrations when data model changes. +5. Report files changed and any intentionally deferred work. + +Do not claim a feature works without running the relevant build/tests when the environment permits it. + +--- + +## Exact Build Order + +Follow this progression: + +### 0. Baseline +- solution/projects +- dependencies +- PostgreSQL +- Identity +- test harness + +### 1. Household +- household entities +- membership +- authorization +- create/select household + +### 2. Grocery List +- concepts/categories +- list/list items +- add/toggle/delete +- mobile Razor view + +### 3. Products and Scan +- brand/product/identifiers +- household product preferences +- local product lookup +- Open Food Facts provider +- barcode scan page + +### 4. Purchase and Price History +- retailer/store +- purchase/purchase item +- price observations +- price service and views + +### 5. Shopping Mode +- grouped mobile list +- purchase/skip/unavailable +- optional entered price +- complete trip + +### 6. Running Low +- deterministic replenishment service +- suggestions on Home/List + +### 7. Polish +- accessibility +- mobile/responsive testing +- performance +- PWA/offline only when core workflows are stable + +Do not jump to Phase 6 features while Phase 2 is incomplete unless the user explicitly requests it. + +--- + +## First End-to-End Slice + +When asked to begin implementation, build this before broad scaffolding: + +```text +Register/Login + -> Create Household + -> Open /list + -> Add “Milk” + -> Persist item + -> Toggle Purchased + -> Reload + -> Verify correct household-scoped state +``` + +This proves authentication, household authorization, EF persistence, MVC routing, Razor rendering, and form/JS behavior. + +--- + +## Out of Scope Unless Explicitly Added + +Do not implement proactively: +- meal planning +- recipes +- AI chat +- nutrition optimization +- receipt OCR +- coupon clipping +- retailer loyalty accounts +- delivery/checkout +- live universal inventory +- store aisle maps +- indoor navigation +- multi-store routing +- shrinkflation analysis +- push notifications + +Leave clean seams for future work; do not build speculative infrastructure. + +--- + +## Definition of a Good CartWise Change + +A good change: +- improves the shopper's grocery memory +- keeps MVC server-rendered +- works on a phone +- does not require a retailer API unless specifically implementing one +- preserves price source/freshness +- maintains household isolation +- adds tests for meaningful rules +- avoids unnecessary framework or architecture expansion + +North star: + +> **CartWise remembers what the household needs, what it buys, and what it normally pays.** diff --git a/README.md b/README.md new file mode 100644 index 0000000..c717405 --- /dev/null +++ b/README.md @@ -0,0 +1,195 @@ +# CartWise + +CartWise is a mobile-first grocery companion web application built with ASP.NET Core MVC. It helps households manage shared grocery lists, remember preferred products, record purchases, and build a trustworthy personal price history. + +## Vision + +CartWise is not a grocery delivery platform and is not tied to a single retailer. Its first job is to become the shopper's grocery memory by helping households: + +- Maintain shared household grocery lists +- Resolve generic grocery concepts like `milk` or `peanut butter` to preferred products +- Scan UPC/GTIN barcodes and identify products +- Record what was purchased, where, and for how much +- Build personal price history over time +- Show useful price intelligence such as latest observed, typical, and lowest price +- Suggest items that may be running low using deterministic purchase intervals +- Support a simple, touch-friendly shopping mode that works well on a phone + +## v1 Scope + +CartWise v1 focuses on the core grocery-memory workflow: + +1. Household creation and access control +2. Shared grocery list management +3. Product catalog and barcode resolution +4. Purchase and price history tracking +5. Mobile shopping mode +6. Running-low suggestions based on purchase history + +The following are explicitly out of scope for v1 unless requirements change: + +- Grocery delivery +- Retailer checkout or payment processing +- Coupon clipping +- Universal live inventory scraping +- Meal planning and recipe generation +- AI chatbot features +- Social features + +## Technology Stack + +### Application + +- ASP.NET Core MVC +- C# +- Razor Views +- HTML5 +- CSS3 +- jQuery +- Vanilla JavaScript +- Entity Framework Core +- PostgreSQL +- ASP.NET Core Identity + +### Architecture + +CartWise is designed as a modular monolith with server-rendered pages first and JavaScript used for progressive enhancement. + +```text +CartWise.sln + +src/ + CartWise.Web/ + CartWise.Application/ + CartWise.Domain/ + CartWise.Infrastructure/ + +tests/ + CartWise.Domain.Tests/ + CartWise.Application.Tests/ + CartWise.Web.Tests/ +``` + +## Product Principles + +- Server-rendered first +- Mobile-first UX +- Retailer independence +- Append-only price history where possible +- Grocery concepts are distinct from sellable products +- UPC/GTIN is an identifier, not the product primary key +- Deterministic, explainable price and replenishment logic +- AI remains optional rather than required for core workflows + +## Core Domain Areas + +- **Households**: users, memberships, and household-scoped access +- **Shopping Lists**: shared lists with unresolved free-text items allowed +- **Catalog**: grocery concepts, brands, products, and product identifiers +- **Preferences**: household-level product preferences for generic grocery concepts +- **Stores and Retailers**: store locations for purchase and price context +- **Purchases**: household transaction history +- **Price Observations**: append-only record of observed prices +- **Replenishment**: deterministic running-low suggestions using purchase intervals + +## Planned Features + +### Phase 0 - Repository and Baseline +- Solution and project setup +- PostgreSQL configuration +- Identity authentication +- Build and test baseline + +### Phase 1 - Household Foundation +- Household creation +- Membership management +- Household authorization + +### Phase 2 - Smart Shopping List +- Shared active grocery list +- Free-text item entry +- Toggle, skip, delete, and concurrency handling + +### Phase 3 - Product Catalog and Barcode Resolution +- Product and identifier modeling +- Product search and details +- Local-first barcode lookup +- Open Food Facts provider integration +- Scan page + +### Phase 4 - Stores, Purchases, and Price History +- Store and retailer setup +- Purchase recording +- Price observation history +- Price insights and reporting + +### Phase 5 - Shopping Mode +- Touch-friendly in-store experience +- Purchase, skip, unavailable, and substitute actions +- Optional price entry during shopping + +### Phase 6 - Running-Low Suggestions +- Median-interval replenishment calculation +- Dashboard and list suggestions +- Add suggestion back to list + +### Phase 7 - Polish and Release Readiness +- Accessibility and responsive review +- Security hardening +- Logging and diagnostics +- Development seed data + +## Development Status + +This repository is being initialized from the `AGENTS.md` build specification. The current focus is establishing the project foundation, backlog, and delivery workflow before implementing the application phases. + +## Getting Started + +The solution scaffolding is planned but may not yet be present in this repository. Once the projects are created, the expected local development flow will be: + +1. Install the .NET SDK +2. Install PostgreSQL +3. Configure connection settings via environment variables or user secrets +4. Run EF Core migrations +5. Start the ASP.NET Core MVC app + +Example commands that will be used once the solution exists: + +```bash +dotnet restore +dotnet build +dotnet test +dotnet run --project src/CartWise.Web +``` + +## Backlog and Delivery + +The team plans to manage this project using Scrum with: + +- Epics aligned to the implementation phases +- User stories for each feature slice +- Tasks under each story for development, testing, and UI work +- Two-week sprints +- A definition of done that includes tests, authorization, validation, and logging + +## Security and Data Principles + +- Every household-scoped action must enforce membership authorization +- State-changing requests must use anti-forgery protection +- User-entered data must be server-side validated +- External provider secrets must stay out of client-side code +- Price history should preserve observation history instead of overwriting past records + +## Contributing + +When contributing: + +- Follow the architecture and implementation order in `AGENTS.md` +- Keep controllers thin +- Keep domain logic out of Razor views and controllers +- Add tests for business rules and bug fixes +- Prefer clear, conventional C# over unnecessary abstraction + +## License + +No license has been defined yet for this repository.