Two taxonomy stamps, validation/module + 412 hash mismatch, SKU charset vs UTF-8 titles, 409 open_existing required, draft = phone form + failed row.
This commit is contained in:
@@ -159,8 +159,8 @@ Payload and publish sequence depth: `docs/CONTENT-WOO.md` (cites this section fo
|
|||||||
- Call 1 auto only if `inference_enabled`; Call 2 never auto-fires (user taps Generate text).
|
- Call 1 auto only if `inference_enabled`; Call 2 never auto-fires (user taps Generate text).
|
||||||
- Call 1 body: all inventory photos @1024 (not top-down-only). Taxonomy lists sent to Call 1 are **exactly the Woo-synced species / attribute / tag sets**. No hardcoded or invented species.
|
- Call 1 body: all inventory photos @1024 (not top-down-only). Taxonomy lists sent to Call 1 are **exactly the Woo-synced species / attribute / tag sets**. No hardcoded or invented species.
|
||||||
- **Confidence 0.7 (default):** ≥ 0.7 → pre-populate species, wood categories, edge, figure, grade, and feat-* from Call 1 (**mutable**). < 0.7 → those fields stay **empty**; user is gated before ready. Mandatory floor: species + ≥1 figure (product also requires ≥1 wood category). Presentation: `docs/UX.md`. Inference never auto-publishes.
|
- **Confidence 0.7 (default):** ≥ 0.7 → pre-populate species, wood categories, edge, figure, grade, and feat-* from Call 1 (**mutable**). < 0.7 → those fields stay **empty**; user is gated before ready. Mandatory floor: species + ≥1 figure (product also requires ≥1 wood category). Presentation: `docs/UX.md`. Inference never auto-publishes.
|
||||||
- **Taxonomy cache:** FastAPI manufactures one last-modified anchor; bump only on stored-subset change; client re-pulls if advanced. `test-woo` always resyncs; publish if >1h. No Woo taxonomy timestamp.
|
- **Taxonomy cache:** `taxonomy_last_sync_at` vs `taxonomy_anchor` (bump only on stored-subset hash change, including deletions). Client re-pulls iff the anchor advanced. `test-woo` always syncs; publish if last_sync >1h. No per-nav sync. No Woo taxonomy timestamp.
|
||||||
- Call 2 must run on a provider that supports **portable Call 1→Call 2 context** (full history resend default; stateful `previous_response_id` only if probe says so). `test-inference` fails closed if neither path works.
|
- Call 2 portable context: default full resend; never require `previous_response_id`. `test-inference` two-turn probe `stateful|stateless|fail`. Fail blocks **Call 2 enablement only**.
|
||||||
- LoRA deferred. No numeric/dimension invention by LLM; templates inject deterministic numbers.
|
- LoRA deferred. No numeric/dimension invention by LLM; templates inject deterministic numbers.
|
||||||
|
|
||||||
Prompt file mechanics: `docs/PROMPTS.md`.
|
Prompt file mechanics: `docs/PROMPTS.md`.
|
||||||
|
|||||||
+12
-8
@@ -175,8 +175,9 @@ ready → publishing → published.
|
|||||||
|
|
||||||
Woo REST exposes **no usable last-modified** on categories, attributes, or tags
|
Woo REST exposes **no usable last-modified** on categories, attributes, or tags
|
||||||
(only products carry `date_modified`). The client never talks to Woo. FastAPI is
|
(only products carry `date_modified`). The client never talks to Woo. FastAPI is
|
||||||
the sole freshness source and **manufactures** one anchor timestamp
|
the sole freshness source. It persists two UTC stamps: `taxonomy_last_sync_at`
|
||||||
(`taxonomy_updated_at` / `cache_anchor`).
|
(every successful Woo pull) and `taxonomy_anchor` (bumps only when the stored-subset
|
||||||
|
hash changes, including deletions).
|
||||||
|
|
||||||
- Anchor bumps **only** when a row-level change is detected in the **stored
|
- Anchor bumps **only** when a row-level change is detected in the **stored
|
||||||
subset** of cache fields (whatever columns the cache tables actually keep:
|
subset** of cache fields (whatever columns the cache tables actually keep:
|
||||||
@@ -207,18 +208,21 @@ only after the next sync that lands it in the cache.
|
|||||||
|
|
||||||
## 8. Shared validation module
|
## 8. Shared validation module
|
||||||
|
|
||||||
One FastAPI module is source of truth for field rules (required, length cap, charset, Woo option membership). Client caches `{ rules, hash }`.
|
One FastAPI module is source of truth (`GET /api/v1/validation/module` → `{ hash, rules[] }`).
|
||||||
|
Two consumers: review UI and Call 2 prompt builder. Client caches the module.
|
||||||
|
|
||||||
- Field-exit: local check against the cache.
|
- SKU charset: `[A-Z0-9-]{3,24}` (printable ASCII subset). Title / short_title / description: UTF-8 within Woo `maxLength`. Not “ASCII-only” and not “extended ASCII”.
|
||||||
- Length: hard input cap (cannot type past Woo max).
|
- Field-exit: local check against the cache. Empty optional is valid.
|
||||||
- Hash is compared **on submit only**. Mismatch → refresh module → re-validate → retry. Draft stays.
|
- Length: `maxLength` is a hard input cap; server still enforces.
|
||||||
|
- Submit carries `validation_hash`. Match → validate. Mismatch → HTTP **412** + fresh module; UI “Updating rules”; re-validate; retry. Never show 412. 409 is duplicate SKU only. Woo-shaped 422 is store rejection (#18).
|
||||||
|
|
||||||
## 9. Call 1 → Call 2 context
|
## 9. Call 1 → Call 2 context
|
||||||
|
|
||||||
Providers differ. Default portable path: **resend the full Call 1 turn** (prompt + images metadata + assistant JSON) with Call 2. Opt into `previous_response_id` only after probe.
|
Providers differ. Default portable path: **resend the full Call 1 turn** (prompt + images metadata + assistant JSON) with Call 2. Opt into `previous_response_id` only after probe.
|
||||||
|
|
||||||
`POST /api/v1/settings/test-inference` fails closed (`ok: false`) if **neither** full-resend nor stateful id works. Do not ship a Call 2 that silently drops Call 1 context.
|
`POST /api/v1/settings/test-inference` two-turn probe records `stateful | stateless | fail`.
|
||||||
|
**Fail blocks Call 2 enablement only** (Gate C inference-OFF still works). Never require `previous_response_id`. Do not ship a Call 2 that silently drops Call 1 context.
|
||||||
|
|
||||||
Per-slab thread. Delete stored thread on successful publish or user abandon.
|
Per-slab thread on the **server** (client does not hold provider IDs). Best-effort delete on publish / abandon / fail; log and continue.
|
||||||
|
|
||||||
Presentation of the review screen: `docs/UX.md`.
|
Presentation of the review screen: `docs/UX.md`.
|
||||||
|
|||||||
+16
-15
@@ -88,17 +88,16 @@ and the final stored content.
|
|||||||
Rebuilds cache tables in one transaction. Failure → `502 woo_error`; previous
|
Rebuilds cache tables in one transaction. Failure → `502 woo_error`; previous
|
||||||
cache retained (never delete-then-fail).
|
cache retained (never delete-then-fail).
|
||||||
|
|
||||||
**Manufactured anchor.** Woo REST has **no** usable last-modified on categories,
|
**Manufactured stamps.** Woo REST has **no** usable last-modified on categories,
|
||||||
attributes, or tags. FastAPI manufactures one `taxonomy_updated_at` /
|
attributes, or tags. FastAPI persists `taxonomy_last_sync_at` (every successful
|
||||||
`cache_anchor` for the whole cache. Bump **only** when a row-level change hits the
|
pull) and `taxonomy_anchor` (bump only when stored-subset hash changes, including
|
||||||
**stored subset** of cache columns (schema-driven: new columns auto-enter the
|
deletions). Client re-pulls the full taxonomy iff the anchor advanced. Do not
|
||||||
diff). Catch adds, edits, and deletions. A no-op sync does **not** bump. Client
|
invent a Woo-side taxonomy timestamp. Do not sync per navigation.
|
||||||
re-pulls the full taxonomy iff the anchor advanced past its last value. One
|
|
||||||
timestamp governs all metadata kinds. Do not invent a Woo-side taxonomy timestamp.
|
|
||||||
|
|
||||||
**Triggers:** (1) successful `test-woo` **always** resyncs; (2) publish path if last
|
**Triggers:** (1) successful `test-woo` **always** resyncs; (2) publish if
|
||||||
successful sync older than **1 hour** (bump only on real change); (3) optional app
|
`now - taxonomy_last_sync_at > 1 hour` then reconcile (bump only on real change);
|
||||||
launch / Settings refresh. Not every navigation.
|
(3) optional app launch / Settings refresh. Stale selected IDs after a bump are
|
||||||
|
#18 422, not a silent remap.
|
||||||
|
|
||||||
**Species exclusivity.** UI pickers, pricing seeds, and Call 1 species lists are
|
**Species exclusivity.** UI pickers, pricing seeds, and Call 1 species lists are
|
||||||
**exactly** the synced species-category leaves. No hardcoded, invented, or fallback
|
**exactly** the synced species-category leaves. No hardcoded, invented, or fallback
|
||||||
@@ -196,7 +195,7 @@ Success path unchanged: set `woo_product_id`, local `published`, purge images.
|
|||||||
```
|
```
|
||||||
1. validate via shared validation module (hash check on submit; see ARCHITECTURE §8)
|
1. validate via shared validation module (hash check on submit; see ARCHITECTURE §8)
|
||||||
2. dedupe by SKU (GET /products?sku=, any status) → 409 duplicate_sku if exists
|
2. dedupe by SKU (GET /products?sku=, any status) → 409 duplicate_sku if exists
|
||||||
3. (re)sync taxonomy if last sync >1h; bump cache_anchor only on stored-subset change
|
3. (re)sync taxonomy if last_sync >1h; bump taxonomy_anchor only on stored-subset hash change
|
||||||
4. resolve category/tags/attributes (Call 1 / manual + name-match; synced set only)
|
4. resolve category/tags/attributes (Call 1 / manual + name-match; synced set only)
|
||||||
5. upload processed PNG inventory images → media → source_urls
|
5. upload processed PNG inventory images → media → source_urls
|
||||||
6. POST /products (status per AGENTS §5 + woo_create_status) → woo_product_id
|
6. POST /products (status per AGENTS §5 + woo_create_status) → woo_product_id
|
||||||
@@ -213,9 +212,11 @@ Success path unchanged: set `woo_product_id`, local `published`, purge images.
|
|||||||
|
|
||||||
## 4. Field-level Woo errors (presentation in `docs/UX.md`)
|
## 4. Field-level Woo errors (presentation in `docs/UX.md`)
|
||||||
|
|
||||||
API codes stay on the wire. The phone never displays `409` or `422` as the message.
|
Wire shape: `{ field, code, message, actions[] }`. The phone never displays HTTP codes.
|
||||||
|
|
||||||
- **409 duplicate SKU** (exists in Woo under any status): inline on SKU. Actions: edit SKU, or open/copy the existing listing. Draft stays on the phone. No in-place update of the other product in POC.
|
- **409 duplicate SKU**: actions `edit_sku` | `open_existing`. Include Woo id + admin URL when GET-by-sku works; if lookup fails, keep `open_existing` (SKU + “open this SKU in Woo admin”). No in-place update of the other product.
|
||||||
- **422 stale or invalid field** (category/attribute/tag/price no longer valid): after a submission-time taxonomy refresh, return the field key plus the **current** allowed options. Inline on that field. Draft stays.
|
- **422 stale field**: current cache options after publish-time sync if #14 fired.
|
||||||
|
- Draft: phone keeps the form; server slab → `failed` (retryable). No IndexedDB.
|
||||||
|
- Module-stale hash is **412**, not 409.
|
||||||
|
|
||||||
The shared validation module (`GET /api/v1/validation-module`) is the client cache for required/length/charset. Hash check on submit only.
|
The shared validation module is `GET /api/v1/validation/module`. Hash check on submit only.
|
||||||
|
|||||||
+3
-2
@@ -21,7 +21,7 @@ Draft-only. Deleted on successful publish. WooCommerce is the source of truth fo
|
|||||||
| Column | Type | Notes |
|
| Column | Type | Notes |
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
| `id` | TEXT PK | client-generated UUID |
|
| `id` | TEXT PK | client-generated UUID |
|
||||||
| `sku` | TEXT UNIQUE NOT NULL | 3-24 [A-Z0-9-] |
|
| `sku` | TEXT UNIQUE NOT NULL | `[A-Z0-9-]{3,24}` printable ASCII subset |
|
||||||
| `status` | TEXT NOT NULL | draft \| calibrated \| ready \| publishing \| published \| failed |
|
| `status` | TEXT NOT NULL | draft \| calibrated \| ready \| publishing \| published \| failed |
|
||||||
| `length_in` | REAL NOT NULL | 1/8" step |
|
| `length_in` | REAL NOT NULL | 1/8" step |
|
||||||
| `thickness_in` | REAL NOT NULL | 1/8" step |
|
| `thickness_in` | REAL NOT NULL | 1/8" step |
|
||||||
@@ -185,7 +185,8 @@ Keys (server settings; secrets encrypted at rest):
|
|||||||
- `inference_enabled`, `content_llm_enabled`
|
- `inference_enabled`, `content_llm_enabled`
|
||||||
- `brand_voice`, `geo_context`
|
- `brand_voice`, `geo_context`
|
||||||
- `woo_create_status` ('draft' | 'publish'), default `draft` — Woo product create status key only; safety policy lives in AGENTS.md §5
|
- `woo_create_status` ('draft' | 'publish'), default `draft` — Woo product create status key only; safety policy lives in AGENTS.md §5
|
||||||
- `taxonomy_updated_at` — manufactured ISO timestamp; bump only when a stored-subset taxonomy row changes. No-op sync does not bump. Not Woo's own `date_modified`.
|
- `taxonomy_last_sync_at` — UTC ISO; every successful Woo taxonomy pull
|
||||||
|
- `taxonomy_anchor` — UTC ISO; bump only when stored-subset hash changes (incl. deletions). No-op sync does not bump. Not Woo `date_modified`.
|
||||||
- `aspect_ratio` ('3:4'), `output_px` (1600), `output_px_min` (1600), `fill_target` (0.80)
|
- `aspect_ratio` ('3:4'), `output_px` (1600), `output_px_min` (1600), `fill_target` (0.80)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -117,7 +117,7 @@ Deliverables:
|
|||||||
|
|
||||||
- Routers per OPENAPI: slabs (CRUD + upload + poll), photos (serve), pricing-rules,
|
- Routers per OPENAPI: slabs (CRUD + upload + poll), photos (serve), pricing-rules,
|
||||||
settings (AES-GCM + test stubs), taxonomy (cache read/write + manufactured
|
settings (AES-GCM + test stubs), taxonomy (cache read/write + manufactured
|
||||||
`taxonomy_updated_at` / `cache_anchor`).
|
`taxonomy_anchor` / `taxonomy_last_sync_at`).
|
||||||
- Optimistic concurrency (`client_rev` / `server_rev`, 409 paths).
|
- Optimistic concurrency (`client_rev` / `server_rev`, 409 paths).
|
||||||
- Status machine only: `draft → calibrated → ready → publishing → published | failed`.
|
- Status machine only: `draft → calibrated → ready → publishing → published | failed`.
|
||||||
Job queue only for publish and inference (later). U2Net-on-demand is deferred from POC. Never for measure/crop.
|
Job queue only for publish and inference (later). U2Net-on-demand is deferred from POC. Never for measure/crop.
|
||||||
|
|||||||
+16
-14
@@ -156,8 +156,9 @@ Sheet/slider prefs are client localStorage only in POC (not in SettingsView).
|
|||||||
- `PUT /api/v1/slabs/{id}` — Update draft fields. Body: partial `SlabCreate` (any subset of editable fields + `client_rev`).
|
- `PUT /api/v1/slabs/{id}` — Update draft fields. Body: partial `SlabCreate` (any subset of editable fields + `client_rev`).
|
||||||
- Server **stores** client-sent derived fields (`sqft`, `bdft`, widths). It may validate shape and ranges. It must **not** recompute mask, sqft, bdft, or widths as source of truth. Client re-runs TECH-SPEC math and PUTs the new numbers.
|
- Server **stores** client-sent derived fields (`sqft`, `bdft`, widths). It may validate shape and ranges. It must **not** recompute mask, sqft, bdft, or widths as source of truth. Client re-runs TECH-SPEC math and PUTs the new numbers.
|
||||||
- `409 revision_conflict`.
|
- `409 revision_conflict`.
|
||||||
- `409 duplicate_sku` → `{ "error": "duplicate_sku", "sku": "...", "woo_admin_url": "..."? }`. UI maps this to the SKU field (`docs/UX.md`). Never show the code.
|
- `409 duplicate_sku` → `{ "field":"sku", "code":"duplicate_sku", "message":"...", "actions":["edit_sku","open_existing"], "woo_product_id": "...?", "woo_admin_url": "...?" }`. `open_existing` is required even if lookup failed (then omit id/url). UI: `docs/UX.md`. Never show the code.
|
||||||
- `422` stale/invalid field → `{ "error": "stale_field", "field": "species_id", "options": [...] }` after a submission-time taxonomy refresh. UI maps to that field. Draft stays.
|
- `422` stale/invalid field → `{ "field":"species_id", "code":"stale_field", "message":"...", "actions":[], "options": [...] }` after a submission-time taxonomy refresh. Draft: phone form + server slab `failed` (retryable).
|
||||||
|
- `412` validation module stale → `{ "code":"validation_module_stale", "module": { "hash":"...", "rules":[] } }`. Not a 409.
|
||||||
- `DELETE /api/v1/slabs/{id}` → `204`. Soft: only unpublished drafts. Published slabs cannot be deleted from the app.
|
- `DELETE /api/v1/slabs/{id}` → `204`. Soft: only unpublished drafts. Published slabs cannot be deleted from the app.
|
||||||
|
|
||||||
### Photo operations
|
### Photo operations
|
||||||
@@ -199,12 +200,13 @@ Sheet/slider prefs are client localStorage only in POC (not in SettingsView).
|
|||||||
```jsonc
|
```jsonc
|
||||||
{ "categories": [...], "attributes": [{ name, slug, terms: [...] }],
|
{ "categories": [...], "attributes": [{ name, slug, terms: [...] }],
|
||||||
"tags": [...],
|
"tags": [...],
|
||||||
"taxonomy_updated_at": "ISO-8601" }
|
"taxonomy_anchor": "ISO-8601",
|
||||||
|
"taxonomy_last_sync_at": "ISO-8601" }
|
||||||
```
|
```
|
||||||
Client re-pulls if `taxonomy_updated_at` is newer than its snapshot. Species/attribute/tag
|
Client re-pulls iff `taxonomy_anchor` advanced. Species/attribute/tag
|
||||||
lists here are the **only** legal options for UI pickers and Call 1.
|
lists here are the **only** legal options for UI pickers and Call 1.
|
||||||
- `POST /api/v1/admin/taxonomy/sync` → `200` `{ "categories": n, "attributes": n, "tags": n, "taxonomy_updated_at": "..." }`.
|
- `POST /api/v1/admin/taxonomy/sync` → `200` `{ "categories": n, "attributes": n, "tags": n, "taxonomy_anchor": "...", "taxonomy_last_sync_at": "..." }`.
|
||||||
- Pulls from Woo, rebuilds the cache. Bump `taxonomy_updated_at` **only** if a stored-subset row changed. `502 woo_error` on failure; previous cache retained.
|
- Pulls from Woo, rebuilds the cache. Always set `taxonomy_last_sync_at` on success. Bump `taxonomy_anchor` **only** if stored-subset hash changed. `502 woo_error` on failure; previous cache retained.
|
||||||
|
|
||||||
### Pricing
|
### Pricing
|
||||||
- `GET /api/v1/admin/pricing` → `[PriceRule]`
|
- `GET /api/v1/admin/pricing` → `[PriceRule]`
|
||||||
@@ -222,16 +224,16 @@ Sheet/slider prefs are client localStorage only in POC (not in SettingsView).
|
|||||||
`woo_consumer_key` / `woo_consumer_secret`.
|
`woo_consumer_key` / `woo_consumer_secret`.
|
||||||
- Secrets re-encrypted server-side. `200` `SettingsView` returned (password
|
- Secrets re-encrypted server-side. `200` `SettingsView` returned (password
|
||||||
never echoed).
|
never echoed).
|
||||||
- `POST /api/v1/settings/test-woo` → `200` `{ "ok": bool, "detail": "...", "taxonomy_updated_at": "..." }`
|
- `POST /api/v1/settings/test-woo` → `200` `{ "ok": bool, "detail": "...", "taxonomy_anchor": "...", "taxonomy_last_sync_at": "..." }`
|
||||||
- Uses stored username + application password over HTTPS Basic Auth against
|
- Uses stored username + application password over HTTPS Basic Auth against
|
||||||
`WOO_BASE_URL` REST v3. Fails closed if credentials missing or URL is not https.
|
`WOO_BASE_URL` REST v3. Fails closed if credentials missing or URL is not https.
|
||||||
- On success, **always** run a taxonomy resync (bump anchor only if rows changed).
|
- On success, **always** run a taxonomy resync (set last_sync; bump anchor only if hash changed).
|
||||||
- `POST /api/v1/settings/test-inference` → `200` `{ "ok": bool, "vision_capable": bool, "portable_resend": bool, "stateful_previous_response_id": bool, "detail": "..." }`
|
- `POST /api/v1/settings/test-inference` → `200` `{ "ok": bool, "mode": "stateful"|"stateless"|"fail", "vision_capable": bool, "detail": "..." }`
|
||||||
- Probe whether Call 1→Call 2 can use full-history resend and/or `previous_response_id`.
|
- Two-turn probe. Default Call 1→Call 2 path is full resend; never require `previous_response_id`.
|
||||||
- **Fail closed** (`ok: false`) if neither portable resend nor stateful id works. Call 2 must not run.
|
- `mode=fail` **blocks Call 2 enablement only**. Gate C inference-OFF still works.
|
||||||
- `GET /api/v1/validation-module` → `200` `{ "hash": "...", "rules": { ... } }`
|
- `GET /api/v1/validation/module` → `200` `{ "hash": "...", "rules": [ ... ] }`
|
||||||
- Shared FastAPI module. Client caches it. Compare `hash` **on submit only**.
|
- Shared FastAPI module (UI + Call 2). Client caches it. Submit sends `validation_hash`.
|
||||||
- Length rules are hard input caps. Field-exit uses the cache locally.
|
- Match → validate. Mismatch → **412** + fresh module. SKU `[A-Z0-9-]{3,24}`; titles UTF-8 + Woo maxLength.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -230,7 +230,7 @@ Price 12.345 → **12.35**.
|
|||||||
| Portable Call 1→Call 2 context (full resend default) | ARCHITECTURE §9, PROMPTS |
|
| Portable Call 1→Call 2 context (full resend default) | ARCHITECTURE §9, PROMPTS |
|
||||||
| Woo payload, publish, 409/422 recovery | CONTENT-WOO, OPENAPI |
|
| Woo payload, publish, 409/422 recovery | CONTENT-WOO, OPENAPI |
|
||||||
| SQLite tables, status machine | DATA-MODEL |
|
| SQLite tables, status machine | DATA-MODEL |
|
||||||
| HTTP contract, taxonomy `cache_anchor` | OPENAPI |
|
| HTTP contract, taxonomy `taxonomy_anchor` | OPENAPI |
|
||||||
| Client vs server topology, validation module | ARCHITECTURE |
|
| Client vs server topology, validation module | ARCHITECTURE |
|
||||||
| Auto-dimension overlay, figure-weighted price | IMPL-PLAN (v1.5) |
|
| Auto-dimension overlay, figure-weighted price | IMPL-PLAN (v1.5) |
|
||||||
| Offline capture | non-goal (online-only POC; see AGENTS) |
|
| Offline capture | non-goal (online-only POC; see AGENTS) |
|
||||||
|
|||||||
+4
-4
@@ -124,8 +124,8 @@ Shared module on FastAPI (`docs/OPENAPI.md`). Client caches it; hash checked **o
|
|||||||
|
|
||||||
- Blur: local check with cached rules. Required fields block; optional empty is valid; optional with content must pass.
|
- Blur: local check with cached rules. Required fields block; optional empty is valid; optional with content must pass.
|
||||||
- Length: hard cap at the Woo limit (cannot type past it).
|
- Length: hard cap at the Woo limit (cannot type past it).
|
||||||
- Character set: invalid characters rejected on exit.
|
- Character set: SKU `[A-Z0-9-]{3,24}`; title/description UTF-8 within Woo max. Invalid characters rejected on exit. Not “extended ASCII”.
|
||||||
- Submit: full pass + hash check. Mismatch → “Updating rules” → re-validate → retry. No data loss.
|
- Submit: full pass + hash check. Mismatch → “Updating rules” (HTTP 412, never shown) → re-validate → retry. No data loss.
|
||||||
|
|
||||||
Presentation: errors sit under the field. No top-of-screen dump for field problems.
|
Presentation: errors sit under the field. No top-of-screen dump for field problems.
|
||||||
|
|
||||||
@@ -133,12 +133,12 @@ Presentation: errors sit under the field. No top-of-screen dump for field proble
|
|||||||
|
|
||||||
## 6. Publish errors (after commit)
|
## 6. Publish errors (after commit)
|
||||||
|
|
||||||
Draft stays on the phone.
|
The phone keeps the form. The server slab goes `failed` (retryable). No IndexedDB.
|
||||||
|
|
||||||
**409 duplicate SKU** — inline on SKU:
|
**409 duplicate SKU** — inline on SKU:
|
||||||
|
|
||||||
- “This SKU already exists in the store (any status).”
|
- “This SKU already exists in the store (any status).”
|
||||||
- Actions: **Edit SKU** (focus the field) · **Open existing listing** (admin URL if the server returned it; otherwise copy SKU and a one-line “look this SKU up in Woo admin”).
|
- Actions: **Edit SKU** (focus the field) · **Open existing listing** (required). Use the admin URL/id when the server returned them; otherwise keep the button and “look this SKU up in Woo admin”.
|
||||||
- Never show `409` as the message.
|
- Never show `409` as the message.
|
||||||
|
|
||||||
**422 stale value** — inline on the field Woo rejected (category, attribute, price, …):
|
**422 stale value** — inline on the field Woo rejected (category, attribute, price, …):
|
||||||
|
|||||||
Reference in New Issue
Block a user