diff --git a/.planning/milestones/v0.5/M3/task_breakdown.md b/.planning/milestones/v0.5/M3/task_breakdown.md index 045d178..fcb5d59 100644 --- a/.planning/milestones/v0.5/M3/task_breakdown.md +++ b/.planning/milestones/v0.5/M3/task_breakdown.md @@ -21,6 +21,13 @@ the same scenario. | Cross | frontend API client; Review / Settings / Publish screens | UI for the slices above | | Exit | inference-off suite, Woo-mock integration, Compose UI E2E, real UAT draft | QUALITY-20/35/50/105/110/120 | +## Parked work items + +1. The seven per-leaf long-description prose templates (the specialized Call 2 prompts) are + authored when the Call 2 slice is built (`METADATA-85`): Knife blanks, Dimensional wood, Live + edge wood, Bowl blanks, logs and caps, Pen blanks, Pepper mill blanks. +2. `Specialty cuts` is out of M3: no title pattern, no prose template, no Call 2 prompt. + ## Frontend layout New code stays outside the Capture union: `frontend/src/api/*`, `frontend/src/create/*`. diff --git a/docs/MILESTONES.md b/docs/MILESTONES.md index 707f91d..28dadc8 100644 --- a/docs/MILESTONES.md +++ b/docs/MILESTONES.md @@ -27,7 +27,7 @@ IDs: METADATA-5, METADATA-10, METADATA-15, METADATA-20, METADATA-25, METADATA-30, METADATA-35, METADATA-40, METADATA-45, METADATA-50, METADATA-55, METADATA-60, METADATA-65, METADATA-66, -METADATA-70, METADATA-75, METADATA-80, METADATA-85, METADATA-90, METADATA-95, METADATA-100, +METADATA-70, METADATA-75, METADATA-80, METADATA-81, METADATA-85, METADATA-90, METADATA-95, METADATA-100, METADATA-105, METADATA-110, METADATA-115, REVIEW-5, REVIEW-10, REVIEW-15, REVIEW-20, REVIEW-25, REVIEW-30, REVIEW-35, REVIEW-40, REVIEW-45, REVIEW-50, diff --git a/docs/REQUIREMENTS.md b/docs/REQUIREMENTS.md index 2573f5f..4b0e67f 100644 --- a/docs/REQUIREMENTS.md +++ b/docs/REQUIREMENTS.md @@ -64,14 +64,15 @@ Sources reconciled 2026-09-18 from the current branch docs plus the `pre-simplif | METADATA-40 | Call 1 returns species, wood categories, edge, figure, grade, and feature-tag suggestions constrained to the synced lists, each with confidence. | M3 | — | | METADATA-45 | Call 1 confidence at or above the threshold (default 0.7) pre-fills review fields and below it leaves them empty; suggestions are always editable. | M3 | — | | METADATA-50 | Typed values always win over inferred values. | M3 | — | -| METADATA-55 | Call 2 is user-triggered only and produces name, short_description, and description; it never runs automatically. | M3 | — | -| METADATA-60 | Call 2 receives all metadata confirmed or changed in review plus the literal dimension fact block; the template guides style and the model has creative license. | M3 | — | +| METADATA-55 | Call 2 runs on demand and produces the product description. | M3 | — | +| METADATA-60 | Call 2 receives the processed photos, the metadata confirmed or changed in review, the primary and secondary wood categories, the parent-category path, the dimension fact block, and brand/geo context. | M3 | — | | METADATA-65 | The model never invents or alters a dimension; every number it prints comes from the fact block. | M3 | — | | METADATA-66 | Every content generation, from a deterministic template or from the model, is logged with its inputs, the model request and response when a model was used, and the final stored content. | M3 | — | -| METADATA-70 | With inference off, deterministic templates produce name, short_description, and description so create never lacks prose. | M3 | — | +| METADATA-70 | With inference off, the server assembles the name and short description and produces the description. | M3 | — | | METADATA-75 | Inference never blocks create and never auto-publishes. | M3 | — | -| METADATA-80 | The fallback title, used only when Call 2 is not run (inference disabled or the text endpoint missing or down), uses the primary figure term name as the character, with a hyphen separator. | M3 | — | -| METADATA-85 | The primary wood category selects a specialized Call 2 prompt. | M3 | — | +| METADATA-80 | The server assembles the product name deterministically from the primary wood category's title pattern and the dimension fact block. Title patterns use only synced taxonomy terms and capture facts. | M3 | — | +| METADATA-81 | The server assembles the short description deterministically as a spec line from the primary wood category's short-description pattern. Short-description patterns use only synced taxonomy terms and capture facts. | M3 | — | +| METADATA-85 | The primary wood category selects the Call 2 prompt and the title and short-description patterns. | M3 | — | | METADATA-90 | The dimension fact block carries exact strings for species, figure term, length, thickness, average width, width min to max, and board feet; there is no L by W by T rectangle assumption. | M3 | — | | METADATA-95 | The dimension-overlay image is never sent to the model. | M3 | — | | METADATA-100 | Call 1 free-text observations (character, inclusions, voids, checks) are mapped to existing synced feat-* tags only; WooCommerce is the authority for feat-* definitions. | M3 | — | @@ -85,7 +86,7 @@ Sources reconciled 2026-09-18 from the current branch docs plus the `pre-simplif |---|---|---|---| | REVIEW-5 | Submission-ready requires at least two images (clean first pic plus its dimension-overlay copy), SKU, length, thickness, computed widths and sqft and bdft, exactly one species leaf, at least one wood-category leaf, one Edge Type, at least one Figure, exactly one Grade, one Thickness band, one Moisture, and a price. | M3 | — | | REVIEW-10 | Title, short_description, and description are required on the product and are satisfied by typed, generated, or templated text. | M3 | — | -| REVIEW-15 | In review the owner declares one primary wood category and any secondary ones; primary drives the title and primary plus secondary drive the description; the default primary is the first category selected. | M3 | — | +| REVIEW-15 | In review the owner declares one primary wood category and any secondary ones before Call 2 runs; primary drives the title and primary plus secondary drive the description; the default primary is the first category selected. | M3 | — | | REVIEW-20 | Every empty required field shows an inline nudge as the owner works; the slab cannot reach ready until the full set is filled, and there is no single submit-time gap list. | M3 | — | | REVIEW-25 | The price suggestion is bdft times the species rate from Settings; when no rate exists the price starts empty and the owner must enter one before create; the owner may override and the override sticks. | M3 | — | | REVIEW-30 | Widths, sqft, and bdft are read-only in production, derived from the mask, and are not overridable. | M3 | — | @@ -112,7 +113,7 @@ Sources reconciled 2026-09-18 from the current branch docs plus the `pre-simplif | PUBLISH-40 | Images upload as processed PNGs with alpha; the clean primary, the primary dimensioned copy, and every remaining batch photo the owner selected are uploaded, with no upper cap on the number of images. | M3 | — | | PUBLISH-45 | The Woo image order is the photos-ready lineup: clean calibration, dimension-overlay copy, then the rest in capture order. | M3 | — | | PUBLISH-50 | Published means the store create succeeded, not that the product is live when status is draft. | M3 | — | -| PUBLISH-55 | Only non-null fields are sent; the payload carries name, type simple, status, sku, regular_price, description, short_description, categories, tags, images, attributes, virtual false, downloadable false, catalog_visibility visible, sold_individually true. | M3 | docs/contracts/WOO-API.md | +| PUBLISH-55 | Only non-null fields are sent; the payload carries name, type simple, status, sku, slug, regular_price, description, short_description, categories, tags, images, attributes, virtual false, downloadable false, catalog_visibility visible, sold_individually true. The slug is the sanitized product name plus the lowercased SKU. | M3 | docs/contracts/WOO-API.md | | PUBLISH-60 | Each published product represents exactly one slab; WooCommerce quantity is one. | M3 | — | ## SETTINGS — configured once diff --git a/docs/capabilities/CREATE.md b/docs/capabilities/CREATE.md index d52ae9b..9ebe68c 100644 --- a/docs/capabilities/CREATE.md +++ b/docs/capabilities/CREATE.md @@ -15,7 +15,7 @@ lives in `docs/contracts/` (`API.md`, `WOO-API.md`, `DATA-MODEL.md`, `PROMPTS.md | METADATA-5, METADATA-10, METADATA-15, METADATA-20, METADATA-25, METADATA-30 | synced taxonomy, cardinality, no term creation | | METADATA-35, METADATA-40, METADATA-45, METADATA-50 | Call 1 inputs, results, confidence gate, precedence | | METADATA-55, METADATA-60, METADATA-65, METADATA-66 | Call 2, context, dimension safety, logging | -| METADATA-70, METADATA-75, METADATA-80, METADATA-85, METADATA-90, METADATA-95 | template fallback, non-blocking, prompt selection, fact block | +| METADATA-70, METADATA-75, METADATA-80, METADATA-81, METADATA-85, METADATA-90, METADATA-95 | deterministic copy, non-blocking, pattern selection, fact block | | METADATA-100, METADATA-105, METADATA-110, METADATA-115 | feat mapping, prompt storage, Generate text control, failure triage | | PUBLISH-5, PUBLISH-10, PUBLISH-15, PUBLISH-20 | create sequence, draft rule, duplicate, stale | | PUBLISH-25, PUBLISH-30, PUBLISH-31, PUBLISH-35, PUBLISH-36, PUBLISH-37, PUBLISH-38 | success, failure, partial failure, triage | @@ -51,17 +51,19 @@ or above the threshold (default 0.7) a field pre-fills and stays editable; below stays empty (`METADATA-45`). Typed values always win (`METADATA-50`). Free-text observations map to synced `feat-*` tags only (`METADATA-100`). -Call 2 is user-triggered only and produces name, short_description, and description -(`METADATA-55`). It is a fresh request carrying the full Call 1 context and the literal dimension -fact block; the model never invents a dimension (`METADATA-60`, `METADATA-65`, `METADATA-90`). -The Generate text control appears only when content generation is enabled and the endpoint is -reachable (`METADATA-110`). All generation is logged (`METADATA-66`). +Call 2 is user-triggered only and produces the product description (`METADATA-55`). It receives the +processed photos, the review-confirmed metadata, the primary and secondary wood categories, the +parent-category path, the dimension fact block, and brand/geo context; the model never invents a +dimension (`METADATA-60`, `METADATA-65`, `METADATA-90`). The Generate text control appears only +when content generation is enabled and the endpoint is reachable (`METADATA-110`). All generation +is logged (`METADATA-66`). -With inference off, deterministic templates produce the prose so create never lacks it -(`METADATA-70`); the fallback title uses the primary figure term with a hyphen separator -(`METADATA-80`). Prompts live in the database with all revisions (`METADATA-105`). Inference never -blocks create and never auto-publishes (`METADATA-75`); a runtime failure offers Retry when -recoverable or explains when it is not (`METADATA-115`). +With inference off, the server assembles the name and short description and produces the +description (`METADATA-70`). The name and short description are assembled from the primary wood +category's patterns (`METADATA-80`, `METADATA-81`, `METADATA-85`). Prompts live in the database with +all revisions (`METADATA-105`). Inference never blocks create and never auto-publishes +(`METADATA-75`); a runtime failure offers Retry when recoverable or explains when it is not +(`METADATA-115`). ## 3. Settings prerequisite diff --git a/docs/contracts/CONTENT-WOO.md b/docs/contracts/CONTENT-WOO.md index 062efe5..ae62d66 100644 --- a/docs/contracts/CONTENT-WOO.md +++ b/docs/contracts/CONTENT-WOO.md @@ -10,67 +10,108 @@ Owns how listing copy is produced and the intent of the create payload. The wire | Requirement | Impact on this design | |---|---| | REVIEW-10, REVIEW-15, REVIEW-40, REVIEW-50 | required prose, primary category, editability, limits | -| METADATA-40, METADATA-55, METADATA-60, METADATA-65, METADATA-66 | model scope, Call 2, context, dimension safety, logging | -| METADATA-70, METADATA-75, METADATA-80, METADATA-85, METADATA-90, METADATA-95, METADATA-110 | template fallback, non-blocking, prompt selection, fact block, overlay exclusion, control | +| METADATA-55, METADATA-60, METADATA-65, METADATA-66 | Call 2, inputs, dimension safety, logging | +| METADATA-70, METADATA-75, METADATA-80, METADATA-81, METADATA-85, METADATA-90, METADATA-95, METADATA-110 | deterministic copy, fallback, pattern selection, fact block, overlay exclusion, control | | PUBLISH-5, PUBLISH-10, PUBLISH-15, PUBLISH-20, PUBLISH-25, PUBLISH-30, PUBLISH-31, PUBLISH-40, PUBLISH-45, PUBLISH-50, PUBLISH-55, PUBLISH-60 | payload and create outcomes | | SETTINGS-30, SETTINGS-65 | brand voice and geo context; create status | | METADATA-10, METADATA-15, METADATA-30 | categories, attributes, tags on the payload | ## 1. Copy sources -Titles and descriptions come from one of three sources (`REVIEW-10`): +Three fields, three sources (`REVIEW-10`): -1. **Typed** by the owner. -2. **Model** when Call 2 runs; the owner triggers it (`METADATA-55`). -3. **Template** as the fallback when Call 2 does not run (`METADATA-70`). +1. **name** — assembled deterministically by the server from the primary wood category's title + pattern and the dimension fact block (`METADATA-80`). The model does not write it. +2. **short_description** — assembled deterministically as a spec line from the primary wood + category's short-description pattern (`METADATA-81`). The model does not write it. +3. **description** — written by Call 2 when it runs (`METADATA-55`), from the inputs in + `METADATA-60`; with inference off the server produces it (`METADATA-70`). -The template guides style; when the model runs it writes the prose (`METADATA-80`). The model -writes prose only and never touches a dimension; every number comes from the dimension fact block -(`METADATA-60`, `METADATA-65`, `METADATA-90`). The dimension-overlay image is never sent -(`METADATA-95`). The owner can edit any result (`REVIEW-40`). All text respects the field limits -in `docs/contracts/WOO-API.md` §9 (`REVIEW-50`). +The primary wood category selects the title pattern, the short-description pattern, and the Call 2 +prompt (`METADATA-85`, `REVIEW-15`). The owner can edit any field until create (`REVIEW-40`), and +every field respects the limits in `docs/contracts/WOO-API.md` §9 (`REVIEW-50`). -The Generate text control appears only when content generation is enabled and the endpoint is +The model writes the long description only, from the fact block; it never invents a dimension +(`METADATA-65`, `METADATA-90`). The dimension-overlay image is never sent (`METADATA-95`). The +Generate text control appears only when content generation is enabled and the endpoint is reachable (`METADATA-110`); a failed generation never blocks create (`METADATA-75`). Every generation is logged (`METADATA-66`). -## 2. Default templates +## 2. Deterministic title and short description -Placeholders use `{field}`; all math fields come from the deterministic pipeline, never the model -(`METADATA-90`). +Both patterns are assembled by the server and use only synced taxonomy terms and capture facts +(`METADATA-80`, `METADATA-81`). Placeholders use `{field}`; every number comes from the +deterministic pipeline, never the model (`METADATA-90`). -**Title** (hyphen separator; `{character}` is the primary figure term name, `METADATA-80`): +The shape follows the convention a working mill store uses: descriptor-led, dimension-light titles, +with the measurements carried separately. See "Provenance" below. + +### 2.1 Title + +Slot order: ``` -{species} {length_ft}ft {length_in_remainder}in x {width_avg_in}in Wood Slab - {character} +[Figure] {species} [Live Edge] {noun} [(dims)] ``` +1. `Figure` leads as a Title-Case adjective and is omitted when the Figure term is `plain`. +2. Grade is not used in the title. +3. `Live Edge` appears only when the Edge Type term is Live Edge. +4. There is no separator between slots. +5. Dimensions ride in the title only for the turning leaves, as a trailing `(L x W x T)`. + +Per-leaf patterns: + +| Leaf | Pattern | Example | +|---|---|---| +| Live edge wood | `[Figure] {species} Live Edge Slab` | `Figured Black Walnut Live Edge Slab` | +| Dimensional wood | `[Figure] {species} [Live Edge] Dimensional` | `Figured Red Oak Dimensional` | +| Knife blanks | `[Figure] {species} Knife Blank (L x W x T)` | `Figured Mirindiba Knife Blank (12 x 6.5 x 1.5 in)` | +| Bowl blanks | `[Figure] {species} [Live Edge] Bowl Blank (L x W x T)` | `Spalted Cherry Bowl Blank (8 x 8 x 3 in)` | +| logs and caps | `[Figure] {species} Log Cap` | `Figured Black Walnut Log Cap` | +| Pen blanks | `[Figure] {species} Pen Blank (L x W x T)` | `Figured Purpleheart Pen Blank (5.5 x 1.1 x 1.1 in)` | +| Pepper mill blanks | `[Figure] {species} Pepper Mill Blank (L x W x T)` | `Birdseye Olive Pepper Mill Blank (10 x 3.2 x 3 in)` | + +`Specialty cuts` has no M3 pattern. + `length_ft = floor(length_in / 12)`, `length_in_remainder = length_in mod 12` (1 dp). -**Short description:** +### 2.2 Short description + +One spec line, so the measurements appear even when the title omits them: ``` -{species} Slab - {character} +[Figure] {species} {noun}, {length} x {width} x {thickness}, {bdft} bdft ``` -**Description** (multi-paragraph): +| Leaf | Example | +|---|---| +| Live edge wood | `Figured Black Walnut Live Edge Slab, 9ft 6in x 24.3in x 1.75in, 18.4 bdft` | +| Dimensional wood | `Figured Red Oak Dimensional, 8ft 0in x 5.8in x 1.25in, 4.8 bdft` | +| Pepper mill blanks | `Birdseye Olive Pepper Mill Blank, 10in x 3.2in x 3in, 0.7 bdft` | -``` -A one-of-a-one {character} {species} slab, {length_ft}ft {length_in_remainder}in long with an -average width of {width_avg_in}in and {thickness_in}in thickness ({bdft} bdft). +### 2.3 Description -Each slab is naturally shaped; width varies along the length at {width_min_in}in to -{width_max_in}in. Sold by the slab; dimensions as measured. {geo_sentence} -``` +The long description is the model's prose when Call 2 runs (`METADATA-55`); with inference off the +server produces it (`METADATA-70`). The per-leaf prose templates that guide Call 2 are authored +with that slice and are not defined here. -`geo_sentence` comes from Settings and is omitted when unset (`SETTINGS-30`). The primary wood -category selects a specialized Call 2 prompt (`METADATA-85`). +### 2.4 Provenance + +The title and short-description form was modeled on the live product titles of Cook Woods +(cookwoods.com), captured 2026-09-19. Borrowed: descriptor-led titles; dimension-free titles for +slabs, lumber, and caps; parenthetical dimensions only for turning stock; no separator; title case. +Not borrowed: their size-class nouns (`Mini Slab`, `Table`, `Micro Lumber`, `Long`) and pack counts +(`5 Pack`), which have no synced taxonomy term, and their grade adjectives, which are not used in +our titles. ## 3. Payload intent The exact wire shape is `docs/contracts/WOO-API.md`. Intent: - Only non-null fields are sent (`PUBLISH-55`). +- The payload carries an explicit `slug`: the sanitized product name plus the lowercased SKU. + Dimensions never appear in the slug (`PUBLISH-55`). - `status` comes from `woo_create_status` (default draft); `SLAB-UAT-*` always draft (`SETTINGS-65`, `PUBLISH-10`). - Categories are the species leaf plus each wood-category leaf; attributes are the five locked diff --git a/docs/contracts/PROMPTS.md b/docs/contracts/PROMPTS.md index 27c8652..ab15e99 100644 --- a/docs/contracts/PROMPTS.md +++ b/docs/contracts/PROMPTS.md @@ -9,8 +9,8 @@ Owns prompt storage, prompt inputs, and the two inference calls. |---|---| | METADATA-5, METADATA-10, METADATA-15, METADATA-30 | taxonomy choices and cardinality | | METADATA-35, METADATA-40, METADATA-45, METADATA-50 | Call 1 inputs, constraints, pre-fill, precedence | -| METADATA-55, METADATA-60, METADATA-65, METADATA-70 | Call 2 trigger, context, constraints, fallback | -| METADATA-75, METADATA-80, METADATA-85, METADATA-90, METADATA-95 | non-blocking, fallback title, prompt selection, fact block, overlay exclusion | +| METADATA-55, METADATA-60, METADATA-65, METADATA-70 | Call 2 output, inputs, dimension safety, fallback | +| METADATA-75, METADATA-80, METADATA-81, METADATA-85, METADATA-90, METADATA-95 | non-blocking, deterministic copy, pattern selection, fact block, overlay exclusion | | METADATA-100, METADATA-105, METADATA-110, METADATA-115 | feat mapping, prompt storage, button, failure | | METADATA-66 | generation logging | | SETTINGS-30 | brand voice and geo context | @@ -59,31 +59,34 @@ recoverable or unrecoverable for the owner. only when content generation is enabled and the endpoint is reachable. **Inputs.** -- A fresh, full Call 1 context: prompt, Call 1 photos, and the Call 1 result. No provider thread - id is used. -- The confirmed or changed metadata from review. +- The clean processed photos. +- The metadata confirmed or changed in review, including the primary and secondary wood categories + and the parent-category path. - The literal dimension fact block, exact strings for species, figure term, length, thickness, average width, width min–max, and board feet; no length-by-width-by-thickness rectangle assumption. - Brand voice and geo context. +No provider thread id is used, and Call 1's raw prompt and result are not sent (`METADATA-60`). + **Constraints.** - The model writes prose only and never invents or alters a dimension; every number comes from the - fact block. -- The dimension-overlay image is never sent. + fact block (`METADATA-65`). +- The dimension-overlay image is never sent (`METADATA-95`). -**Output.** `title`, `short_description`, `description`. The owner can edit the result. +**Output.** The long `description` prose. The server assembles `name` and `short_description` +(`METADATA-55`, `METADATA-80`, `METADATA-81`). The primary wood category selects the specialized +prompt (`METADATA-85`). The owner can edit the result (`REVIEW-40`). ## 4. Fallback and templates -- With inference off, deterministic templates produce name, short_description, and description so - create never lacks prose. -- The template is the fallback only; when Call 2 runs, the model writes and the template guides - style. -- The fallback title uses the primary figure term name as the character, with the hyphen - separator. -- The primary wood category selects a specialized Call 2 prompt. -- All generated text stays within the field limits in `docs/contracts/WOO-API.md` §9. +- With inference off, the server assembles the name and short description and produces the + description so create never lacks prose (`METADATA-70`). +- The deterministic title and short-description patterns live in `docs/contracts/CONTENT-WOO.md` + (`METADATA-80`, `METADATA-81`). +- The primary wood category selects the specialized Call 2 prompt (`METADATA-85`). +- All generated text stays within the field limits in `docs/contracts/WOO-API.md` §9 + (`REVIEW-50`). ## 5. Prompt management diff --git a/docs/contracts/WOO-API.md b/docs/contracts/WOO-API.md index c8da102..4102a1e 100644 --- a/docs/contracts/WOO-API.md +++ b/docs/contracts/WOO-API.md @@ -49,6 +49,7 @@ shape and the external constraints. | — | `type` | string | `simple` (default) | | create status | `status` | string | `draft`, `pending`, `private`, `publish`; default `publish` | | sku | `sku` | string | unique; no documented format | +| slug (name + SKU) | `slug` | string | sanitized; WordPress dedupes on collision | | price | `regular_price` | string | monetary string, 2 decimals | | description | `description` | string | none documented | | short_description | `short_description` | string | none documented | @@ -126,6 +127,7 @@ Hard limits are verified from WordPress/WooCommerce source; the display caps are | `short_description` | `wp_posts.post_excerpt` (`text`) | 65,535 | 160 | search snippet / meta description width | | `description` | `wp_posts.post_content` (`longtext`) | ~16.7M | none | no word-count target; write for the page | | `sku` | `wc_product_meta_lookup.sku` (`varchar(100)`, index `sku(50)`) | 100 | 50 | index prefix length | +| `slug` | `wp_posts.post_name` (`varchar(200)`) | 200 | 200 | WordPress `wp_unique_post_slug` truncates the base near 200 | Sources: WordPress `schema.php`, WooCommerce `class-wc-install.php`, `class-wc-product-data-store-cpt.php`, and Google Search Central (title links, snippets, SEO