Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

57KB

CartWise Scrum Backlog

Purpose

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.

Product Vision

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.

Scrum Workflow

Solo Cadence

  • Planning cadence: weekly
  • Delivery rhythm: one active story at a time
  • Daily check-in: review top 3 priorities and blockers
  • Backlog refinement: once per week
  • Review/demo: every Friday
  • Retrospective: every 2 weeks

Working Model

  • Founder / Product Owner: prioritizes scope, clarifies acceptance criteria, reviews AI output, and decides trade-offs
  • AI Development Partner: scaffolds, implements, documents, and proposes follow-up tasks within approved scope
  • Primary QA: founder validates behavior locally before marking stories done

Board Columns

  • Backlog
  • Ready
  • In Sprint
  • In Progress
  • Code Review
  • Verify
  • Done

Estimation

  • Use XS, S, M, and L sizing on stories
  • Split any story larger than L before implementation
  • Track tasks as checklist items and keep them aligned with the repository state

Definitions

Definition of Ready

A story is ready when:

  • Business value is clear
  • Acceptance criteria are written
  • Dependencies are identified
  • UI, domain, and data impact are understood
  • Test approach is known
  • The story is small enough to deliver in a sprint

Definition of Done

A story is done when:

  • Code is implemented
  • Tests are added or updated
  • Build passes
  • Authorization and validation are enforced where applicable
  • Logging is added where useful
  • User-facing behavior is verified
  • Work remains aligned with AGENTS.md architecture and scope

Tracking Conventions

  • Use - [ ] for not started tasks
  • Use - [x] for completed tasks
  • Update story acceptance criteria or add a short status note when a story is fully delivered
  • Keep task state aligned with the actual repository state

Release Strategy

  • Release: MVP means required for the local launch target by September 10, 2026
  • Release: Post-MVP means explicitly deferred until after MVP is stable
  • Release: Deferred means do not implement unless scope changes
  • Priority: P0 means must ship for MVP
  • Priority: P1 means valuable next but can slip
  • Priority: P2 means later or exploratory
  • Effort: XS under 2 hours, S about half a day, M about a day, L 2 or more days
  • Decision Gate calls out places where the founder must make a product or architecture decision before implementation continues

Epic Roadmap

MVP Release

  1. CW-EPIC-01 Platform Foundation
  2. CW-EPIC-02 Household and Access Control
  3. CW-EPIC-03 Smart Shopping List
  4. CW-EPIC-05 Stores, Purchases, and Price Intelligence
  5. CW-EPIC-07 Running-Low Suggestions
  6. Selected hardening work from CW-EPIC-08

Post-MVP Release

  1. CW-EPIC-04 Product Catalog and Barcode Resolution
  2. CW-EPIC-06 Shopping Mode
  3. Remaining polish work from CW-EPIC-08

CW-EPIC-01 Platform Foundation

Goal: establish the solution structure, authentication, SQLite wiring for MVP, and baseline engineering standards.

CW-STORY-01.1 Create solution skeleton

Release: 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 exists
  • src/ and tests/ project layout matches the spec
  • Project references follow approved dependency direction
  • Nullable reference types are enabled

Tasks

  • Create CartWise.sln
  • Create src/CartWise.Web
  • Create src/CartWise.Application
  • Create src/CartWise.Domain
  • Create src/CartWise.Infrastructure
  • Create tests/CartWise.Domain.Tests
  • Create tests/CartWise.Application.Tests
  • Create tests/CartWise.Web.Tests
  • Add project references
  • Enable nullable reference types

Status: 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 startup

Release: 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

  • ASP.NET Core MVC app starts locally
  • Shared layout and static assets load correctly
  • Environment-based configuration is wired
  • SQLite connection is configurable for MVP
  • PostgreSQL migration path remains possible later

Tasks

  • Configure MVC services and middleware
  • Add base layout and navigation shell
  • Organize wwwroot structure
  • Add configuration bindings
  • Wire SQLite connection string for MVP
  • Note PostgreSQL transition path in docs
  • Verify local app startup

Status: 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 authentication

Release: MVP
Priority: P0
Effort: M User story: As a user, I want to sign in so my household data is protected.

Acceptance criteria

  • Registration and sign-in are available
  • Authenticated routes can be protected
  • Identity persistence is configured

Tasks

  • Add ASP.NET Core Identity
  • Create ApplicationUser
  • Configure Identity persistence
  • Scaffold auth UI
  • Verify login and logout flow

Status: 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 baseline

Release: MVP
Priority: P1
Effort: S User story: As a team, we want build and test standards so changes remain stable over time.

Acceptance criteria

  • Build can run consistently
  • Basic test execution is available
  • Logging defaults are configured

Tasks

  • Add analyzer and formatting settings
  • Configure structured logging baseline
  • Add smoke test coverage
  • Add CI-oriented build and test commands to docs

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 Control

Goal: enable household creation, membership, and household-scoped authorization.

CW-STORY-02.1 Model households and membership

Release: MVP
Priority: P0
Effort: S User story: As a developer, I want household entities persisted so household features have a secure foundation.

Acceptance criteria

  • Household and membership entities exist
  • Roles are defined
  • EF configurations and migration are created

Tasks

  • Create Household entity
  • Create HouseholdMember entity
  • Add role enum
  • Add EF configurations
  • Create migration
  • Add domain tests

Status: 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 service

Release: 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

  • Household creation is supported
  • Current household can be resolved
  • Membership checks are reusable

Tasks

  • Create IHouseholdService
  • Implement HouseholdService
  • Add create household use case
  • Add get current household use case
  • Add membership verification logic
  • Add application tests

Status: 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 UI

Release: 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

  • Household create page is available
  • Household overview page is available
  • Validation errors are shown clearly

Tasks

  • Create HouseholdController
  • Create household view models
  • Build Views/Household/Create.cshtml
  • Build Views/Household/Index.cshtml
  • Add validation messaging
  • Add redirect flow after creation

Status: 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 authorization

Release: MVP
Priority: P0
Effort: S User story: As a user, I want household data isolated so other users cannot access it.

Acceptance criteria

  • Household membership is enforced on household-scoped resources
  • Unauthorized requests are blocked
  • Resource isolation is covered by tests

Tasks

  • Add household authorization policy or helper
  • Enforce membership checks in services
  • Add integration tests for access control
  • Add tests for unauthorized and cross-household access

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 List

Goal: allow household members to maintain a shared grocery list, including unresolved free-text items.

CW-STORY-03.1 Model shopping list and grocery concepts

Release: 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

  • Grocery concept, category, shopping list, and shopping list item entities exist
  • Item statuses are defined
  • Optimistic concurrency is supported for list items

Tasks

  • Create GroceryConcept
  • Create ProductCategory
  • Create ShoppingList
  • Create ShoppingListItem
  • Add list status enums
  • Add row version concurrency token
  • Add EF configurations
  • Create migration

Status: Done — ProductCategory, GroceryConcept, ShoppingList (aggregate root with an Items backing-field navigation, mirroring Household/HouseholdMember), and ShoppingListItem added in CartWise.Domain.Entities; ShoppingListStatus (Active/Completed/Archived) and ShoppingListItemStatus (Needed/Purchased/Skipped/Unavailable/Substituted) in CartWise.Domain.Enums. Concurrency: ShoppingListItem.RowVersion is a Guid configured with .IsConcurrencyToken() rather than .IsRowVersion() — SQLite has no native auto-generated rowversion column, and IsConcurrencyToken() works identically on SQLite and a future PostgreSQL move (EF includes the original value in the WHERE clause either way); regenerating it on mutation is deferred to CW-STORY-03.4, which is where mutation methods land. Scope note: unlike Household.AddMember, no ShoppingList.AddItem(...) aggregate method exists yet — ShoppingListItem's constructor is public for now since 03.1 is pure modeling and CW-STORY-03.3 owns the add-item use case; that story should consider tightening ShoppingListItem's constructor to internal behind an aggregate method, matching the Household pattern. ShoppingListItem.PreferredProductId from the AGENTS.md §5.4 spec was intentionally omitted — Product doesn't exist until the post-MVP CW-EPIC-04, and MVP explicitly doesn't require product resolution; it will be added via a future migration once Product exists. AddShoppingList migration created and verified applying cleanly against a scratch SQLite database.

CW-STORY-03.2 Implement active shopping list service

Release: 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

  • Active list can be retrieved
  • Default active list can be created
  • Household rule for default active list is enforced

Tasks

  • Create IShoppingListService
  • Implement get active list
  • Implement create list
  • Enforce one default active list rule
  • Add application tests

Status: Done — IShoppingListService/ShoppingListService in CartWise.Application.{Interfaces,Services}, following the exact pattern established by HouseholdService in CW-STORY-02.2: GetActiveListAsync (read, includes Items) and CreateListAsync (returns Result<ShoppingList>, rejects a second active list per the AGENTS.md §5.4 v1 rule with a clear error message). IApplicationDbContext extended with ShoppingLists/ShoppingListItems DbSets. Registered in AddApplication(). 6 application tests added (EF InMemory, same harness as HouseholdServiceTests), including a cross-household isolation check mirroring the pattern from CW-STORY-02.4. Also fixed a CA1861 warning surfaced by the AddShoppingList migration's composite-column indexes by scoping a suppression to Data/Migrations/** in .editorconfig — migrations are generated code and shouldn't be hand-edited to satisfy analyzers.

CW-STORY-03.3 Add grocery items quickly

Release: 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

  • Free-text item entry is supported
  • Quantity and unit are optional
  • Item appears on the active list
  • Product resolution is not required to add an item

Tasks

  • Implement add item use case
  • Support DisplayName, quantity, and unit
  • Add validation rules
  • Add item partial view
  • Add controller POST action
  • Add tests

Status: Done — ShoppingList.AddItem(...) aggregate method added (mirrors Household.AddMember; ShoppingListItem's constructor is now internal), computing SortOrder and rejecting adds to a non-active list. IShoppingListService.AddItemAsync added. ShoppingListController (GET /list, POST /list/items) auto-provisions a household's first list (named “Groceries”) on first visit — no separate “create list” UI step, matching AGENTS.md's “one active list is enough for MVP” and the First End-to-End Slice in CLAUDE.md. Views/ShoppingList/Index.cshtml + _ListItem.cshtml built (functional, Bootstrap-styled; the dedicated mobile-first/touch-friendly pass is CW-STORY-03.5). Scope note carried from CW-STORY-03.1's status resolved: the constructor tightening happened here as anticipated.

Two real bugs found and fixed via testing, not just the earlier apostrophe-encoding test bug:

  1. EF Core state-tracking bug: adding a new ShoppingListItem purely by mutating the already-tracked parent ShoppingList's backing-field collection (list.AddItem(...)) left the new item's state ambiguous to EF Core's change tracker — since ShoppingListItemId is a client-generated Guid (not store-generated), EF couldn't reliably infer Added vs Modified, and attempted an UPDATE on a row that didn't exist yet, throwing DbUpdateConcurrencyException. Fixed by explicitly calling _db.ShoppingListItems.Add(item) in ShoppingListService.AddItemAsync after list.AddItem(...).
  2. Model-binding prefix mismatch: Views/ShoppingList/Index.cshtml's tag helpers generate field names like NewItem.DisplayName (based on ShoppingListViewModel.NewItem's property path), but ShoppingListController.AddItem bound a bare, unprefixed AddShoppingListItemViewModel model parameter — so nothing bound, DisplayName silently came through empty, and (surprisingly) no validation error surfaced either. Fixed with [Bind(Prefix = "NewItem")] on the action parameter.

Both were caught by genuine integration tests (ShoppingListAccessControlTests, real HTTP + real SQLite) written for this story, not by the weaker EF-InMemory application tests alone — worth remembering for future controller/view-model pairs that use a nested form-section pattern like this one. Extracted WebTestHelpers (register/create-household/extract-antiforgery-token) out of HouseholdAccessControlTests into a shared file so ShoppingListAccessControlTests didn't duplicate it. Verified live end-to-end via dotnet run + curl, matching CLAUDE.md's First End-to-End Slice exactly: register → create household → /list auto-creates “Groceries” → add “Milk” (2 gal) → reload shows it with Needed status; empty-name submission correctly redisplays the form with a visible validation error (200, not a redirect).

CW-STORY-03.4 Update shopping list items

Release: 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

  • Items can be marked purchased
  • Items can be skipped and unskipped
  • Items can be deleted
  • Concurrency conflicts are handled safely

Tasks

  • Implement toggle purchased
  • Implement skip and unskip
  • Implement delete item
  • Handle concurrency exceptions
  • Add progressive enhancement with jQuery
  • Preserve server-post fallback
  • Add tests

Status: Done — ShoppingListItem.TogglePurchased/ToggleSkipped domain methods added (each regenerates RowVersion); POST /list/items/{id}/toggle and /skip are true toggles (one route each, per AGENTS.md §11 — no separate “unpurchase”/“unskip” routes), /delete hard-deletes (no “Deleted” status exists, and list items aren't history — unlike PriceObservation, nothing here needs to stay append-only). IShoppingListService.TogglePurchasedAsync/ToggleSkippedAsync/DeleteItemAsync all take householdId and resolve the item via a household-scoped join query (FindItemForHouseholdAsync) rather than trusting the route-supplied item id alone — an item belonging to a different household is indistinguishable from “not found,” so no existence is leaked. On reflection this made the "HouseholdMember" authorization policy from CW-STORY-02.4 unnecessary here: query-scoping is atomic (one query both finds the item and enforces ownership) and matches the pattern already used everywhere else in the codebase, so it wasn't invoked for this story — it remains available and tested for a future scenario that needs to authorize before it can even construct the right query.

Concurrency handling: the client-rendered rowVersion hidden field round-trips through the form; the service sets _db.Entry(item).Property(i => i.RowVersion).OriginalValue = expectedRowVersion before mutating, so EF Core's generated UPDATE/DELETE includes the client's last-seen value in its WHERE clause (the standard EF Core disconnected-entity concurrency pattern) — a stale submission throws DbUpdateConcurrencyException, caught and surfaced as Result.Failure(...), never a 500.

Progressive enhancement: wwwroot/js/pages/shopping-list.js intercepts the toggle/skip/delete forms’ submit via jQuery, POSTs via $.post (which sets X-Requested-With: XMLHttpRequest automatically), and the controller detects that header to return either the updated _ListItem partial (toggle/skip) or 204/409 (delete) instead of a redirect — Views/ShoppingList/_ListItem.cshtml's three per-item forms work unmodified via plain server POST if JS is unavailable, satisfying “standard form posts must work where practical.”

Two more real bugs found by tests (bringing the running total for CW-EPIC-03 to four — see CW-STORY-03.3's notes for the first two): both were test-assertion bugs on my part, not product bugs — Assert.DoesNotContain("Purchased", html) false-failed because the toggle button's label is literally “Purchased” on an unpurchased item (isPurchased ? "Undo" : "Purchased"), unrelated to the actual status badge. Fixed by asserting against the specific badge bg-secondary">{status}</span> markup instead of a bare substring — worth remembering for CW-STORY-03.5 and beyond: any status-driven UI where the action-button label overlaps with a possible status name needs a precise assertion, not Contains/DoesNotContain on the raw status word. Verified live end-to-end via dotnet run + curl: toggle Needed→Purchased shown in the status badge with the correct rowVersion, and an AJAX request (X-Requested-With: XMLHttpRequest) with a deliberately stale rowVersion correctly returns 409.

CW-STORY-03.5 Deliver a mobile-first list UI

Release: 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

  • List page is readable on mobile
  • Quick add is prominent
  • Tap targets are appropriate for shopping use

Tasks

  • Build Views/ShoppingList/Index.cshtml
  • Build Views/ShoppingList/_ListItem.cshtml
  • Add quick-add UI
  • Add touch-friendly styles
  • Add wwwroot/js/pages/shopping-list.js

Status: Done, with a verification caveat — the Index/_ListItem views, quick-add UI, and shopping-list.js already existed functionally from CW-STORY-03.3/03.4; this story was the dedicated mobile-first/touch-friendly polish pass. Changes: (1) fixed site.css's inverted base font size (was 14px on mobile, 16px on desktop — backwards, and inputs under 16px trigger iOS Safari's zoom-on-focus) to a flat 16px; (2) added .shopping-list-quick-add/.shopping-list-item/.shopping-list-item-actions CSS establishing ~44px-minimum touch targets on every button (removed btn-sm) and a 3rem-tall, 1.1rem-font quick-add input/button pair; (3) NewItem.DisplayName is now col-12 at every breakpoint (not just mobile) so it's unambiguously the prominent primary action, with Quantity/Unit/Add sharing a secondary row below; (4) added inputmode="decimal" to the Quantity field so mobile browsers show a numeric keypad instead of a full keyboard; (5) narrow viewports (max-width: 575.98px) stack each list item's name above its actions instead of cramming them into one row.

Verification caveat: no browser-automation tool (Playwright/chromium-cli) is available in this environment, so this could not be visually screenshotted at a mobile viewport as the production-readiness guidance calls for. What was verified: dotnet run + curl confirms the rendered HTML carries the new classes/attributes exactly as written, the CSS file is served and contains the expected rules, and the full test suite (67 tests, unaffected by these purely-visual changes) still passes. The founder should do a quick real-device or DevTools-mobile-emulation pass before treating this as fully done — the CSS follows standard, well-established sizing conventions (WCAG 2.5.5's ~44px target, 16px inputs) but hasn't been eyeballed.


CW-EPIC-04 Product Catalog and Barcode Resolution

Goal: support product identity, household product preferences, and local-first barcode resolution with provider fallback.

CW-STORY-04.1 Model product catalog entities

Release: 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

  • Brand, product, identifier, and household preference entities exist
  • Identifier uniqueness rules are defined
  • EF configurations and migration are created

Tasks

  • Create Brand
  • Create Product
  • Create ProductIdentifier
  • Create HouseholdProductPreference
  • Add source type enums
  • Add indexes and normalized fields
  • Add EF configurations
  • Create migration

Status: Done — per DEC-010, this epic is being built now ahead of MVP-tagged CW-EPIC-05/07 at explicit founder direction (strict numeric epic order), overriding this story's own “confirm whether a product catalog is still needed” decision gate implicitly via that same direction. Product is the aggregate root for ProductIdentifier (AddIdentifier, mirroring Household.AddMember/ShoppingList.AddItem), rejecting a duplicate (IdentifierType, NormalizedValue) pair before it would ever hit the DB's unique index — same defense-in-depth pattern as HouseholdService's one-active-list check. ProductIdentifier.Normalize is public static since CW-STORY-04.3's barcode lookup will need the identical normalization before it ever constructs an identifier. HouseholdProductPreference enforces the HouseholdId+GroceryConceptId uniqueness from AGENTS.md §5.3 as a real unique index. AddProductCatalog migration verified applying cleanly.

CW-STORY-04.2 Search and view products

Release: 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

  • Local product search works
  • Product details page displays key product information
  • Household price insights can be shown later on the details page

Tasks

  • Create IProductService
  • Implement local catalog search
  • Create ProductController
  • Build Views/Product/Search.cshtml
  • Build Views/Product/Details.cshtml
  • Add product view models
  • Add tests

Status: Done — IProductService/ProductService return ProductSummaryDto/ProductDetailsDto (new CartWise.Application.DTOs) rather than raw Product entities, since search/details need BrandName/ConceptName resolved via joins that don't belong on the Domain entity itself. Search is a simple NormalizedName.Contains(...) match, capped at 25 results. ProductController routes (/products/search, /products/{id:guid}) match AGENTS.md §11 exactly; Details 404s for an unknown id. Price/household-insight fields from AGENTS.md §12's ProductDetailsViewModel were intentionally omitted — the acceptance criteria itself says “can be shown later,” and CW-EPIC-05 (price intelligence) doesn't exist yet; the details page has a placeholder note instead of empty stub fields. 5 application tests + 3 Web tests added.

CW-STORY-04.3 Implement barcode lookup flow

Release: 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

  • Barcode input is normalized
  • Local identifier lookup runs first
  • Provider fallback runs when local lookup misses
  • Accepted results persist locally

Tasks

  • Define IProductDataProvider
  • Implement barcode normalization
  • Implement local-first lookup flow
  • Persist imported products and identifiers
  • Add unit and application tests

Status: Done — IProductDataProvider (CartWise.Application.Interfaces) matches AGENTS.md §9 exactly; ProductLookupResult/ProductSearchResult DTOs added. IProductService.ResolveBarcodeAsync implements the local-first flow: normalize (ProductIdentifier.Normalize, already built in CW-STORY-04.1) → local ProductIdentifiers lookup → iterate injected IEnumerable<IProductDataProvider> (empty until CW-STORY-04.4 registers a real one — the flow is fully testable now via a FakeProductDataProvider test double) → persist Product+ProductIdentifier (tagged Gtin since the scanned format isn't always known) and a Brand if the provider returned one not already in the catalog → return the same ProductDetailsDto shape CW-STORY-04.2 already established. 5 new application tests (local hit skips the provider entirely, provider fallback persists correctly with the right SourceType/SourceExternalId, not-found-anywhere fails cleanly, missing-barcode input fails cleanly).

CW-STORY-04.4 Integrate Open Food Facts

Release: 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

  • Open Food Facts provider can query by barcode
  • Provider models remain isolated to infrastructure
  • Provider errors are logged safely

Tasks

  • Implement OpenFoodFactsProductDataProvider
  • Map provider responses to internal DTOs
  • Add configuration and secrets support
  • Add error handling and logging
  • Add tests with mocked provider behavior

Status: Done — OpenFoodFactsProductDataProvider (CartWise.Infrastructure.Integrations.OpenFoodFacts) is registered as a typed HttpClient (AddHttpClient<IProductDataProvider, OpenFoodFactsProductDataProvider>), so CW-STORY-04.3's ProductService.ResolveBarcodeAsync now actually has a provider in its IEnumerable<IProductDataProvider> instead of an empty list. Response models (OpenFoodFactsResponse/OpenFoodFactsProduct) are internal to Infrastructure — never referenced outside it, satisfying “never bind Razor pages directly to provider-specific response models.” Base URL is configurable via OpenFoodFacts:BaseUrl in appsettings.json (no API key needed for OFF's public lookup endpoint, but the config-driven shape is ready for a future key-requiring provider like USDA). HTTP/JSON failures are caught narrowly (HttpRequestException, JsonException, TaskCanceledException) and logged via a source-generated [LoggerMessage] delegate rather than the ad-hoc ILogger.LogWarning(...) extension — a small perf-conscious pattern worth reusing in CW-STORY-08.3. A lightweight quantity parser extracts SizeValue/SizeUnit from OFF's free-text quantity field (e.g. "16 oz"), returning null/null rather than guessing when it doesn't parse. No dedicated Infrastructure test project exists (AGENTS.md's structure only lists Domain/Application/Web test projects), so — consistent with how HouseholdServiceTests/ShoppingListServiceTests already exercise real Infrastructure code — the 4 new tests live in CartWise.Application.Tests, using a hand-rolled fake HttpMessageHandler (found/not-found/HTTP-failure/malformed-JSON) rather than pulling in a mocking library.

CW-STORY-04.5 Build scan page

Release: 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

  • Scan page requests camera permission
  • Barcode result can resolve product details
  • User can add scanned product to the list or record a price

Tasks

  • Create ScanController
  • Build Views/Scan/Index.cshtml
  • Add camera permission flow
  • Add barcode JS integration
  • Add add-to-list and record-price actions

Status: Done, with one deliberate partial: “record-price” isn't wired up because CW-EPIC-05 (Purchases/Price Intelligence) hasn't been built yet — a direct consequence of DEC-010's strict-numeric-order choice reaching 04.5 before 05 exists. “Add to list” is fully wired instead, which satisfies the acceptance criterion's “add ... to the list or record a purchase/price.” Will revisit once CW-EPIC-05's PriceController exists.

Also added GET /products/barcode/{barcode} and GET /api/products/barcode/{barcode} to ProductController (per AGENTS.md §11 — these were specified but not yet built in CW-STORY-04.2/04.3, since neither of those stories’ task lists mentioned a controller route; the scan page is what actually needed them). The JSON route is the “actual client-side need” AGENTS.md §11 requires before introducing an /api route — the scan page's JS calls it via fetch/$.getJSON to resolve a detected barcode without a full page reload.

No barcode-scanning JS library is vendored — this project has no bundler/npm step (wwwroot is plain static files, matching the existing jQuery/Bootstrap vendoring pattern), and pulling in a third-party scanning library's browser build wasn't practical to do well without one. Instead, wwwroot/js/pages/scan.js uses the native browser BarcodeDetector API (Chrome/Edge/Android WebView) with graceful degradation: browsers lacking it get a clear “not supported” message and a manual barcode-entry text field instead, which also doubles as the non-JS fallback path per AGENTS.md §14.

Verified live end-to-end with a real barcode against the real Open Food Facts API (not mocked): GET /api/products/barcode/3017620422003 (Nutella) → provider fallback fires → product persists locally with the correct name/brand/identifier → /products/{id} renders it → “Add to my list” persists it onto the household's shopping list. Also confirmed a genuinely-unknown barcode correctly 404s (HTML route) / returns {"found":false} (JSON route) after the provider fallback misses too.


CW-EPIC-05 Stores, Purchases, and Price Intelligence

Goal: record where purchases happened and build a trustworthy append-only price history.

CW-STORY-05.1 Model retailers and store locations

Release: 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

  • Retailer and store entities exist
  • Location fields support practical v1 store data
  • EF configurations and migration are created

Tasks

  • Create Retailer
  • Create StoreLocation
  • Add EF configurations
  • Create migration
  • Add tests for basic persistence rules

Status: Done — per DEC-011, store tracking is optional everywhere it appears (Purchase.StoreLocationId/PriceObservation.StoreLocationId are nullable, as AGENTS.md §5.6 already specified for the latter). Retailer/StoreLocation are plain independent entities (no aggregate relationship needed — a store isn't composed/protected by its retailer the way ShoppingListItem is by ShoppingList). AddRetailersAndStores migration verified applying cleanly. 9 domain tests.

CW-STORY-05.2 Model purchases and purchase items

Release: MVP
Priority: P0
Effort: M User story: As a developer, I want purchase history modeled so shopping outcomes can be stored.

Acceptance criteria

  • Purchase and purchase item entities exist
  • Purchase source types are defined
  • EF configurations and migration are created

Tasks

  • Create Purchase
  • Create PurchaseItem
  • Add source type enum
  • Add EF configurations
  • Create migration
  • Add tests

Status: Done — Purchase is the aggregate root for PurchaseItem, mirroring ShoppingList/ShoppingListItem's split across stories: Items navigation exists now, but no AddItem composition method yet and PurchaseItem's constructor stays public — both arrive in CW-STORY-05.4 when the service layer actually needs them, exactly matching the CW-STORY-03.103.3 precedent. PurchaseSourceType (Manual/ShoppingMode/ReceiptImport/RetailerImport) added. PurchaseItem enforces Quantity > 0 and LinePrice >= 0 as constructor invariants. AddPurchases migration verified applying cleanly. 11 domain tests.

CW-STORY-05.3 Model append-only price observations

Release: MVP
Priority: P0
Effort: S User story: As a developer, I want append-only price observations so historical price facts are preserved.

Acceptance criteria

  • Price observation entity exists
  • Useful indexes are defined for history queries
  • Older observations are not overwritten by newer ones

Tasks

  • Create PriceObservation
  • Add indexes for product, household, and store history
  • Add EF configuration
  • Create migration
  • Add tests for append-only behavior

Status: Done — PriceObservation has zero mutation methods (only a constructor); a dedicated reflection-based test (Type_HasNoPublicMutationMethods) asserts no public instance methods exist beyond property getters, so the append-only invariant can't silently regress. Real bug caught and fixed while writing the EF configuration: Product → PriceObservation was initially wired DeleteBehavior.Cascade (copy-paste from other configs), which would have let deleting a Product silently wipe out its entire price history — directly contradicting CLAUDE.md's “protect historical data from accidental cascade deletion” rule. Changed to Restrict. All three indexes from AGENTS.md §5.6 added (ProductId+ObservedUtc, +StoreLocationId, +HouseholdId). ConfidenceScore is constructor-validated to [0, 1]; CurrencyCode must be exactly 3 letters (defaults "USD"). AddPriceObservations migration verified applying cleanly. 7 domain tests.

CW-STORY-05.4 Record household purchases

Release: 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

  • Purchase flow can start and complete
  • Purchase items can be added
  • Shopping list items can be linked when relevant

Tasks

  • Create IPurchaseService
  • Implement start purchase flow
  • Implement add purchase item flow
  • Implement complete purchase flow
  • Link purchase items to shopping list items where applicable
  • Add tests

Status: Done, resolved per DEC-008 (manual purchase entry acceptable, list linkage can be simplified). Purchase.AddItem/Complete landed here as planned (see CW-STORY-05.2's note); Purchase.IsCompleted (Subtotal.HasValue) is the completion signal — there's no separate Status field since AGENTS.md's Purchase spec doesn't define one. AddPurchaseItemAsync accepts an optional shoppingListItemId to set PurchaseItem.ShoppingListItemId (the FK link this task asks for); it does not also auto-mark the source ShoppingListItem as purchased — that's a UX nicety beyond what “link purchase items to shopping list items” literally asks for, and the current UI doesn't yet offer a way to pick a list item when adding a purchase item anyway (no product/list-item picker built — out of scope for this story). PurchaseController/Views/Purchase/* implement a simple single-page recording flow (start → add items one at a time, mirroring the proven ShoppingList UX pattern → complete, after which the page becomes read-only) rather than a multi-step wizard, keeping state entirely server-side with no session/multi-request complexity. Verified live end-to-end via dotnet run + curl: start → add “Milk” ($7.98) → complete → subtotal/total correct → add-item form disappears once completed → index list reflects the total. 7 application tests + 3 Web tests (including cross-household isolation — a different household's purchase 404s, not 200-with-empty-data).

CW-STORY-05.5 Derive price intelligence

Release: 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

  • Purchase items can create price observations
  • Latest, average, median, lowest, and unit price calculations are supported when data allows
  • Freshness and source can be displayed

Tasks

  • Create IPriceService
  • Derive observations from purchase items
  • Implement latest price calculation
  • Implement average and median calculations
  • Implement lowest recent price calculation
  • Implement unit price calculation
  • Add tests for price statistics

CW-STORY-05.6 Build price views

Release: 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

  • Prices index page exists
  • Product-specific price history page exists
  • Product details can show historical price insight

Tasks

  • Create PriceController
  • Build Views/Price/Index.cshtml
  • Build Views/Price/Product.cshtml
  • Add price history to product details view
  • Add filtering and sorting UI
  • Add freshness and source display

CW-EPIC-06 Shopping Mode

Goal: provide a simple, touch-friendly in-store workflow for marking progress and recording purchases.

CW-STORY-06.1 Implement shopping mode service

Release: 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

  • Shopping mode view can be prepared from list data
  • Items can be grouped for easier shopping
  • Remaining and purchased counts are available

Tasks

  • Create IShoppingModeService
  • Implement shopping mode preparation
  • Group by category-based sections
  • Calculate remaining and purchased counts
  • Add tests

CW-STORY-06.2 Build shopping mode UI

Release: 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

  • Shopping mode works well on a phone
  • Groups and counts are clear
  • Key actions are easy to reach

Tasks

  • Create ShoppingController
  • Build Views/Shopping/Start.cshtml
  • Build Views/Shopping/_ShoppingItem.cshtml
  • Add touch-friendly controls
  • Add wwwroot/js/pages/shopping-mode.js

CW-STORY-06.3 Support in-store item actions

Release: 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

  • Item transitions are supported
  • Anti-forgery is enforced on state changes
  • Actions update the shopping workflow correctly

Tasks

  • Add purchase action
  • Add skip action
  • Add unavailable action
  • Add substitute action
  • Add anti-forgery for AJAX and form posts
  • Add tests

CW-STORY-06.4 Record prices while shopping

Release: 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

  • Optional price entry is available during purchase actions
  • Money input is validated
  • Captured values feed purchase and price history logic

Tasks

  • Add optional per-item price input
  • Validate price and quantity inputs
  • Persist captured values through purchase service
  • Add tests

CW-EPIC-07 Running-Low Suggestions

Goal: use deterministic purchase history to suggest what a household may need soon.

CW-STORY-07.1 Build replenishment engine

Release: MVP
Priority: P0
Effort: M User story: As a developer, I want deterministic running-low logic so suggestions are explainable and testable.

Acceptance criteria

  • Last N purchase dates can be evaluated
  • Median interval is used when sufficient history exists
  • Minimum-history and tolerance rules are enforced

Tasks

  • Create IReplenishmentService
  • Gather recent purchase dates
  • Calculate purchase intervals
  • Implement median interval logic
  • Add minimum-history thresholds
  • Add tolerance window logic
  • Return score and reason data
  • Add tests

CW-STORY-07.2 Show suggestions on home and list views

Release: 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

  • Home page can display suggestions
  • Shopping list page can display suggestions
  • Suggestions include reason or confidence context

Tasks

  • Extend HomeViewModel
  • Add suggestions to home controller flow
  • Add running-low partial views
  • Render suggestions on list page
  • Add tests

CW-STORY-07.3 Add suggestion-to-list workflow

Release: 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

  • Suggestion can be added to the active list
  • Existing list workflows are reused
  • Duplicate accidental adds are reasonably controlled

Tasks

  • Add controller action to add suggestion
  • Reuse shopping list add-item workflow
  • Add duplicate-prevention rules if needed
  • Add tests

CW-EPIC-08 UX Polish, Security, and Release Readiness

Goal: harden the v1 experience for real-world use and release confidence.

CW-STORY-08.1 Improve mobile usability and accessibility

Release: 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

  • Key pages are responsive
  • Labels, focus states, and contrast meet baseline usability needs
  • Error handling is understandable

Tasks

  • Review responsive layouts
  • Improve form labels and focus states
  • Improve contrast and tap sizes
  • Validate keyboard navigation
  • Validate error summaries

CW-STORY-08.2 Harden security coverage

Release: MVP
Priority: P0
Effort: S User story: As a team, we want security-sensitive paths reviewed so household data remains protected.

Acceptance criteria

  • Anti-forgery coverage is verified
  • Authorization coverage is verified
  • Secrets handling is consistent with app rules

Tasks

  • Review anti-forgery coverage
  • Review authorization enforcement
  • Review validation across state-changing flows
  • Review secrets and configuration handling
  • Add integration tests for protected routes

CW-STORY-08.3 Add diagnostics and logging

Release: MVP
Priority: P1
Effort: S User story: As a team, we want actionable diagnostics so failures can be understood and fixed quickly.

Acceptance criteria

  • External provider failures are logged
  • Barcode misses can be diagnosed
  • Authorization failures are logged safely

Tasks

  • Add structured logging for provider failures
  • Log unresolved barcode outcomes
  • Log authorization failures safely
  • Add basic health-check strategy if included

CW-STORY-08.4 Seed realistic development data

Release: 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

  • Development-only seed data exists
  • Seed data covers household, products, stores, and price history
  • Production is not polluted with fake data

Tasks

  • Seed one household
  • Seed grocery concepts and categories
  • Seed brands and products
  • Seed product identifiers
  • Seed store locations
  • Seed purchase and price history
  • Restrict seeding to development only

CW-STORY-08.5 Harden HTML assertions in Web integration tests

Release: 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

  • Web integration test assertions against rendered HTML compare against HTML-decoded content, not raw encoded output
  • Test data is not artificially restricted to avoid HTML-special characters (apostrophes, ampersands, quotes)

Tasks

  • Add an HTML-decoding assertion helper to CartWise.Web.Tests
  • Update HouseholdAccessControlTests to use realistic household names (including an apostrophe) via the new helper
  • Note the pattern so future integration tests use it by default

Status: 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 &#x27; 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.


Suggested 30-Day Delivery Sequence

Week 1 - Foundation

  • CW-EPIC-01 Platform Foundation
  • Start CW-EPIC-02 Household and Access Control

Week 2 - Shared List

  • Finish CW-EPIC-02 Household and Access Control
  • Start and finish core stories in CW-EPIC-03 Smart Shopping List

Week 3 - Purchases and Price History

  • Deliver CW-EPIC-05 MVP stories

Week 4 - Running Low and Hardening

  • Deliver CW-EPIC-07 MVP stories
  • Deliver selected CW-EPIC-08 hardening stories
  • Run local MVP release checklist

After MVP

  • Resume CW-EPIC-04 Product Catalog and Barcode Resolution
  • Resume CW-EPIC-06 Shopping Mode
  • Complete remaining polish work in CW-EPIC-08

Story Template

Use 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

Notes

  • Keep v1 scope tightly aligned to the grocery-memory goals
  • Avoid introducing speculative architecture or out-of-scope features
  • Reprioritize stories only when dependencies or product value clearly require it
  • Preserve the implementation order unless a small change is needed to unblock progress

Powered by TurnKey Linux.