diff --git a/docs/superpowers/specs/2026-07-29-orders-review-modal-design.md b/docs/superpowers/specs/2026-07-29-orders-review-modal-design.md
new file mode 100644
index 0000000..4da8006
--- /dev/null
+++ b/docs/superpowers/specs/2026-07-29-orders-review-modal-design.md
@@ -0,0 +1,222 @@
+# Orders Review Modal Design
+
+Date: 2026-07-29
+Area: `src/app/(app)/orders/page.tsx`, `src/components/orders-review-modal.tsx` (new), `src/lib/server/orders-service.ts`, `src/app/api/v1/orders/[id]/review-save/route.ts` (new)
+Status: Drafted for review
+
+## Goal
+
+Add a per-order "review against source" workflow so the operator can verify AI-populated order contents (line items, prices, customer) against the original email and attachments in one screen, and correct any AI mistakes inline — without leaving the orders list.
+
+## User Need
+
+Today the AI populates orders from inbound emails via the n8n pipeline. When the AI makes a mistake (wrong order number, wrong customer, mis-extracted qty/price, missed line item), the operator has no way to see the AI's output next to the source-of-truth RFQ email/attachment. They have to remember what the email said, open the order detail page, and fix things one field at a time.
+
+The operator needs:
+
+- A visible affordance on every order card that opens a review surface.
+- The order's line items side-by-side with the source email and attachment(s).
+- Inline editing of order_number, customer, and line-item prices/quantities.
+- A preview of the actual attachment (PDF/image) inline — not a separate download — so they only need to spot-check the PDF/picture against the system, not flip windows.
+
+## Approved Direction
+
+Add a new amber-tinted icon button on every order card that opens a review modal. The modal lays out the order's editable fields on the left and an inline preview of the source attachment (PDF/image) — or, if no attachment, the email body — on the right. Edits save atomically via a new `/api/v1/orders/[id]/review-save` endpoint; the card refreshes in place via the existing PB realtime subscription.
+
+## Scope
+
+In scope:
+
+- A new `OrdersReviewModal` component rendered from `OrdersPage` whenever a card's review button is clicked.
+- A new warn-amber icon button on each `OrderCard`, placed to the LEFT of the existing `ExternalLink` "open" button.
+- A new `updateOrderReviewFields` + `updateOrderItemsPrices` pair in `src/lib/server/orders-service.ts`.
+- A new `POST /api/v1/orders/[id]/review-save` route handler.
+- Inline preview of the first matching attachment (PDF via iframe, image via ``, both via the existing `/api/v1/files/{collectionId}/{recordId}/{token}` proxy); tab strip when there are multiple.
+- Fallback to the email body (subject + body) when the order has zero source attachments.
+- Free-text `order_number` override with uniqueness validation against the `orders` collection (excluding self) on save.
+- `CustomerSearch` combobox for the `customer` field.
+
+Out of scope:
+
+- Migrating the existing `getOrdersWithItems` / `searchOrdersWithItems` calls in `OrdersPage` from `pb-helpers.ts` to the service layer (one resource at a time, no big-bang rewrites per AGENTS.md).
+- Renaming the order-number generator or changing the `order_counters` collection. The override is a free-text PATCH against `orders.order_number`; the counter is untouched.
+- New PocketBase collections or schema changes.
+- A dedicated `/orders/[id]/review` route.
+- Anything beyond header fields + line-item prices/qty in the editable region (description edits, supplier changes, status transitions, etc. stay on the order detail page).
+
+## Data Source
+
+Reads from existing collections:
+
+- `orders` (id, order_number, company, customer, rep, quote, ai_insights)
+- `order_items` (order, description, po_description, quantity, selling_price, cost_price, markup)
+- `quotes` (via expand on `orders.quote`) — supplies the source email: `email_subject`, `email_body`, and `email_message_id` for fallback
+- `quote_attachments` (via `quote` relation) — supplies the source attachment(s)
+
+Writes back to existing collections:
+
+- `orders.order_number`, `orders.customer`
+- `order_items.quantity`, `order_items.selling_price`, `order_items.cost_price`, `order_items.markup`
+
+The modal reads via the existing `pb.collection("orders").getOne(orderId, { expand: "quote,customer,company" })` pattern (mirrors the `OrderDetailPage` fetch) plus a separate `quote_attachments` query keyed on `quote_attachments.quote = `. The proxy URL pattern `/api/v1/files/{collectionId}/{recordId}/{token}` is already used by `SprintItemPricingModal` for quote attachments.
+
+## UX
+
+### Card affordance
+
+```
+┌──────────────────────────────────────────────────────────────────────────┐
+│ ┊ ▸ 🛒 ORD-2026-07-014 [↗ Q-2026-07-022] ✓ Complete · Acme Ltd … │
+│ ┊ 12 items · 5 new · 3 processing · 2 delivered · 2 cancelled │
+│ ┊ ⚠ ◯ │
+└──────────────────────────────────────────────────────────────────────────┘
+ ▲ ▲
+ │ │
+ │ └─ ExternalLink (existing) "open"
+ └───── NEW warn-amber icon "review"
+```
+
+Same size + ghost-icon styling as `ExternalLink`. Tinted warn-amber. Tooltip: "Review order against email & attachments." Click opens the modal.
+
+### Modal layout
+
+```
+┌──────────────────────────────────────────────────────────────────────────────┐
+│ ⚠ Review order [Cancel] [ Save ] │
+│ Verify AI-populated fields against the source email & attachments │
+├──────────────────────────────────────────────────────────────────────────────┤
+│ │
+│ Order number [ ORD-2026-07-014 ] │
+│ Company Acme (Pty) Ltd (read-only) │
+│ Customer [ 🏢 J. Smith — Acme ▾ ] (CustomerSearch) │
+│ Rep Maria (read-only) │
+│ │
+├──────────────────────────────┬───────────────────────────────────────────────┤
+│ ORDER ITEMS (left) │ SOURCE OF TRUTH (right) │
+│ │ │
+│ Description Qty Price │ ── Attachment ────────────────────────────── │
+│ ──────────── ──── ──────── │ ┌─[Acme-RFQ.pdf] [site-photo.jpg]──────┐ │
+│ HSS drill 6mm [2 ] [42.50]│ │ │ │
+│ HSS drill 8mm [1 ] [38.00]│ │ [ PDF rendered via iframe / │ │
+│ HSS drill 10mm[3 ] [44.00]│ │ image rendered via ] │ │
+│ HSS drill 12mm[4 ] [50.00]│ │ │ │
+│ │ └───────────────────────────────────────┘ │
+│ + markup auto-recomputes │ │
+│ cost ↔ selling via lock │ ── (only if 0 attachments) ──────────────── │
+│ │ Subject: Re: RFQ — Drill bits │
+│ │ Hi team, please quote on the following: │
+│ │ • 2× 6mm HSS drills │
+│ │ • 1× 8mm HSS drills │
+│ │ … │
+└──────────────────────────────┴───────────────────────────────────────────────┘
+```
+
+### Behavior details
+
+- **Editability** — every numeric cell in the order items table is an `Input`. The `selling_price` ↔ `cost_price` pair uses the lock-pattern from `SprintItemPricingModal` (type into one, the other recomputes via `markup`). Quantity is a plain integer input. `description` and `po_description` are read-only here (edits live on the order detail page).
+- **Dirty state** — the modal tracks a `dirty: Set<"number" | "customer" | "items">` so Save is disabled until at least one field changes. Cancel and Esc discard changes without confirmation if nothing's dirty; if dirty, show a confirm-discard dialog.
+- **Save flow** — POST to `/api/v1/orders/[id]/review-save` with `{ order_number?, customer?, items?: [{ id, quantity?, selling_price?, cost_price? }] }`. The route returns 200 on success, 409 on number collision (with the colliding order id), or 4xx with a message on validation failure.
+- **Number collision error** — rendered inline beneath the order-number input: "ORD-2026-07-014 is already used by another order." Modal stays open, no fields lost.
+- **Realtime collision** — if PB fires an `orders` or `order_items` write while the modal is open, the modal keeps its own dirty state and refetches on close. The card behind the modal updates via the existing subscription; reopening the modal re-fetches fresh data.
+- **No source email** (order not from a quote) — right pane shows "No source email — this order wasn't created from a quote." Edits still work; nothing to preview.
+
+### Attachment preview rules
+
+The preview region resolves attachments in this priority:
+
+1. If `quote_attachments` has ≥1 entry with `kind = "pdf"`, prefer the first PDF. Render via `