docs(m3): settle deterministic title and Call 2 design

Encode the recovered per-leaf title/short-description rule and the Call 2
input set into the point of record.

- REQUIREMENTS.md: METADATA-55 now produces the description only; METADATA-60
  carries the ruled input set; METADATA-80 is the deterministic title; new
  METADATA-81 is the deterministic spec-line short description; METADATA-85
  selects the patterns; REVIEW-15 declares the primary before Call 2;
  PUBLISH-55 carries an explicit name+SKU slug.
- CONTENT-WOO.md: per-leaf title and short-description catalog, slot order,
  dimension placement, and the Cook Woods provenance.
- PROMPTS.md: Call 2 inputs and output; the deterministic patterns are cited,
  not restated.
- CREATE.md, WOO-API.md, MILESTONES.md, task_breakdown.md updated to match.

Modeled on Cook Woods product-title form; the seven per-leaf prose templates
and Specialty cuts are parked in the M3 breakdown.
This commit is contained in:
2026-09-19 11:36:03 -07:00
parent 1dfff0b7c9
commit 120e0be871
7 changed files with 119 additions and 63 deletions
@@ -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/*`.
+1 -1
View File
@@ -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,
+8 -7
View File
@@ -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
+13 -11
View File
@@ -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
+69 -28
View File
@@ -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
+19 -16
View File
@@ -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
+2
View File
@@ -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