Fix Feature 12 review findings F2–F8 on Regular grounding and onboarding
CI / ci (push) Successful in 2m21s
CI / ci (pull_request) Successful in 2m29s

Grounding (F2–F5):
- Each ground_dinner job carries the dinner's new grounding_revision
  (migration 0010). Renaming a pending Regular raises the revision, and a
  job only grounds and writes a dinner still at its revision, so a queued
  original job no longer grounds the new name or decides the outcome.
- Background grounding keeps a method, minutes or difficulty a member saved
  while the Regular was pending, and fills only those left empty.
- Grounding by hand clears the pasted recipe_url, and link deduplication
  uses the source_url of the attached recipe, else the pending link.
- Archived dinners are not grounded: the job checks before any search,
  fetch or model call, and its final update requires an unarchived row.

Onboarding (F6, F7):
- Step 3 shows "Add Regulars" back to step 2 when the server refuses to
  finish with fewer than three Regulars.
- Common picks are hidden by a copy of harness Normalize/Similar, checked
  against the same shared cases as the Go code.

Docs (F8): ARCHITECTURE.md covers the three-Regular minimum, step-3
recovery, the grounding revision and the dedupe rules.

F1 was fixed in 1304487; its test now also asserts the old link is never
grounded and recipe_url stays cleared.

Maestro task 5037ff86-181d-448e-927b-c2fd2258a25d.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-06 19:24:47 -04:00
co-authored by Claude Opus 5.5
parent 13044879f6
commit 837c9924d1
22 changed files with 875 additions and 232 deletions
+35
View File
@@ -1,10 +1,45 @@
package harness
import (
"encoding/json"
"os"
"testing"
"unicode"
)
// TestSharedDedupeCases runs testdata/dedupe_cases.json, which the PWA's
// copy of Normalize and Similar (web/src/dinners/names.ts) runs too, so the
// two cannot drift apart on these names.
func TestSharedDedupeCases(t *testing.T) {
raw, err := os.ReadFile("testdata/dedupe_cases.json")
if err != nil {
t.Fatal(err)
}
var cases struct {
Normalize []struct{ Name, Want string }
Similar []struct {
A, B string
Want bool
}
}
if err := json.Unmarshal(raw, &cases); err != nil {
t.Fatal(err)
}
if len(cases.Normalize) == 0 || len(cases.Similar) == 0 {
t.Fatal("dedupe_cases.json has no cases")
}
for _, c := range cases.Normalize {
if got := Normalize(c.Name); got != c.Want {
t.Errorf("Normalize(%q) = %q, want %q", c.Name, got, c.Want)
}
}
for _, c := range cases.Similar {
if got := Similar(c.A, c.B); got != c.Want {
t.Errorf("Similar(%q, %q) = %v, want %v", c.A, c.B, got, c.Want)
}
}
}
func TestNormalize(t *testing.T) {
tests := []struct {
name, want string
+119
View File
@@ -0,0 +1,119 @@
{
"normalize": [
{
"name": "Easy Chicken Tikka-Masala!",
"want": "chicken tikka masala"
},
{
"name": " Homemade Chili ",
"want": "chili"
},
{
"name": "Grandma's Stew",
"want": "grandmas stew"
},
{
"name": "Grandma’s Stew",
"want": "grandmas stew"
},
{
"name": "İskender Kebab",
"want": "iskender kebab"
},
{
"name": "Paella à la Valenciana",
"want": "paella la valenciana"
},
{
"name": "Crème Brûlée",
"want": "creme brulee"
},
{
"name": "Crème Brûlée",
"want": "creme brulee"
},
{
"name": "Smørrebrød",
"want": "smorrebrod"
},
{
"name": "Pierogi z Łososiem",
"want": "pierogi z lososiem"
},
{
"name": "Straße Würstchen",
"want": "strasse wurstchen"
},
{
"name": "Œufs en Cocotte",
"want": "oeufs en cocotte"
},
{
"name": "Æbleskiver",
"want": "aebleskiver"
},
{
"name": "Phở Bò",
"want": "pho bo"
},
{
"name": "The Best Quick Recipe",
"want": ""
},
{
"name": "Chicken stir-fry",
"want": "chicken stir fry"
},
{
"name": "7-layer dip",
"want": "7 layer dip"
}
],
"similar": [
{
"a": "Homemade chili",
"b": "Chili",
"want": true
},
{
"a": "Easy Spaghetti Bolognese",
"b": "spaghetti bolognese",
"want": true
},
{
"a": "Chicken stir fry",
"b": "Chicken stir-fry",
"want": true
},
{
"a": "Crème brûlée",
"b": "Creme brulee",
"want": true
},
{
"a": "Smørrebrød",
"b": "smorrebrod",
"want": true
},
{
"a": "Beef tacos",
"b": "Fish tacos",
"want": false
},
{
"a": "Chili",
"b": "White chicken chili",
"want": false
},
{
"a": "Pot roast",
"b": "Roast",
"want": false
},
{
"a": "The best",
"b": "Easy recipe",
"want": false
}
]
}