This document translates the product and implementation guidance from AGENTS.md into a Scrum-friendly backlog for CartWise v1. It organizes work into epics, user stories, and implementation tasks that align with the required phased delivery order.
This backlog is optimized for a solo founder + AI delivery model targeting a local MVP by September 10, 2026.
CartWise is a mobile-first grocery companion web app that helps households remember what to buy, what they bought, what they paid, and what they may need again soon.
XS, S, M, and L sizing on storiesL before implementationA story is ready when:
A story is done when:
AGENTS.md architecture and scope- [ ] for not started tasks- [x] for completed tasksRelease: MVP means required for the local launch target by September 10, 2026Release: Post-MVP means explicitly deferred until after MVP is stableRelease: Deferred means do not implement unless scope changesPriority: P0 means must ship for MVPPriority: P1 means valuable next but can slipPriority: P2 means later or exploratoryEffort: XS under 2 hours, S about half a day, M about a day, L 2 or more daysDecision Gate calls out places where the founder must make a product or architecture decision before implementation continuesCW-EPIC-01 Platform FoundationCW-EPIC-02 Household and Access ControlCW-EPIC-03 Smart Shopping ListCW-EPIC-05 Stores, Purchases, and Price IntelligenceCW-EPIC-07 Running-Low SuggestionsCW-EPIC-08CW-EPIC-04 Product Catalog and Barcode ResolutionCW-EPIC-06 Shopping ModeCW-EPIC-08CW-EPIC-01 Platform FoundationGoal: establish the solution structure, authentication, SQLite wiring for MVP, and baseline engineering standards.
CW-STORY-01.1 Create solution skeletonRelease: MVP
Priority: P0
Effort: S
User story: As a developer, I want the CartWise solution scaffolded so the app follows a clean modular architecture.
Acceptance criteria
CartWise.sln existssrc/ and tests/ project layout matches the specTasks
CartWise.slnsrc/CartWise.Websrc/CartWise.Applicationsrc/CartWise.Domainsrc/CartWise.Infrastructuretests/CartWise.Domain.Teststests/CartWise.Application.Teststests/CartWise.Web.TestsStatus: Done — solution builds and baseline template tests pass across all three test projects. CartWise.Web references CartWise.Infrastructure in addition to CartWise.Application for DI/startup registration (allowed per AGENTS.md §22 Phase 0).
CW-STORY-01.2 Configure application startupRelease: MVP
Priority: P0
Effort: M
User story: As a developer, I want a runnable MVC shell so the team can build on a working baseline.
Acceptance criteria
Tasks
wwwroot structureStatus: Done — app starts and serves the rebranded layout and static assets on a local port (verified via dotnet run + curl smoke check). CartWiseDbContext (empty for now — entities land in CW-EPIC-02) is wired to SQLite through CartWise.Infrastructure.InfrastructureServiceCollectionExtensions.AddInfrastructure, using ConnectionStrings:DefaultConnection in appsettings.json. PostgreSQL transition path documented in docs/decision-log.md under DEC-005.
CW-STORY-01.3 Add Identity authenticationRelease: MVP
Priority: P0
Effort: M
User story: As a user, I want to sign in so my household data is protected.
Acceptance criteria
Tasks
ApplicationUserStatus: Done — ApplicationUser : IdentityUser (DisplayName, CreatedUtc) lives in CartWise.Infrastructure.Identity; CartWiseDbContext is now IdentityDbContext<ApplicationUser>. Identity is registered via AddInfrastructure (AddIdentity + AddEntityFrameworkStores<CartWiseDbContext>). Auth UI is a conventional MVC AccountController (Register/Login/Logout) with matching Razor views under Views/Account, anti-forgery on all POSTs, open-redirect guarded via Url.IsLocalUrl. HomeController.Privacy carries [Authorize] as the protected-route proof. Initial InitialIdentity EF Core migration created in CartWise.Infrastructure/Data/Migrations; the Development environment auto-applies pending migrations on startup (Program.cs) so local setup needs no manual dotnet ef database update step. Verified end-to-end via a live dotnet run + curl pass: anonymous access to a protected route redirects to /Account/Login?ReturnUrl=..., register auto-signs-in, logout clears the session, login re-authenticates, and a wrong password is rejected with a clear error.
CW-STORY-01.4 Establish engineering baselineRelease: MVP
Priority: P1
Effort: S
User story: As a team, we want build and test standards so changes remain stable over time.
Acceptance criteria
Tasks
Status: Done — Directory.Build.props centralizes Nullable, ImplicitUsings, and .NET analyzers (EnableNETAnalyzers, AnalysisLevel=latest-recommended, EnforceCodeStyleInBuild) across all 7 projects; per-project .csproj files no longer duplicate those settings. Root .editorconfig added (generated via dotnet new editorconfig), with a scoped override so CA1707 doesn't fight the xUnit Method_Scenario_Expected naming convention under tests/. Logging defaults tuned in appsettings.json (quiet Microsoft.EntityFrameworkCore/Identity categories) and appsettings.Development.json (Microsoft.EntityFrameworkCore.Database.Command: Information to see generated SQL locally). Smoke coverage added where there's real behavior to smoke — a CartWiseWebApplicationFactory-based integration suite (tests/CartWise.Web.Tests/SmokeTests.cs) boots the full app (DI, EF Core/SQLite, Identity, routing) against an isolated temp SQLite file and asserts: home page loads, an anonymous request to an [Authorize] route redirects to /Account/Login, and both auth pages load. CartWise.Domain.Tests/CartWise.Application.Tests still carry only the template placeholder test — there's no real domain/application logic yet to smoke-test; meaningful coverage there starts with CW-STORY-02.1. CI-oriented commands documented in README.md (dotnet restore/build/test against CartWise.sln; analyzers enforced at build time, not a separate lint step).
CW-EPIC-02 Household and Access ControlGoal: enable household creation, membership, and household-scoped authorization.
CW-STORY-02.1 Model households and membershipRelease: MVP
Priority: P0
Effort: S
User story: As a developer, I want household entities persisted so household features have a secure foundation.
Acceptance criteria
Tasks
Household entityHouseholdMember entityStatus: Done — Household and HouseholdMember live in CartWise.Domain.Entities, HouseholdRole (Owner/Member) in CartWise.Domain.Enums. Household is the aggregate root: its constructor auto-creates an Owner membership for the creator, AddMember rejects duplicate users, and HouseholdMember's constructor is internal so members can only be created through the aggregate — Domain stays free of any ASP.NET Core/Identity reference (CreatedByUserId/UserId are plain strings, not ApplicationUser navigations). HouseholdConfiguration/HouseholdMemberConfiguration in CartWise.Infrastructure/Data/Configurations establish the real FK to AspNetUsers (Restrict on Household.CreatedByUserId to protect household history from user-deletion cascades; Cascade on HouseholdMember rows), a composite PK on (HouseholdId, UserId), and an index on HouseholdMember.UserId for “which households does this user belong to” lookups. AddHousehold migration created and verified applying cleanly against a scratch SQLite database (and implicitly on every Web smoke test run, since those auto-migrate on boot). 11 domain tests added covering the invariants above.
CW-STORY-02.2 Implement household serviceRelease: MVP
Priority: P0
Effort: M
User story: As a developer, I want household operations centralized so controllers stay thin and rules stay testable.
Acceptance criteria
Tasks
IHouseholdServiceHouseholdServiceStatus: Done — IHouseholdService/HouseholdService live in CartWise.Application.{Interfaces,Services}, exposing CreateHouseholdAsync (returns Result<Household>), GetCurrentHouseholdAsync, and IsMemberAsync (the reusable membership check CW-STORY-02.4 will build authorization on top of). One household per user is enforced at this layer — see DEC-009. Data access goes through a new IApplicationDbContext abstraction (CartWise.Application.Interfaces, exposing just the DbSet<Household>/DbSet<HouseholdMember> needed) rather than a generic repository, since AGENTS.md explicitly rules out “generic repository abstractions added only for pattern compliance” — CartWiseDbContext implements it directly and is registered against the interface in AddInfrastructure. Time comes from the built-in TimeProvider (registered as TimeProvider.System) rather than a bespoke clock abstraction, keeping CreateHouseholdAsync deterministically testable without extra packages. AddApplication() (new CartWise.Application DI extension) is called from Program.cs alongside AddInfrastructure. 6 application tests added using EF Core's InMemory provider against the real CartWiseDbContext (via a new CartWise.Infrastructure reference in CartWise.Application.Tests, scoped to tests only — production CartWise.Application still has zero reference to CartWise.Infrastructure).
CW-STORY-02.3 Build household UIRelease: MVP
Priority: P0
Effort: S
User story: As a user, I want to create and view my household so I can begin using CartWise.
Acceptance criteria
Tasks
HouseholdControllerViews/Household/Create.cshtmlViews/Household/Index.cshtmlStatus: Done — HouseholdController ([Authorize], attribute-routed to /household and /household/create per AGENTS.md §11) with HouseholdIndexViewModel/HouseholdMemberViewModel/CreateHouseholdViewModel in CartWise.Web.ViewModels.Household. Index resolves member display names via UserManager<ApplicationUser> in the controller (not the Application layer, to keep CartWise.Application free of Identity references) and redirects to Create when the signed-in user has no household yet; Create redirects back to Index if a household already exists (enforcing DEC-009's one-per-user rule at the UI layer too, on top of the service-layer guard). Added a “Household” nav link, visible only when authenticated. Only Create + Index were built — member invite/remove (/household/members/*) are explicitly out of scope per AGENTS.md, which marks invite [future/simple v1 if desired], and HouseholdService has no add/remove-member use case yet to back them. Verified live end-to-end via dotnet run + curl: anonymous-to-registered-to-household-created-to-overview-shown flow, member display name and Owner role render correctly, revisiting /household/create after a household exists redirects to /household, and an empty-name submission redisplays the form with visible validation errors (200, not a redirect).
CW-STORY-02.4 Enforce household authorizationRelease: MVP
Priority: P0
Effort: S
User story: As a user, I want household data isolated so other users cannot access it.
Acceptance criteria
Tasks
Status: Done — added a reusable resource-based "HouseholdMember" authorization policy: HouseholdMemberRequirement + HouseholdMemberAuthorizationHandler (CartWise.Web.Authorization), backed by the IHouseholdService.IsMemberAsync check already built in CW-STORY-02.2, registered via AddAuthorizationBuilder() in Program.cs. Note on current scope: HouseholdController's existing actions (Index/Create) never accept a client-supplied household id — they always resolve “my household” from the authenticated user's own membership — so there's no IDOR surface to protect on them today; the new policy exists so the first household-scoped resource that does take an id (starting with CW-EPIC-03's shopping list) can apply [Authorize(Policy = "HouseholdMember")]/resource-based AuthorizeAsync immediately instead of hand-rolling the check. Verified with: a unit-style test exercising the policy through the real DI container (HouseholdAuthorizationTests — two real users/households, confirms the policy succeeds for the owning member and fails for a user from a different household), and Web integration tests (HouseholdAccessControlTests) proving an anonymous request to /household redirects to login, and that two independently-registered users each only ever see their own household's name on /household — never each other's.
CW-EPIC-03 Smart Shopping ListGoal: allow household members to maintain a shared grocery list, including unresolved free-text items.
CW-STORY-03.1 Model shopping list and grocery conceptsRelease: MVP
Priority: P0
Effort: M
User story: As a developer, I want shopping list entities defined so shared list workflows can be persisted.
Acceptance criteria
Tasks
GroceryConceptProductCategoryShoppingListShoppingListItemCW-STORY-03.2 Implement active shopping list serviceRelease: MVP
Priority: P0
Effort: S
User story: As a user, I want my household to have an active grocery list so shared planning is easy.
Acceptance criteria
Tasks
IShoppingListServiceCW-STORY-03.3 Add grocery items quicklyRelease: MVP
Priority: P0
Effort: S
User story: As a household member, I want to add free-text grocery items quickly so I do not lose shopping intent.
Acceptance criteria
Tasks
DisplayName, quantity, and unitCW-STORY-03.4 Update shopping list itemsRelease: MVP
Priority: P0
Effort: M
User story: As a household member, I want to toggle, skip, and delete items so the list stays accurate.
Acceptance criteria
Tasks
CW-STORY-03.5 Deliver a mobile-first list UIRelease: MVP
Priority: P0
Effort: S
User story: As a shopper, I want a touch-friendly grocery list so it works well on my phone.
Acceptance criteria
Tasks
Views/ShoppingList/Index.cshtmlViews/ShoppingList/_ListItem.cshtmlwwwroot/js/pages/shopping-list.jsCW-EPIC-04 Product Catalog and Barcode ResolutionGoal: support product identity, household product preferences, and local-first barcode resolution with provider fallback.
CW-STORY-04.1 Model product catalog entitiesRelease: Post-MVP
Priority: P1
Effort: M
Decision Gate: Confirm whether a product catalog is still needed before barcode work begins.
User story: As a developer, I want product and identifier entities modeled so products can be resolved independently of UPC.
Acceptance criteria
Tasks
BrandProductProductIdentifierHouseholdProductPreferenceCW-STORY-04.2 Search and view productsRelease: Post-MVP
Priority: P1
Effort: M
User story: As a user, I want to search and view products so I can understand and choose preferred items.
Acceptance criteria
Tasks
IProductServiceProductControllerViews/Product/Search.cshtmlViews/Product/Details.cshtmlCW-STORY-04.3 Implement barcode lookup flowRelease: Deferred
Priority: P2
Effort: M
Decision Gate: Barcode scanning is out of MVP scope.
User story: As a shopper, I want a barcode lookup flow so known products can be resolved quickly.
Acceptance criteria
Tasks
IProductDataProviderCW-STORY-04.4 Integrate Open Food FactsRelease: Deferred
Priority: P2
Effort: M
Decision Gate: External providers are out of MVP scope.
User story: As a developer, I want a provider fallback for unknown products so barcode resolution remains useful.
Acceptance criteria
Tasks
OpenFoodFactsProductDataProviderCW-STORY-04.5 Build scan pageRelease: Deferred
Priority: P2
Effort: M
Decision Gate: Camera scan flow is out of MVP scope.
User story: As a shopper, I want a camera-based scan page so I can identify products with my phone.
Acceptance criteria
Tasks
ScanControllerViews/Scan/Index.cshtmlCW-EPIC-05 Stores, Purchases, and Price IntelligenceGoal: record where purchases happened and build a trustworthy append-only price history.
CW-STORY-05.1 Model retailers and store locationsRelease: MVP
Priority: P1
Effort: S
Decision Gate: Decide whether store tracking in MVP is minimal or omitted from first purchase flow.
User story: As a developer, I want retailer and store entities so purchases can be tied to real locations.
Acceptance criteria
Tasks
RetailerStoreLocationCW-STORY-05.2 Model purchases and purchase itemsRelease: MVP
Priority: P0
Effort: M
User story: As a developer, I want purchase history modeled so shopping outcomes can be stored.
Acceptance criteria
Tasks
PurchasePurchaseItemCW-STORY-05.3 Model append-only price observationsRelease: MVP
Priority: P0
Effort: S
User story: As a developer, I want append-only price observations so historical price facts are preserved.
Acceptance criteria
Tasks
PriceObservationCW-STORY-05.4 Record household purchasesRelease: MVP
Priority: P0
Effort: M
Decision Gate: Decide whether MVP purchase entry must link directly to shopping list items or can begin as a manual flow.
User story: As a shopper, I want to record purchases so CartWise can remember what I bought and what I paid.
Acceptance criteria
Tasks
IPurchaseServiceCW-STORY-05.5 Derive price intelligenceRelease: MVP
Priority: P0
Effort: M
User story: As a user, I want price history and price insights so I can make better grocery decisions.
Acceptance criteria
Tasks
IPriceServiceCW-STORY-05.6 Build price viewsRelease: MVP
Priority: P0
Effort: S
User story: As a user, I want a price history screen so I can review what my household typically pays.
Acceptance criteria
Tasks
PriceControllerViews/Price/Index.cshtmlViews/Price/Product.cshtmlCW-EPIC-06 Shopping ModeGoal: provide a simple, touch-friendly in-store workflow for marking progress and recording purchases.
CW-STORY-06.1 Implement shopping mode serviceRelease: Post-MVP
Priority: P1
Effort: S
Decision Gate: Decide whether a dedicated shopping mode is needed beyond the list page.
User story: As a developer, I want shopping-mode orchestration so in-store behavior is consistent and testable.
Acceptance criteria
Tasks
IShoppingModeServiceCW-STORY-06.2 Build shopping mode UIRelease: Post-MVP
Priority: P1
Effort: M
User story: As a shopper, I want a low-noise mobile shopping view so I can use CartWise during a real trip.
Acceptance criteria
Tasks
ShoppingControllerViews/Shopping/Start.cshtmlViews/Shopping/_ShoppingItem.cshtmlwwwroot/js/pages/shopping-mode.jsCW-STORY-06.3 Support in-store item actionsRelease: Post-MVP
Priority: P1
Effort: M
User story: As a shopper, I want to mark items purchased, skipped, unavailable, or substituted so the trip stays accurate.
Acceptance criteria
Tasks
CW-STORY-06.4 Record prices while shoppingRelease: Post-MVP
Priority: P1
Effort: S
User story: As a shopper, I want to optionally enter prices in shopping mode so price history stays current.
Acceptance criteria
Tasks
CW-EPIC-07 Running-Low SuggestionsGoal: use deterministic purchase history to suggest what a household may need soon.
CW-STORY-07.1 Build replenishment engineRelease: MVP
Priority: P0
Effort: M
User story: As a developer, I want deterministic running-low logic so suggestions are explainable and testable.
Acceptance criteria
Tasks
IReplenishmentServiceCW-STORY-07.2 Show suggestions on home and list viewsRelease: MVP
Priority: P0
Effort: S
Decision Gate: Confirm the minimum launch dashboard: active list count, recent prices, and running-low suggestions.
User story: As a user, I want running-low suggestions surfaced in context so I can act on them quickly.
Acceptance criteria
Tasks
HomeViewModelCW-STORY-07.3 Add suggestion-to-list workflowRelease: MVP
Priority: P1
Effort: S
User story: As a user, I want to add a running-low suggestion to my list so I can convert insight into action quickly.
Acceptance criteria
Tasks
CW-EPIC-08 UX Polish, Security, and Release ReadinessGoal: harden the v1 experience for real-world use and release confidence.
CW-STORY-08.1 Improve mobile usability and accessibilityRelease: MVP
Priority: P1
Effort: S
User story: As a user, I want the app to be easy to use on my phone and accessible across core workflows.
Acceptance criteria
Tasks
CW-STORY-08.2 Harden security coverageRelease: MVP
Priority: P0
Effort: S
User story: As a team, we want security-sensitive paths reviewed so household data remains protected.
Acceptance criteria
Tasks
CW-STORY-08.3 Add diagnostics and loggingRelease: MVP
Priority: P1
Effort: S
User story: As a team, we want actionable diagnostics so failures can be understood and fixed quickly.
Acceptance criteria
Tasks
CW-STORY-08.4 Seed realistic development dataRelease: Post-MVP
Priority: P2
Effort: M
User story: As a developer, I want realistic sample data so workflows can be tested quickly during development.
Acceptance criteria
Tasks
CW-STORY-08.5 Harden HTML assertions in Web integration testsRelease: MVP
Priority: P2
Effort: XS
User story: As a developer, I want Web integration test assertions to be resilient to HTML encoding so tests don't produce false negatives on ordinary characters in test data.
Acceptance criteria
Tasks
CartWise.Web.TestsHouseholdAccessControlTests to use realistic household names (including an apostrophe) via the new helperStatus: Done — added HtmlAssert.Contains/DoesNotContain (tests/CartWise.Web.Tests/HtmlAssert.cs), which runs WebUtility.HtmlDecode before comparing. HouseholdAccessControlTests now uses "Alice's Household" / "Bob's Household" (real apostrophes) through the new helper and passes. Future Web integration tests asserting against rendered HTML should use HtmlAssert rather than raw Assert.Contains/DoesNotContain.
Origin: Discovered during CW-STORY-02.4 verification — EachUser_OnlySeesTheirOwnHousehold failed against "Alice's Household" because Razor correctly HTML-encodes ' as ' in @Model.Name, but the test asserted a literal apostrophe. The immediate fix (switching to apostrophe-free test data) papered over the real issue: any test data with &, ', ", <, or > would hit the same false negative. This story is the real fix.
CW-EPIC-01 Platform FoundationCW-EPIC-02 Household and Access ControlCW-EPIC-02 Household and Access ControlCW-EPIC-03 Smart Shopping ListCW-EPIC-05 MVP storiesCW-EPIC-07 MVP storiesCW-EPIC-08 hardening storiesCW-EPIC-04 Product Catalog and Barcode ResolutionCW-EPIC-06 Shopping ModeCW-EPIC-08Use this format for new backlog items:
### `CW-STORY-XX.X` Story Title
**User story:** As a `user type`, I want `capability` so that `benefit`.
**Acceptance criteria**
- Criterion 1
- Criterion 2
**Tasks**
- [ ] Task 1
- [ ] Task 2
Powered by TurnKey Linux.