Chapter 3 — Apply on Your Own: Product Wishlist
You have seen the full OpenSpec loop in Chapter 2. Now drive it yourself. The prompts are gone; the thinking is yours.
- Reading time: 10 minutes
- Practical time: 90–120 minutes
Feature brief
What we're building: A wishlist feature where authenticated users can save products they are interested in. Users must be logged in to add items to their wishlist. They can add products from the product detail page, view all saved items on a dedicated wishlist page, and remove items they no longer want.
Why this feature: It touches all three layers — a new database table, new Spring REST endpoints, and new Angular components — and introduces a cross-cutting concern: authentication. The wishlist endpoints require a logged-in user, adding Spring Security configuration to the spec. The CRUD surface (create, read, delete) combined with auth makes it an ideal first full-stack spec to author independently.
Scope at a glance:
- A new
wishlist_itemstable with (at minimum)id,product_id,user_id,created_at. - A
POST /api/wishlistendpoint to add a product to the authenticated user's wishlist (body:{ productId }). - A
GET /api/wishlistendpoint to list all wishlist items for the authenticated user, including product details (name, price, image). - A
DELETE /api/wishlist/{id}endpoint to remove an item from the wishlist (only the owning user can delete). - A
GET /api/wishlist/check?productId={id}endpoint returning{ exists: boolean }so the UI can show whether a product is already wishlisted. - An
AddToWishlistComponentrendered on theProductDetailPageComponent(a button/toggle, visible only to logged-in users). - A
WishlistPageComponentaccessible via a new route (/wishlist) showing all saved products (requires authentication). - Authentication via the existing user system — the user identity is derived from the session/token, not from request parameters.
Functional requirements
Your spec must capture these behaviours:
- Only authenticated users can add products to their wishlist. Unauthenticated users see a "Log in to save" prompt instead of the wishlist button.
- On the product detail page, a "Save to Wishlist" button adds the current product to the user's wishlist.
- If the product is already in the wishlist, the button displays "Saved" (or similar) and clicking it removes the item.
- The wishlist page (
/wishlist) shows all saved products as cards displaying: product name, price, image thumbnail, and a "Remove" action. - Removing an item from the wishlist page removes it immediately from the displayed list (optimistic UI or after response).
- An empty state ("Your wishlist is empty. Browse products to add some!") is shown when no items exist.
- The API rejects duplicate entries — adding the same product for the same user a second time returns an appropriate error.
- All wishlist endpoints return
401 Unauthorizedif the request is not authenticated.
Technical scope
Key architectural constraints to encode in your spec so the agent does not need to invent them:
- New table only. Do not modify the
productstable or any existing JPA entity. TheWishlistItementity referencesProductbyproduct_idandUserbyuser_id(foreign keys), not bidirectional JPA relationships. - Authentication required. All wishlist endpoints must be protected. Use Spring Security to secure
/api/wishlist/**. The authenticated user's identity is extracted from the security context (e.g.,SecurityContextHolderor@AuthenticationPrincipal) — never passed as a request parameter. - Duplicate prevention at the DB level. Add a unique constraint on
(product_id, user_id)so the database enforces one entry per product per user. - Frontend: Angular standalone components in an NgModule app. The demo shop uses NgModule-based architecture. Both
AddToWishlistComponentandWishlistPageComponentmust be declared withstandalone: truein their decorators and added to theimportsarray of the host module (notdeclarations). Note: Chapter 2'sRecentlyViewedComponentused the traditionaldeclarationspattern — this chapter uses standalone components, the newer Angular approach. They handle their own HTTP calls viaHttpClient; injectHttpClientdirectly — the app-levelHttpClientModulealready provides it. Do not importHttpClientModuleinside the component's ownimportsarray. - Auth-aware UI. The wishlist button and page must check authentication state. If the user is not logged in, show a prompt to log in rather than a broken button. Use an Angular route guard for the
/wishlistpage. - New route required. Register
/wishlistas a new route pointing toWishlistPageComponent. Add a navigation link in the app header/nav (visible only when logged in). - No changes to existing tests. Your implementation may add new tests; it must not break existing ones.
Your task: the OpenSpec loop
Work through each step below. Check off each item as you complete it.
- [ ] Run
/opsx:proposewith a concise feature description to generate your initial proposal folder. - [ ] Read
proposal.md,specs/,design.md, andtasks.mdin full. Identify at least two decisions the agent made that you need to verify or override. - [ ] Refine the spec via agent dialogue until the spec reflects every functional requirement and technical constraint listed above.
- [ ] Run
/opsx:applyand monitor progress againsttasks.md. Pause if the agent deviates from the spec. - [ ] Manually test: log in, add a product to the wishlist, verify the button toggles to "Saved", navigate to
/wishlistand confirm the item appears, remove it, reload and confirm persistence, test duplicate prevention, verify unauthenticated access returns 401. - [ ] Run
/opsx:archiveonce all tasks are checked off intasks.md.
Decisions your proposal must address
Your spec is incomplete if it does not make a call on each of these. There are no right answers — but the agent needs explicit direction on all of them before /opsx:apply.
- Authentication mechanism — does the app already have a user/session system to hook into, or does one need to be created? If creating, what type: session-based, JWT, or basic auth?
- Wishlist capacity — is there a maximum number of items per user? If so, what error does the API return when exceeded?
- Product deletion cascading — if a product is removed from the catalogue, should its wishlist entries be cascade-deleted, or should the wishlist show a "Product no longer available" state?
- Unauthenticated experience — should the wishlist button be hidden entirely for logged-out users, or visible but disabled with a tooltip/link to login?
- Button placement — where exactly on the product detail page does the wishlist button appear? Near the price? In the image area? Next to an "Add to Cart" button?
- Response format for list endpoint — does
GET /api/wishlistreturn full product objects nested inside each wishlist item, or justproductIdrequiring a separate lookup? - Ordering — are wishlist items displayed newest-first, or in some other order?
Acceptance criteria
Your implementation is complete when all of the following are true:
POST /api/wishlistwith valid{ productId }and an authenticated session returns201 Createdwith the saved wishlist item.POST /api/wishlistwith a duplicate(productId, userId)returns409 Conflict.GET /api/wishlistreturns a JSON array of the authenticated user's wishlist items with product details.DELETE /api/wishlist/{id}returns204 No Contentand removes the item (only if owned by the authenticated user).GET /api/wishlist/check?productId={id}returns{ "exists": true }or{ "exists": false }for the authenticated user.- All wishlist endpoints return
401 Unauthorizedwhen called without authentication. - The product detail page shows a wishlist toggle button for logged-in users; logged-out users see a login prompt.
- The
/wishlistroute renders all saved products with a remove action (guarded — redirects to login if unauthenticated). - An empty wishlist displays the empty-state message.
- All new Spring controller/service tests pass; all pre-existing tests continue to pass.
openspec/changes/contains an archived entry for this feature.
A downloadable version of this checklist is available at acceptance-checklist.md.
Escape hatches
Agent generates non-standalone components — if the agent creates components without standalone: true in their @Component decorators, they will use the declarations pattern from Chapter 2 instead of the standalone pattern required here. Check the generated component files: standalone: true must appear in the decorator, and the host module's imports array (not declarations) must include the components. If wrong, prompt: "Both wishlist components must be standalone with standalone: true in their @Component decorators. Please fix and update the host module to add them to imports, not declarations."
Agent invents fields not in the spec — for example, it adds a priority column or a notes field. Ask it to re-read proposal.md and specs/ and remove anything not explicitly requested. If you actually want the field, amend the spec first, then re-run the relevant task.
Agent skips the unique constraint — if you see duplicate wishlist entries in testing, check whether the migration includes UNIQUE(product_id, user_id). If missing, prompt: "The spec requires a unique constraint on (product_id, user_id) to prevent duplicates at the database level. Please add it."
Spec is missing a decision — if the agent stops mid-task to ask for clarification, that is a signal the spec has a gap. Do not answer the agent directly in chat. Instead, update the relevant spec file, commit it, and then tell the agent "I have updated the spec — please re-read it before continuing."
Test failures after /opsx:apply — resist the urge to patch the code to make tests pass. First check whether the test is catching a genuine spec violation. If it is, fix the code to match the spec. If the test itself is wrong, update the spec to clarify intent and then update the test to match.
Agent skips a task — tasks.md items should all be checked off. If the agent marks a task done without producing output, prompt: "Please show me the code change that corresponds to task N before marking it complete."
Reflection
Before archiving, take five minutes to review your spec artefacts (proposal.md, specs/, design.md).
Consider:
- Which decision in "Decisions your proposal must address" was hardest to spec clearly? How would you phrase it differently next time?
- Where did the agent deviate most from your spec? Was the spec ambiguous, or was the agent hallucinating?
- If you were handing this spec to a colleague instead of an AI, would they have enough information to implement the feature without asking you a single question?
There are no answers to submit — this is for your own learning. The OpenSpec loop produces artefacts (the openspec/changes/ folder) that ARE the documentation of your reasoning; review them.
Done
You have completed all three chapters of the Spec-Driven Agentic Coding lab. Return to the lab overview for a summary of what you covered.