# Solution Design: Products — bulk CSV import/export | | | | --- | --- | | **Anchor** | [wiggleverse/wiggleverse-ecomm#13](https://git.wiggleverse.org/wiggleverse/wiggleverse-ecomm/issues/13) (`type/feature`, `priority/P1`) — parent epic [#12](https://git.wiggleverse.org/wiggleverse/wiggleverse-ecomm/issues/12) | | **Author(s)** | Claude (session ecomm-0030), for Ben Stull | | **Reviewers / approvers** | Ben Stull | | **Status** | `draft` | | **Version** | v0.1.0 | | **Source artifacts** | BDD corpus: [prototype rfcs 11-05-\* (products import/export)](https://git.wiggleverse.org/wiggleverse/wiggleverse-ecomm-prototype-content/src/branch/main/rfcs) · [research/shopify/products](https://git.wiggleverse.org/wiggleverse/wiggleverse-ecomm-prototype-content/src/branch/main/research/shopify/products/products.md) · Prototype: [wiggleverse-ecomm-prototype](https://git.wiggleverse.org/wiggleverse/wiggleverse-ecomm-prototype) · Reference: Shopify product CSV · Builds on: [SD-0001](./SD-0001-mvp-sign-up-and-single-storefront.md) · Supersedes: — | **Change log** | Date | Version | Change | By | | --- | --- | --- | --- | | 2026-06-11 | v0.1.0 | Initial draft — §1 Business Context (session ecomm-0030) | Claude + Ben | --- ## 1. Business Context *The business lens — solution-agnostic throughout. No mechanism is proposed until §2.* ### 1.1 Executive Summary A merchant's catalog already exists before they ever meet this platform — in spreadsheets, in their current platform's export, in the working files they price and re-price every season. The value at stake is letting that catalog *become* their storefront without re-entering it, letting catalog-wide changes (a season's prices, a supplier's new costs) land as one gesture instead of hundreds, and guaranteeing the catalog can leave whole at any time — so adopting the platform never means retyping a business in, and never means locking a business up. The beneficiaries are merchants — most acutely those migrating from another platform — and the platform itself: every downstream capability of an ecomm (browsing, orders, checkout) presupposes a real catalog existing, and today none can. ### 1.2 Background ecomm is Wiggleverse's multi-tenant Shopify alternative. Its first Solution Design ([SD-0001](./SD-0001-mvp-sign-up-and-single-storefront.md)) shipped sign-up, identity, and single-storefront creation: a merchant can exist and their storefront can exist — and that storefront is, by design, an honestly empty shell ("There's nothing to manage yet"). The Products MVP epic ([#12](https://git.wiggleverse.org/wiggleverse/wiggleverse-ecomm/issues/12)) is the next move: give the storefront a real product catalog. The epic makes a deliberate boundary decision: the MVP ships **no per-item editing** — bulk ingestion and extraction is the *only* way product data moves. Its sibling Features display the catalog ([#14](https://git.wiggleverse.org/wiggleverse/wiggleverse-ecomm/issues/14), list + read-only detail) and extend it to bundled offerings ([#15](https://git.wiggleverse.org/wiggleverse/wiggleverse-ecomm/issues/15), kits); **this** Feature is the mechanism they all feed from. The backdrop that makes bulk-first the right boundary: real merchant catalogs are not built one item at a time inside an admin tool. Merchants switching platforms hold their entire catalog as an export of their current system — hundreds of products, each with variations, images, and prices. Merchants who stay maintain their catalogs batch-wise: a seasonal price list, a supplier's updated cost sheet, a spreadsheet kept as the working source of truth. The catalog's natural lifecycle is bulk in, bulk update, bulk out. ### 1.3 Business Actors / Roles Real-world roles — they exist whether or not this platform does. | Role | Responsible for (in the business) | | --- | --- | | **Merchant** | Owns a storefront's catalog: what is sold, at what price, shown how. Keeps it current as seasons, suppliers, and stock change. | | **Migrating merchant** | A merchant switching platforms. Their catalog already lives, complete, in their current system and its export files; they decide whether switching is worth the cost. | | **Platform builder** | Builds and validates the capabilities a catalog feeds (browsing, orders, demos). Needs realistic catalogs to exist before those capabilities can be exercised. | ### 1.4 Problem Statement A storefront can exist but cannot hold anything sellable: there is no way to get product data into the platform at all, and the MVP deliberately offers no per-item editing to fall back on. For the merchant whose catalog already exists elsewhere — the common case — hand re-entry of hundreds of products, each with variations, imagery, and prices, is a switching cost that simply ends the conversation. The gap recurs after adoption: catalog maintenance is batch-shaped (whole-catalog price changes, supplier updates), and one-at-a-time upkeep mismatches how merchants actually work their data. And data that goes in must come out: a catalog that cannot be extracted whole makes the platform a trap, which careful merchants correctly treat as a reason not to enter. ### 1.5 Pain Points | # | Pain | Who feels it | Cost / frequency today | | --- | --- | --- | --- | | PP-1 | A created storefront is a dead end — nothing to sell, nothing to manage, nothing to show | Merchant | Total: blocks any real use of the platform; felt by every merchant immediately after sign-up | | PP-2 | An existing catalog would have to be re-keyed item by item to move over | Migrating merchant | Hundreds of products × dozens of fields each — a prohibitive switching cost; prospective merchants don't switch | | PP-3 | Catalog-wide changes (season prices, supplier costs) have no whole-catalog gesture | Merchant | Recurring — every season, supplier change, or repricing event; effort scales with catalog size | | PP-4 | No realistic catalog can be stood up to build, demo, or validate against | Platform builder | Continuous — every product-dependent capability (#14, #15, future orders) is blocked or faked | | PP-5 | Whatever goes in cannot be gotten back out — the catalog would be trapped | Merchant | Felt at the adoption decision (a reason to say no) and at every backup/audit/leave moment after | ### 1.6 Targeted Business Outcomes | # | Outcome | Success metric | Baseline → Target | Guardrail (must not regress) | How / when measured | | --- | --- | --- | --- | --- | --- | | BO-1 | Merchants with existing catalogs adopt — populating a storefront is hours, not weeks | # storefronts holding a real catalog; effort to first full catalog | 0 storefronts can hold a catalog → a several-hundred-product catalog lands in under an hour of merchant effort | Sign-up → storefront flow (SD-0001) stays intact | Platform records (operationalized §8.1); reviewed monthly | | BO-2 | Catalog upkeep happens *in* the platform — merchants refresh in bulk rather than letting data go stale or leaving | # storefronts performing repeat bulk updates | No upkeep possible → catalog-holding storefronts update in bulk as their prices/data change | Existing catalog data never corrupted by an update | Platform records; reviewed monthly | | BO-3 | "My data would be trapped" is a non-objection — the catalog is extractable whole, always | Full-catalog extraction available and exercised | No extraction exists → every storefront's complete catalog can be taken out on demand, at any time | Extraction completeness: nothing the merchant supplied is unrecoverable | Platform records; reviewed quarterly | | BO-4 | Product-dependent work is unblocked — sibling and downstream capabilities build against real catalogs | #14 / #15 / future order work proceeding against realistic data | Nothing to build against → realistic catalogs standable in minutes | — | Delivery record of #14/#15; per Feature | ### 1.7 Scope (business) - **In scope:** populating a storefront's catalog in bulk from records the merchant already holds; updating the catalog in bulk; extracting the complete catalog at any time. "Catalog" here means products in full commercial reality: variations (size/color and the like), imagery, organization (vendor, type, tags, category), pricing/cost, stock levels, and publication state. - **Out of scope:** per-item create/edit (a later epic — the MVP's deliberate boundary); removing products in bulk (nothing is deleted by this mechanism — see §2); automated or scheduled synchronization with other platforms; movement of non-catalog data (customers, orders — other epics); the meaning and behavior of bundled offerings (kits — Feature #15, which rides this mechanism but defines its own semantics). - **Non-goals:** real-time integration with other commerce systems (the supplied file is the boundary, deliberately); making the MVP a catalog *editing* tool of any kind. ### 1.8 Assumptions · Constraints · Dependencies - **Assumptions:** merchants hold, or can readily produce, their catalog as a structured file (a spreadsheet, their current platform's export). *Risk if wrong:* a merchant with no digital catalog at all has no entry path in this MVP. Catalogs at MVP scale run to a few thousand items. *Risk if wrong:* larger catalogs need heavier machinery (a named seam, §2). - **Constraints:** bulk-only is Epic #12's deliberate MVP boundary — this design must not quietly grow per-item editing. One storefront per account (SD-0001 INV-4) — a merchant's catalog context is unambiguous. - **Dependencies:** SD-0001's shipped surfaces (identity, storefront, admin shell) — live as of v0.4.0. Sibling #14 displays what this ingests; sibling #15 extends what the records can express — both sequence after this mechanism exists. ### 1.9 Business Use Cases **BUC-1 — As a migrating merchant, I can bring my existing catalog into my storefront in one bulk gesture, so that switching platforms doesn't mean re-keying my business.** ```gherkin Scenario: BUC-1 — An existing catalog comes over whole Given a merchant whose catalog of several hundred products lives in an export from their current platform When they supply that catalog to their storefront in one gesture Then the storefront holds the full catalog — every product with its variations, imagery, organization, and prices And nothing had to be re-entered by hand ``` - **BUC-1 acceptance criteria:** the catalog the storefront holds matches the records the merchant supplied, completely; the merchant's effort is one supply-and-review gesture, not per-product work. **BUC-1a — unhappy path: unusable records are reported, not silently dropped.** ```gherkin Scenario: BUC-1a — Unusable records come back with actionable feedback Given a merchant supplies catalog records of which some are incomplete, contradictory, or unreadable When the storefront cannot accept some of the records Then the merchant is told exactly which records failed and why, in terms they can act on And they can correct and re-supply the problem records And nothing is silently dropped, guessed at, or invented ``` - **BUC-1a acceptance criteria:** every rejected record is identified with an actionable reason; accepted vs. rejected is fully accounted for — the merchant is never left wondering what made it in. **BUC-2 — As a merchant, I can see exactly what a bulk change will do to my catalog before it happens, so that I never damage my own data by accident.** ```gherkin Scenario: BUC-2 — A bulk change is reviewed before it takes effect Given a merchant about to apply a bulk change to a catalog their business depends on When they supply the new records Then they see what will be added, what will change (and to what), and what is in error — before anything takes effect And the change happens only when they say so ``` - **BUC-2 acceptance criteria:** the preview is complete and faithful (what it shows is exactly what applying does); declining costs nothing; nothing changes without the merchant's explicit go-ahead. *(OHM: consent & agency — the merchant authorizes the change knowing what it is.)* **BUC-3 — As a merchant, I can update my whole catalog at once, so that batch-shaped changes (seasonal prices, supplier costs) cost one gesture instead of hundreds.** ```gherkin Scenario: BUC-3 — A catalog-wide change lands as one gesture Given a storefront holding a catalog, and a season's repricing touching most of it When the merchant supplies the updated records Then the affected products are updated to the new values And the unaffected products are untouched And no duplicates are created ``` - **BUC-3 acceptance criteria:** updates land on the products they target; re-supplying unchanged records changes nothing (supplying the same catalog twice never doubles it); the merchant's effort does not scale with catalog size. **BUC-4 — As a merchant, I can take my complete catalog out at any time, so that my business data is never trapped in the platform.** ```gherkin Scenario: BUC-4 — The catalog leaves whole, on demand Given a storefront holding a catalog the merchant built up over time When the merchant asks for their catalog Then they receive the complete catalog — every product, variation, image, and price — in a form they can use elsewhere And what they receive could rebuild the same catalog faithfully ``` - **BUC-4 acceptance criteria:** extraction is always available, complete (nothing the merchant supplied is unrecoverable), and round-trips — what comes out can go back in and reproduce the same catalog. *(OHM: agency — leaving is always possible; the platform earns retention, never extorts it.)* --- ## 2. Solution Proposal **Build software:** CSV file import/export in the storefront admin — a new **Products** surface where a merchant uploads a CSV to add and update products in bulk, and downloads a CSV of the full catalog at any time. **The shape of it:** - **One canonical CSV format**, Shopify-flavored: one row per variant, consecutive rows sharing a `Handle` form one product (with its options, variants, and images). The column set is a deliberate superset of Shopify's product CSV — adding component columns (the kits seam, #15), volume, cost/tax fields — so a Shopify export is structurally familiar and our format never needs to churn when siblings arrive. The full column specification is §6. - **Two accepted dialects, auto-detected:** the canonical format, and Shopify's own product-CSV export (mapped onto canonical at the boundary). The header row identifies the dialect; the merchant never picks from a menu. This is the migrating merchant's path (BUC-1): their existing export imports directly. - **Import is upsert-only.** A row whose `Handle` is new creates a product; a known `Handle` updates it (variants matched by option-value combination). Nothing is ever deleted by an import — the mechanism is non-destructive by construction, so a partial file can never silently destroy catalog (BUC-2's safety, structurally). - **Validate-all → preview → confirm.** The whole file is validated and diffed against the catalog before anything is written. The merchant sees a summary — N products to add, M to update, K rows in error — and can drill into any record to see exactly which fields will change and to what. Nothing applies until they confirm; confirming applies the valid records and leaves the error rows listed, unapplied, and actionable (BUC-1a, BUC-2). Unrecognized columns are warned about in the preview and ignored, never silently dropped. - **Synchronous, with a stated capacity** of ~5,000 rows per file. No job queue in the MVP; the design names the async seam for when catalogs outgrow it (§6.9). One deliberate exception rides that seam's first rail: **images**. - **Images are fetched and hosted.** `Image Src` URLs are downloaded into platform object storage after the catalog rows commit, with renditions (thumbnail / card / detail) generated at fetch. Images that can't be fetched, aren't images, or fall below the resolution bar are **rejected** (the product shows a placeholder), with the outcome reported per image. The catalog is self-contained: exports reference our hosted copies, and the storefront never depends on a third-party host staying alive. - **Imports are first-class records.** Every import run is kept — file name, dialect detected, who ran it, add/update/error counts, image-fetch outcomes — with a detail view the merchant can leave and return to (the post-commit image phase reports there and on the Products page). - **Export is one gesture:** the complete catalog (optionally filtered by status) as a canonical CSV. Export → re-import is lossless and idempotent — the acceptance bar from #13 and BUC-4's round-trip guarantee. **Why this approach.** CSV is the medium merchant catalogs already live in: every platform exports it, every spreadsheet tool speaks it, and the migrating merchant (the sharpest pain, PP-2) holds one today. Accepting Shopify's dialect directly converts the largest migration population's existing artifact into a working catalog with zero transformation effort. Upsert-only plus preview-confirm makes a powerful bulk mechanism safe to hand to a non-technical merchant: the destructive operations simply don't exist, and every change is consented to with full knowledge (OHM: consent & agency). **Alternatives weighed** (detail in §6.7): a per-item editing UI first (rejected — Epic #12's explicit boundary, and it does nothing for migration); integration APIs / direct platform connectors (rejected — heavy, narrow, and premature before the catalog model exists); accepting spreadsheet formats (XLSX) (rejected for MVP — CSV covers the actual artifacts merchants hold; XLSX is an adapter behind the same boundary later); do-nothing / concierge data entry by the operator (rejected — doesn't scale past a demo and builds no product capability). **Solution-specific non-goals:** no deletion semantics of any kind in the CSV; no scheduled/automated re-import; no foreign dialects beyond Shopify's product CSV in the MVP; no kit *semantics* (the format carries the columns; #15 defines their meaning); no image editing/cropping — the platform hosts what the merchant's URLs provide, or rejects it. ## 3. Product Personas | Product persona | In ecomm | Maps to business role(s) | | --- | --- | --- | | **Merchant** | The authenticated storefront owner (SD-0001 §3), now extended: manages their catalog from the admin's new **Products** section — imports, reviews, confirms, exports. The MVP's only authenticated user type. | Merchant; Migrating merchant | The **Platform builder** (§1.3) gets no persona of their own: they exercise the same Merchant surface with demo data (stand up a storefront, import a realistic catalog). The **Operator** persona (SD-0001) is untouched by this design. ## 4. Product Use Cases Corpus grounding: scenario IDs cite the prototype corpus's products import/export scenarios ([rfcs 11-05-0032–0037](https://git.wiggleverse.org/wiggleverse/wiggleverse-ecomm-prototype-content/src/branch/main/rfcs)). Divergences are marked — the load-bearing one: the corpus matches rows to products **by SKU** (11-05-0035); this design matches products **by `Handle`** and variants **by option-value combination**, with SKU as correctable data (rationale in §6.7). **PUC-1 — The Products section exists, honestly** *(product-only; creates the surface #14 fills in)* ```gherkin Scenario: PUC-1 — Products page, before and after a catalog exists Given a Merchant in their storefront admin When they open "Products" in the admin navigation Then with an empty catalog they see an empty state: "No products yet", a primary "Import products" action, and links to the sample CSV and column reference And with a populated catalog they see the product count, "Import products" and "Export" actions, and the import history — with an honest note that the browsable product list arrives with a coming release (#14) ``` **PUC-2 — Upload a file to import** *(realizes BUC-1; corpus 11-05-0034)* ```gherkin Scenario: PUC-2 — A Merchant starts an import Given a Merchant on the Products page When they choose "Import products" and select a .csv file Then the file uploads and a "Validating…" state shows progress And they land on the import preview (PUC-3) when validation completes ``` **PUC-3 — Preview: see exactly what will happen** *(realizes BUC-2; the consent gate)* ```gherkin Scenario: PUC-3 — The preview summarizes and lets the Merchant drill in Given a Merchant whose file has been validated Then they see which dialect was recognized ("Canonical" or "Shopify product CSV — mapped") And a summary: N products to add, M to update, U unchanged, K rows in error And any columns that will not be imported, listed by name And selecting any product in the preview shows its record-level detail: fields to be set (for adds) or a field-by-field before → after diff (for updates) And nothing has been written to the catalog yet ``` ```gherkin Scenario: PUC-3a — Declining costs nothing Given a Merchant on the import preview When they choose "Cancel" Then they return to the Products page and the catalog is unchanged And no trace of the canceled import remains in the import history ``` **PUC-4 — Confirm: the import applies** *(realizes BUC-1, BUC-3)* ```gherkin Scenario: PUC-4 — Confirming applies the valid records Given a Merchant on a preview showing additions and updates When they choose "Import N products" Then an "Importing…" state shows progress And they land on the import run's detail page: products added, products updated, rows in error (each listed and unapplied), and the image-fetch phase begins (PUC-7) And the catalog now holds exactly what the preview showed ``` **PUC-5 — Row errors come back actionable** *(realizes BUC-1a; corpus 11-05-0036)* ```gherkin Scenario: PUC-5 — Bad rows are reported, good rows are not blocked Given a Merchant's file where some rows are invalid (e.g. a malformed price, a variant row whose options contradict its product) Then the preview's error table lists each failed row: line number, the column at fault, and what's wrong, in plain language And confirming imports the valid rows and leaves the error rows unapplied And the same error table remains on the import run's detail page ``` ```gherkin Scenario: PUC-5a — A file the importer cannot work with is rejected whole Given a Merchant uploads a file that is not parseable CSV, is missing a required column (e.g. "Title"), exceeds the row capacity (§2), or matches no known dialect Then the upload is rejected with one clear message naming the problem And nothing is imported and no import run is recorded ``` **PUC-6 — A Shopify export imports directly** *(realizes BUC-1; the migration path)* ```gherkin Scenario: PUC-6 — A migrating Merchant brings their Shopify export Given a Merchant with the product CSV their Shopify store exported When they upload it unmodified Then the preview shows "Shopify product CSV — mapped" and the standard summary And Shopify columns with no canonical equivalent are listed as not imported (warned, never silent) And confirming lands their catalog: products, variants, options, images, prices, tags, status ``` **PUC-7 — Image outcomes are reported, not assumed** *(realizes BUC-1a for imagery)* ```gherkin Scenario: PUC-7 — The image-fetch phase reports per image Given a confirmed import whose records reference image URLs Then the run detail shows live progress ("Fetching images: 312 of 4,950") And when complete, each image's outcome: fetched, rejected (below the resolution bar / not an image), or failed (unreachable) And products whose images were rejected or failed show a placeholder and are surfaced on the Products page — pre-#14 as an aggregate notice ("N products have image problems") linking to the run detail; per-product badges arrive with #14's list view And the Merchant can re-import corrected URLs to retry (upsert, BUC-3) ``` **PUC-8 — Import history** *(realizes BUC-2's accountability)* ```gherkin Scenario: PUC-8 — Past imports are reviewable Given a Merchant who has run imports before When they open the import history on the Products page Then they see each run: when, file name, dialect, who ran it, and its add / update / error / image-outcome counts And opening a run shows its full detail page (PUC-4, PUC-5, PUC-7), unchanged since the run finished ``` **PUC-9 — Export the catalog** *(realizes BUC-4; corpus 11-05-0032, 11-05-0033 — diverges: MVP filter is status only, vendor/collection filters deferred)* ```gherkin Scenario: PUC-9 — One gesture produces the whole catalog Given a Merchant on the Products page with a populated catalog When they choose "Export" (optionally filtering by status: all / active / draft / archived) Then a canonical-format CSV downloads containing every matching product — all variants, options, tags, prices, and image URLs (pointing at the platform's hosted copies) And with an empty catalog the "Export" action is disabled, with a note ``` **PUC-10 — The round-trip is visibly lossless** *(realizes BUC-3, BUC-4; the #13 acceptance bar)* ```gherkin Scenario: PUC-10 — Re-importing an export is a no-op, and says so Given a Merchant who exports their catalog and re-imports the file unmodified Then the preview shows every product as "unchanged" — nothing to add, nothing to update, no errors And the import action is unavailable, with the note "Nothing to change — your catalog already matches this file" And canceling leaves the catalog identical ``` **PUC-11 — Learning the format** *(realizes BUC-1 for the cold-start merchant)* ```gherkin Scenario: PUC-11 — The sample CSV and column reference teach the shape Given a Merchant who has never seen the canonical format When they download the sample CSV from the Products page or import screen Then it opens in their spreadsheet tool with a handful of example products — including one with variants and one with images And the linked column reference documents every column: name, meaning, required or optional, accepted values ``` ## 5. UX Layout Text is the source of truth; wireframes are generated from these descriptions on demand and never committed. All surfaces live inside SD-0001's admin shell (its header: storefront name left, signed-in email + sign out right). ### 5.1 Design tooling decision - **Decision:** Code directly. - **Rationale:** rubric score **−1** (claude-design-vs-code.md): Q1 **+1** (net-new Products section, no data-surface precedent in the codebase), Q2 **−1** (this section describes the UI to wireframe level — named components, enumerated states), Q3 **0** (one admin section, multi-step but utilitarian), Q4 **+1** (no design system in the ecomm app yet), Q5 **−1** (solo build, operator is the only reviewer), Q6 **−1** (work is dominantly logic: parsing, diffing, upsert, image pipeline). Default at −1 is Code; no override fires — the import preview is novel but structural, and prose specifies it adequately. ### 5.2 Screen: Products (serves PUC-1, PUC-7, PUC-8, PUC-9, PUC-11) - **Purpose:** the catalog's home in the admin; where imports start, exports download, and past imports are reviewed. Created by this design; #14 fills in the browsable list. - **Layout (top → bottom):** - **Header row:** title "Products" + product count (e.g. "Products · 412"); actions right-aligned: secondary **"Export"** (opens a small menu: status filter All / Active / Draft / Archived, then downloads — PUC-9; disabled with a note when the catalog is empty), primary **"Import products"** (→ §5.3). - **Notices band (conditional):** image-problems aggregate notice — "N products have image problems" linking to the most recent run's detail (PUC-7); shown while any product's image status is rejected/failed. - **Primary content, empty catalog:** empty state — "No products yet. Bulk import is how product data gets in." with the **"Import products"** action repeated, plus links **"Download sample CSV"** and **"Column reference"** (PUC-11). - **Primary content, populated catalog:** the honest pre-#14 state — the product count and one line: "Your catalog is loaded. The browsable product list arrives with an upcoming release." (replaced by #14's list). - **Import history (section):** table of runs, newest first — date · file name · dialect · added / updated / errors · image outcomes (e.g. "4,938 ✓ · 12 ✗") · status (Importing… / Fetching images… / Complete / Complete with problems). Row click → run detail (§5.5). Empty state: "No imports yet." - **States:** loading: skeleton for count + history · error: honest failure banner with retry (INV-9 spirit) · empty/populated per above. - **Notifications:** none beyond the notices band (no toasts here; outcomes live on durable surfaces). ### 5.3 Screen: Import — upload (serves PUC-2, PUC-5a, PUC-11) - **Purpose:** choose and validate a file. - **Layout:** - **Header:** "Import products", back to Products. - **Primary content:** drop zone / file picker ("CSV, up to 5,000 rows"); beneath it the format help: "Works with the canonical format or a Shopify product export." + links to sample CSV and column reference (PUC-11). - **Actions:** none beyond the picker — selecting a file starts upload + validation immediately (one fewer click; canceling is free, PUC-3a). - **States:** idle · uploading/validating: progress ("Validating… 2,140 of 4,950 rows") with cancel · file-level rejection (PUC-5a): error banner naming the one problem ("This file is missing the required column 'Title'"), picker remains for retry · success: → preview (§5.4). - **Notifications:** none (errors render in place). ### 5.4 Screen: Import — preview (serves PUC-3, PUC-3a, PUC-5, PUC-6, PUC-10) - **Purpose:** the consent gate — see exactly what will happen, then decide. - **Layout:** - **Header:** "Import preview — ⟨file name⟩"; dialect line: "Recognized: Shopify product CSV — mapped to canonical" or "Canonical format" (PUC-6). - **Warnings band (conditional):** columns not imported, listed by name (unknown-column warning); collapsible when long. - **Summary tiles:** **N to add · M to update · U unchanged · K errors** — each tile filters the detail list below. - **Detail list:** one row per product (grouped from its variant rows): handle, title, change kind (add / update / unchanged / error), variant count. Expanding a row drills in (PUC-3): adds show the fields to be set; updates show a field-by-field **before → after** diff (only changed fields, per variant where applicable); errors show the error detail. - **Error table (under the errors filter):** line number · column · plain- language problem ("row 214: 'Variant Price' — '12,50' is not a price"). - **Actions (sticky footer):** primary **"Import N products"** (disabled when nothing valid to apply — all-unchanged reads "Nothing to change — your catalog already matches this file", PUC-10); secondary **"Cancel"** (returns to Products, no trace, PUC-3a). - **States:** the preview *is* a state machine of its content (mixes of add/update/unchanged/error per above); plus stale-session expiry: a preview left open past its validity window (§6) prompts re-validation rather than applying stale diffs. - **Notifications:** none. ### 5.5 Screen: Import run detail (serves PUC-4, PUC-5, PUC-7, PUC-8) - **Purpose:** the durable record of one confirmed import — what applied, what errored, how the image fetch went. The post-confirm landing page and the import-history destination. - **Layout:** - **Header:** file name · imported ⟨relative time⟩ by ⟨email⟩ · dialect. - **Result summary:** added / updated / errors counts (mirrors the preview's final numbers). - **Images section:** while fetching — progress "Fetching images: 312 of 4,950" (updates live); when done — outcome summary ("4,938 fetched · 9 rejected · 3 failed") and an outcomes table listing only problems: product handle · variant · image URL · outcome (rejected: below the resolution bar / not an image · failed: unreachable) · what to do ("correct the URL and re-import"). All-clean collapses to one line. - **Error table:** the run's unapplied rows (line · column · problem), exactly as the preview showed them (PUC-5). - **States:** applying ("Importing…") · fetching images · complete · complete with problems (errors and/or image problems present). - **Notifications:** in-app only (decided: no email summaries in the MVP). ## 6. Technical Design *(not yet authored)* ## 7. Delivery Plan *(not yet authored)* ## 8. Success Metrics & Measurement *(not yet authored)* ## 9. Telemetry, Observability & Monitoring *(not yet authored)* ## 10. Operations *(not yet authored)* ## 11. Documentation plan *(not yet authored)* ## 12. Traceability matrix *(not yet authored)* ## 13. Open Questions & Decisions log *(not yet authored)* ## 14. Glossary & References *(not yet authored)*