# UAT Practice Walk: one `SLAB-UAT-*` listing Status: WALK · for Ty to run · SlabUploader This is the walkable practice plan. It proves one listing end to end without touching a live product. Follow the sections in order. Each one names what you do, what you should see, and where the rule lives. **Inference is OFF for this walk.** The optional ON path is only valid after Gate C is green and the inference endpoint is already configured. Inference stays off here. --- > On branch `m3-ds`, Milestone 3 exit criteria and field rules live in `docs/CREATE.md`. This walk is the human practice path for a `SLAB-UAT-*` listing. Prefer `CREATE.md` if anything conflicts. ## 1. Host and deploy preconditions Before this walk, the app must already be on the inventoried host. This section only checks that. Host IPs and hostnames come from `deploy/INVENTORY.md`. 1. **Inventory is filled.** `deploy/INVENTORY.md` has `APP_HOST`, `APP_HOSTNAME`, ports, Caddy location, and the smoke results. It is the only source for what runs where on this host. See `docs/DEPLOYMENT.md` §1 for the worksheet rules. 2. **Reachable over LAN/Tailscale.** Your phone reaches `https://${APP_HOSTNAME}` on the same network (LAN or Tailscale). No public exposure. Verify with a browser, not `curl`. 3. **HTTPS only.** The URL is `https://`. The browser shows a valid certificate. If the phone warns, the Caddy TLS trust is not set up yet. That is a deploy problem, not a listing problem. 4. **Health passes.** In a browser on the phone, open `https://${APP_HOSTNAME}/api/health`. You should see an ok response. This is the same check in `docs/DEPLOYMENT.md` §8 step 1. 5. **Store URL is set.** `WOO_BASE_URL` is in the host `.env` and points at the store over `https://`. It is not something you type in the app. See `docs/DEPLOYMENT.md` §3 and §5. If any of these fail, fix the deploy before continuing. This walk does not cover Gate B host work. It assumes the app is up and reachable. --- ## 2. WordPress Application Password setup You do this once per store, in the store's WordPress admin. Do it before you open the app's Settings. 1. **Store is on HTTPS.** WordPress only offers Application Passwords over HTTPS. The store must already be on TLS. If it is not, stop and fix the store first. 2. **Dedicated low-privilege user.** Create or use a WordPress user that can create and edit products and upload media. The store admin login is not reused. A shop manager role or a custom role with product create/edit plus media upload is enough. 3. **Generate the password.** Sign in as that user. Go to **Users → Profile → Application Passwords**. Generate a password and label it "SlabUploader". Copy the password. WordPress shows it once. 4. **Enter it in the app.** Open the app's Settings on the phone. Enter the WordPress username and the application password. The server stores them encrypted. The browser does not keep them. 5. **Run `test-woo`.** In Settings, tap the test. It should report ok. This also resyncs the store's species, categories, tags, and attributes into the app. If it fails, the username or password is wrong, or the store is unreachable. Rules: this is a WordPress Application Password, not WooCommerce consumer keys (`ck_`/`cs_`). The app never calls the store from the browser. FastAPI is the only caller. Rotate by revoking the old password in the WP profile and entering a new one in Settings, then re-running `test-woo`. Full lock: `AGENTS.md` §4 and §5, `docs/DEPLOYMENT.md` §5. --- ## 3. Practice build rules These rules keep practice runs invisible to customers. They are locked. Changes are not made for the walk. - **SKU prefix `SLAB-UAT-*`.** Every practice listing uses a SKU that starts with `SLAB-UAT-`. Pick a unique one each time, for example `SLAB-UAT-001`. - **Always a Woo draft.** A `SLAB-UAT-*` SKU creates a WooCommerce draft even if Settings say publish. Customers cannot open it. This is hard rule in `AGENTS.md` §5. - **`woo_create_status` defaults to draft.** The Settings field that controls create status defaults to `draft`. Practice uses the default. If it is `publish`, the `SLAB-UAT-*` rule still forces a draft. - **Customers never see practice listings.** A Woo draft is hidden from the storefront. If you can see the product on the live store, something is wrong. If a practice SKU is not `SLAB-UAT-*`, it is not a practice run. Real SKUs are not published in this walk. --- ## 4. Prove one listing end to end This is the main walk. Run it on a phone browser first, then repeat on a PC browser. Both are in the signed compatibility matrix (`docs/UX.md`, signed 2026-09-06). Supported browsers: iOS Safari, Android Chrome on phones; desktop Chrome, Edge, Firefox on PC. Exact version pins are tracked under issue #4. If the browser is not supported, the app shows a hard stop. Switch to a supported browser. ### 4.1 Capture 1. Open the app on the phone. Start a new slab. 2. Take 1 to 5 photos of the slab. The first should be top-down, lens parallel to the broad face. A tilted shot makes the numbers wrong. Retake if it is tilted. 3. **Mask overlay and four knobs.** The app draws a mask over the photo. You see which pixels are slab and which are not. Tune the four knobs: **Sheet** (auto/green/black), **Sensitivity**, **Edge offset**, **Feather**. Each change re-runs the mask immediately. Look at the edge against the real wood. If it is still off, retake the photo. 4. **Confirm the mask.** Tap confirm. 5. **Length axis.** The app draws the bounding rectangle and the length axis. Confirm it follows the slab's long direction. Rotate if it is skewed. On a nearly square slab, you say which way is length. 6. **Enter numbers by hand.** Length in 1/8" steps. Thickness as a 1/8" pick. SKU: type `SLAB-UAT-001` (or your chosen practice SKU). 7. **Check the computed numbers.** The browser shows square feet, board feet, and width samples. Board feet = square feet × thickness in inches. No divide-by-12. If the numbers look wrong, go back and fix the mask or the length. 8. **Continue.** This uploads the originals, the processed PNGs, and the numbers to the server. The slab status becomes `calibrated`. ### 4.2 Review 1. You are on the review screen. Fill every required field. Inference is OFF, so everything starts empty. 2. **Species.** Pick exactly one from the synced store list. The list came from `test-woo`. Only species in the list may be typed. 3. **Wood categories.** Pick at least one from the synced list. 4. **Edge type, figure, grade.** Pick from the synced lists. At least one figure, at least one grade. 5. **Thickness band.** Pick the Woo band that the measured thickness rounds up into. 6. **Moisture.** Keep the default (kiln-dried) or change it. 7. **fig-* tags.** Pick at least one. 8. **feat-* tags.** Zero or more. Only pick existing `feat-*` tags. 9. **Price.** A recommendation fills from the species $/bdft rule times board feet. You can override it. If there is no rule for the species, the price starts empty. Type it. Publish is still allowed with a manual price. 10. **Title and description.** You can type them, or leave them for templates at publish. Templates inject the deterministic numbers. Generate text is OFF for this walk. Every empty required field shows an inline nudge where it sits. The slab cannot reach `ready` until the full set is filled. ### 4.3 Publish 1. Tap **Publish**. 2. The server checks the SKU against the store. If `SLAB-UAT-001` is new, it creates the product. 3. **What you should see:** a success message on the screen. The local draft and its photos are gone from the app. The slab status in the app is `published`. 4. **What that does NOT mean:** the product is not live in the store. It is a WooCommerce draft. The app's "published" means the store create succeeded and the local row was cleaned up. ### 4.4 Confirm in the store 1. Open the store's WooCommerce admin. 2. Find the product by SKU `SLAB-UAT-001`. 3. **It must be a draft.** Status is draft, not published. 4. **Check the fields:** SKU, price, category (species), tags (fig-*, feat-*), attributes (Edge Type, Figure, Grade, Thickness, Moisture), and images (the processed transparent PNGs). 5. **Open the live storefront.** The product must not appear. A draft is invisible to customers. ### 4.5 Repeat on a PC browser Repeat 4.1 through 4.4 on a desktop browser (Chrome, Edge, or Firefox). Use a different practice SKU, for example `SLAB-UAT-002`. The flow must work the same way. This is the signed PC-browser compatibility check. --- ## 5. Pass and fail checks You can see all of these without reading code. | Check | Where | Pass looks like | |---|---|---| | Health endpoint | Phone browser, `https://${APP_HOSTNAME}/api/health` | ok response | | `test-woo` ok | App Settings | ok; species list is populated | | `woo_create_status` is draft | App Settings | shows `draft` (or `publish`, but `SLAB-UAT-*` forces draft anyway) | | Slab reaches `calibrated` | App, after capture | status is `calibrated` | | Slab reaches `ready` | App, after review | no empty required fields; inline nudges gone | | Publish succeeds | App, after Publish | success message; local draft is gone | | Woo product exists as draft | Store admin, products | SKU `SLAB-UAT-*`, status draft | | SKU prefix correct | Store admin | starts with `SLAB-UAT-` | | Store not live | Live storefront URL | product does not appear | | Board feet identity | App review screen, or a fixture | bdft = sqft × thickness in inches; no divide-by-12 | | Images are processed PNGs | Store admin, product images | transparent PNGs, not the original JPEGs | ### Duplicate SKU recovery If you publish the same SKU twice, the second time the app stops before creating a second product. Under the SKU field you see: "This SKU already exists in the store (any status)." Two paths: edit the SKU to a new value, or open the existing listing. The draft stays in the app. Nothing is lost. This is the expected behavior, not a bug. See `docs/CONTENT-WOO.md` §2.5. ### What a fail looks like - **No success message after Publish.** The app shows a plain message that the listing could not be created. No status number, no stack trace. The server kept a log. You can retry from the same screen without re-photographing. - **A value is no longer valid.** If a category, attribute, or price was removed from the store between sync and publish, that field shows "This value is no longer valid. Pick from the current list." Re-pick and retry. - **Store unreachable.** A plain message says the store could not be reached. Check the network and retry. --- ## 6. Cleanup After the walk, decide what to do with the Woo draft UAT product. - **Leave it or delete it.** Both are fine. It is a draft. Customers cannot see it. If you delete it, the store admin is the place to do it. The app does not delete store products. - **Never customer-visible.** If the product is visible on the live storefront, it is not a draft. That is a bug. Stop and report it. **Real live publish is a separate step.** It is not part of this walk. A real product uses a non-UAT SKU (no `SLAB-UAT-` prefix) and is published only after you review it. That is a different decision. That step is not run here. The name of that step is **final POC publish**, and it is gated on your review of one real listing. --- ## Where each rule lives | Rule | Source | |---|---| | Practice SKU forces draft | `AGENTS.md` §5 | | `woo_create_status` default draft | `AGENTS.md` §5, `docs/CONTENT-WOO.md` §2.4 | | App Password setup, not consumer keys | `AGENTS.md` §4, `docs/DEPLOYMENT.md` §5 | | Host inventory before deploy | `docs/DEPLOYMENT.md` §1, `deploy/INVENTORY.md` | | Capture → review → publish flow | `docs/UX.md` (SIGNED 2026-09-06) | | Board feet identity | `docs/TECH-SPEC-PIPELINE.md` | | Duplicate SKU recovery | `docs/CONTENT-WOO.md` §2.5, `docs/UX.md` Publish | | PC browser compatibility | `docs/UX.md` Browser compatibility | | Gate C = early Phase 1 | `docs/IMPL-PLAN.md` Gates map and Phase 1 |