Hold the floors drive to its claims, restore the digest's separator, and refresh the weights
Fix round 1 of Task 10, on top of 9231830. digestJS joins its parts with "\u0001" again, as SEED's literal U+0001 byte did; the port had lost the byte to the tool that wrote the file, and without it two elements whose adjacent property values shift across a boundary hash the same. A byte scan of every ported file against its SEED file found that one control byte in scope and no other, and a comparison of every backtick string found nothing else changed beyond the seeded bodies and the helpers that live in the rig. The floors drive now holds the whole of what it claims. With no script, the set of inventory idioms measured on each state must equal the set measured with scripts, both directions, except the three a script builds (the combobox's options, the date pick button, the suggestion rows); the stricter reading holds on every state. Every icon in an inventory control keeps at least 3px between its edge and the word beside it, read in both directions of text with every overlay open, beside the size check. The stretched links the inventory exempts are measured as the row or tile they stretch over, as measureFn measures them on the seeded rows page. A combobox or date list that never opens fails, and every field's list on a page is opened, each in its own field: the first run of that reported the second field of a date range as never opening, because the lookup found the first field's closed list, and the scoped lookup passed. The visited check compares what the server served against the manifest's own list. The idioms no example renders are pinned, so a new gap fails and a filled one does too. The plan id in the file comment is gone. Controls, each in a scratch copy: a script that marks the root and a rule that hides the row action pill without the mark reddens the no-script leg naming list-row-action; the floor block's icon gap set to 0 reddens the touch leg naming two states in both directions. An earlier mutation through <noscript> stayed green because the no-script twin strips script tags with scripting still on, and one on back-nav went red by the gap check, since that state links no script; both are recorded in the report. Weights refreshed from one full verbose run of check: every test at 2s or more has a measured line, which puts the busy drive (22s, unlisted before) and the floors drive (58s) where the deal can see them. Shards 128/146/138s; make ci green.
Human
Prompt
Task 10 fix round 1: restore SEED's digest separator and byte-diff every ported JS string; make the no-script leg check controls per state; add the icon-spacing floor; the minor items (served-based visited check, failing lists, pinned gaps, plan id, weights for the two slow drives, floorsJS comment, stretched links); every mutation in a scratch copy; covering tests, one shard pass and make ci; append Fix round 1 to the report.
From this commit's own message. The prompt ledger has no entries for it — which is what a squashed, rebased or amended commit looks like, and what every commit looks like in a repo without a ledger.
Summary
check/coverage_browser_test.go: per-state no-script equality of measured idioms (scriptBuilt allowed), icon-to-word gap of 3px in both directions, stretched links measured as their rows, every list opened per field and failing if it never opens, served-request visited check against the manifest, pinned floorGaps, plan id removed. check/markup_v3_browser_test.go: digestJS joins with the u0001 escape as SEED's literal byte did. hack/test-weights.txt: every browser test at 2s or more from a full verbose run.
Agent notes
Three mutations recorded honestly: the noscript one could not take (scripting stays enabled on the no-script twin); the back-nav one went red by the pinned-gaps check because that state links no script; the list-row-action one is the real control. The floors drive costs 58s now (was 26s) from the gap reading; weights refreshed wholesale from a -v run per the file's own rule, shards 128/146/138s. Report: .superpowers/sdd/2026-10-27-fiesta-1-package/task-10-report.md, section Fix round 1.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
3 files changed,
+276
−76
check/coverage_browser_test.go+239 −58check/markup_v3_browser_test.go+1 −1hack/test-weights.txt+36 −17
diff --git a/check/coverage_browser_test.go b/check/coverage_browser_test.go| index 8d8aff1..695b9c6 100644 |
| --- a/check/coverage_browser_test.go |
| +++ b/check/coverage_browser_test.go |
| @@ -2,10 +2,11 @@ |
| // The design-system floors on the examples: every state of every |
| // non-layout example, each loaded as its own document, held to the |
| -// tap-target floor, the 16px text floor, the icon sizes and a clean |
| -// load. The seeded sizing fixture (sizing_browser_test.go) is the |
| -// preservation drive for the same floors; this is the coverage drive, |
| -// which the examples feed. The layouts are Task 11's, with the shells. |
| +// tap-target floor, the 16px text floor, the icon sizes and spacing, |
| +// a clean load, and the same controls with no script at all. The |
| +// seeded sizing fixture (sizing_browser_test.go) is the preservation |
| +// drive for the same floors; this is the coverage drive, which the |
| +// examples feed. The layouts are held with the shells, not here. |
| package check |
| import ( |
| @@ -15,6 +16,7 @@ import ( |
| "net/http" |
| "sort" |
| "strings" |
| + "sync" |
| "testing" |
| "time" |
| @@ -32,6 +34,26 @@ type floorState struct { |
| Path, NoScript string |
| } |
| +// floorServer serves the states and counts what it served, by path, |
| +// so the drive can show it visited every state of the manifest rather |
| +// than every state of its own list. |
| +type floorServer struct { |
| + http.Handler |
| + mu sync.Mutex |
| + served map[string]int |
| +} |
| + |
| +// servedSince returns how many times each path was served, as a copy. |
| +func (s *floorServer) snapshot() map[string]int { |
| + s.mu.Lock() |
| + defer s.mu.Unlock() |
| + out := map[string]int{} |
| + for k, v := range s.served { |
| + out[k] = v |
| + } |
| + return out |
| +} |
| + |
| // floorPages reads every state of every non-layout example through |
| // the manifest and example.Document, and serves each at the depth the |
| // tree keeps it, examples/<name>/<state>.html, so the document's own |
| @@ -41,7 +63,7 @@ type floorState struct { |
| // no-script twin, the same document with every script tag stripped, |
| // at examples/<name>/<state>.noscript.html, the same depth. The rest |
| // of the tree (css/, js/, examples/assets/) is served as it is. |
| -func floorPages(t *testing.T) ([]floorState, http.Handler) { |
| +func floorPages(t *testing.T) ([]floorState, *floorServer) { |
| t.Helper() |
| m, err := example.Load(fiesta.FS()) |
| if err != nil { |
| @@ -75,30 +97,62 @@ func floorPages(t *testing.T) ([]floorState, http.Handler) { |
| if len(states) <= 100 { |
| t.Fatalf("the manifest lists %d non-layout states, want more than 100; a set this small is a broken manifest, not a design system", len(states)) |
| } |
| + srv := &floorServer{served: map[string]int{}} |
| mux := http.NewServeMux() |
| tree := http.FileServerFS(fiesta.FS()) |
| for _, prefix := range []string{"/css/", "/js/", "/examples/assets/"} { |
| mux.Handle("GET "+prefix, tree) |
| } |
| for path, doc := range pages { |
| - doc := doc |
| + path, doc := path, doc |
| mux.HandleFunc("GET "+path, func(w http.ResponseWriter, _ *http.Request) { |
| + srv.mu.Lock() |
| + srv.served[path]++ |
| + srv.mu.Unlock() |
| w.Header().Set("Content-Type", "text/html; charset=utf-8") |
| fmt.Fprint(w, doc) |
| }) |
| } |
| - return states, mux |
| + srv.Handler = mux |
| + return states, srv |
| } |
| -// floorProbes is the tap inventory as two selector lists: every probe |
| -// the floor holds on a page (the narrow: and modal: prefixes stripped, |
| -// the elsewhere: entries left out, since a drive of their own measures |
| -// them in a state no example reaches), and the exemptions. An example |
| -// state carries whatever controls its component has, and only the |
| -// ones the inventory names are the floor's to hold: a link in running |
| -// prose is not. The lists are what the measurement filters on. |
| +// manifestStates is the manifest's own list of non-layout states, as |
| +// name/state, read apart from floorPages so the visited check compares |
| +// what the server served against the manifest rather than against the |
| +// loop that asked for it. |
| +func manifestStates(t *testing.T) map[string]floorState { |
| + t.Helper() |
| + m, err := example.Load(fiesta.FS()) |
| + if err != nil { |
| + t.Fatal(err) |
| + } |
| + out := map[string]floorState{} |
| + for _, e := range m.Examples { |
| + if e.Document || e.Kind == "layout" { |
| + continue |
| + } |
| + for _, s := range e.States { |
| + out[e.Name+"/"+s.ID] = floorState{Name: e.Name, State: s.ID, |
| + Path: "/examples/" + e.Name + "/" + s.ID + ".html", |
| + NoScript: "/examples/" + e.Name + "/" + s.ID + ".noscript.html"} |
| + } |
| + } |
| + return out |
| +} |
| + |
| +// floorProbes is the tap inventory as selector lists: every probe the |
| +// floor holds on a page (the narrow: and modal: prefixes stripped, the |
| +// elsewhere: entries left out, since a drive of their own measures |
| +// them in a state no example reaches), plus the stretched links the |
| +// inventory exempts, which measureFn measures as the row or tile they |
| +// stretch over (the whole-row target); and the exemptions proper, the |
| +// inline link and the keyboard-only submit. An example state carries |
| +// whatever controls its component has, and only the ones the |
| +// inventory names are the floor's to hold: a link in running prose is |
| +// not. The lists are what the measurement filters on. |
| func floorProbes(wide bool) (probes, exempt string, keyed map[string]string) { |
| - var sels []string |
| + var sels, ex []string |
| keyed = map[string]string{} |
| for _, e := range tapInventory { |
| sel := e.Probe |
| @@ -116,30 +170,62 @@ func floorProbes(wide bool) (probes, exempt string, keyed map[string]string) { |
| sels = append(sels, sel) |
| keyed[sel] = e.Idiom |
| } |
| - var ex []string |
| - for sel := range tapExempt { |
| + for sel, why := range tapExempt { |
| + if strings.Contains(why, "stretched") { |
| + sels = append(sels, sel) |
| + keyed[sel] = "stretched link, measured as its row or tile" |
| + continue |
| + } |
| ex = append(ex, sel) |
| } |
| sort.Strings(ex) |
| return strings.Join(sels, ", "), strings.Join(ex, ", "), keyed |
| } |
| +// scriptBuilt are the inventory idioms a script draws at run time, so |
| +// a page with no script cannot carry them: the no-script leg allows |
| +// their absence and nothing else's. |
| +var scriptBuilt = map[string]string{ |
| + "[rst-combo-option]": "select.js builds the combobox's list", |
| + "[rst-dtp-pick]": "datetime.js builds the pick button", |
| + "[rst-dtp-row]": "datetime.js builds the suggestion list", |
| +} |
| + |
| +// floorGaps are the inventory idioms no example state renders, each |
| +// with why, which the seeded sizing page holds instead. A new gap |
| +// fails the drive, and so does a pinned gap that an example has since |
| +// filled. |
| +var floorGaps = map[string]string{ |
| + "[rst-shell-chrome] > summary": "the legacy sidebar drawer: no example carries legacy shell markup", |
| +} |
| + |
| // floorsJS measures one state as measureEverything measures the sizing |
| // page, over the inventory's controls only: the closed page, then every |
| // <details> opened alone (the way overlayJS opens it, the shell's |
| -// sibling tail included), then the combobox's list and the date |
| -// field's list where the page has one, each opened by the script that |
| -// owns it and waited for up to ninety frames. It is a function the |
| -// caller applies to the two selector lists. It also says which probes |
| -// are rendered on the page, exemptions aside, with no element measured: |
| -// an idiom an example renders that the floor never read. |
| +// sibling tail included), then every combobox's list and every date |
| +// field's list, each opened by the script that owns it; a list that |
| +// does not open within ninety frames is reported as failed. It repeats |
| +// overlayJS's walk and measureEverything's list opening rather than |
| +// calling them because both are written against the sizing page (the |
| +// walk returns one overlay per evaluate, the lists are found by |
| +// #sizing-combo and the one [rst-dtp]) and they take no filter; the |
| +// filter here is what keeps a prose link out of the floor. It is a |
| +// function the caller applies to the two selector lists. It also |
| +// returns which probes are rendered on the page, exemptions aside, |
| +// with no element measured (an idiom an example renders that the |
| +// floor never read), how many elements each probe had measured, and |
| +// the gap between every icon in a control and the word beside it, in |
| +// both directions of text, with every overlay open. |
| const floorsJS = `(async (probes, exempt) => { ` + measureFn + `; |
| const floored = el => el.matches(probes) && !el.matches(exempt); |
| const skip = el => !floored(el); |
| const frame = () => new Promise(r => requestAnimationFrame(r)); |
| const frames = () => new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))); |
| const until = async cond => { for (let i = 0; i < 90; i++) { if (cond()) return true; await frame(); } return false; }; |
| - const out = {Closed: measure(document, skip), Overlays: [], Lists: {}, Unmeasured: []}; |
| + const name = el => el.tagName.toLowerCase() + (el.id ? "#" + el.id : "") + |
| + [...el.attributes].filter(a => a.name.startsWith("rst-")).map(a => "[" + a.name + (a.value ? "=" + a.value : "") + "]").join("") + |
| + " “" + (el.getAttribute("aria-label") || el.textContent || "").trim().replace(/\s+/g, " ").slice(0, 30) + "”"; |
| + const out = {Closed: measure(document, skip), Overlays: [], Lists: {}, Failed: [], Unmeasured: [], Counts: {}, Gaps: []}; |
| for (const d of document.querySelectorAll("details")) { |
| for (let p = d; p; p = p.parentElement && p.parentElement.closest("details")) p.open = true; |
| const tail = d.closest("[rst-shell-tail], .rst-shell__tail"); |
| @@ -155,23 +241,62 @@ const floorsJS = `(async (probes, exempt) => { ` + measureFn + `; |
| document.querySelectorAll("details[open]").forEach(x => { x.open = false; }); |
| out.Overlays.push(got); |
| } |
| - const shown = sel => { const l = document.querySelector(sel); return !!l && l.checkVisibility() && l.querySelectorAll("[role=option]").length > 0; }; |
| - const combo = document.querySelector("[rst-combo] [role=combobox]"); |
| - if (combo) { |
| + // Each field's own list, not the page's first: a date range holds |
| + // two fields, and the first one's list stays closed while the |
| + // second's is open. |
| + const shown = l => !!l && l.checkVisibility() && l.querySelectorAll("[role=option]").length > 0; |
| + for (const combo of document.querySelectorAll("[rst-combo] [role=combobox]")) { |
| + const label = "the combobox's list (" + name(combo) + ")", list = combo.closest("[rst-combo]").querySelector("[rst-combo-list]"); |
| combo.focus(); combo.click(); |
| - if (await until(() => shown("[rst-combo-list]"))) out.Lists["the combobox's list"] = measure(document.querySelector("[rst-combo-list]"), skip); |
| + if (await until(() => shown(list))) out.Lists[label] = measure(list, skip); |
| + else out.Failed.push(label); |
| combo.dispatchEvent(new KeyboardEvent("keydown", {key: "Escape", bubbles: true})); |
| + await frames(); |
| } |
| - const dtp = document.querySelector("[rst-dtp] [role=combobox]"); |
| - if (dtp) { |
| + for (const dtp of document.querySelectorAll("[rst-dtp] [role=combobox]")) { |
| + const label = "the date field's list (" + name(dtp) + ")", list = dtp.closest("[rst-dtp]").querySelector("[rst-dtp-list]"); |
| dtp.focus(); dtp.value = "t"; dtp.dispatchEvent(new Event("input", {bubbles: true})); |
| - if (await until(() => shown("[rst-dtp-list]"))) out.Lists["the date field's list"] = measure(document.querySelector("[rst-dtp-list]"), skip); |
| + if (await until(() => shown(list))) out.Lists[label] = measure(list, skip); |
| + else out.Failed.push(label); |
| + dtp.dispatchEvent(new KeyboardEvent("keydown", {key: "Escape", bubbles: true})); |
| + await frames(); |
| } |
| const rendered = el => { if (el.checkVisibility && !el.checkVisibility({visibilityProperty: true})) return false; const r = el.getBoundingClientRect(); return r.width >= 2 && r.height >= 2 && getComputedStyle(el).clipPath !== "inset(50%)"; }; |
| for (const sel of probes.split(", ")) { |
| - const present = [...document.querySelectorAll(sel)].filter(e => rendered(e) && !e.matches(exempt) && !e.closest("[inert]")); |
| + const all = [...document.querySelectorAll(sel)]; |
| + out.Counts[sel] = all.filter(e => e.hasAttribute("data-measured")).length; |
| + const present = all.filter(e => rendered(e) && !e.matches(exempt) && !e.closest("[inert]")); |
| if (present.length && !present.some(e => e.hasAttribute("data-measured"))) out.Unmeasured.push(sel); |
| } |
| + // The space between an icon and the word beside it, read as |
| + // TestAnIconKeepsItsSpaceBesideItsText reads it: from the icon's edge |
| + // to the word's first letter, with every overlay open so menu items |
| + // are drawn, in the page's direction and the other one. |
| + const was = document.documentElement.dir; |
| + document.querySelectorAll("details").forEach(d => { d.open = true; }); |
| + for (const dir of ["ltr", "rtl"]) { |
| + document.documentElement.dir = dir; |
| + await frames(); |
| + for (const c of document.querySelectorAll(probes)) { |
| + if (c.matches(exempt) || !c.checkVisibility()) continue; |
| + for (const i of c.querySelectorAll("svg.icon")) { |
| + if (!i.checkVisibility()) continue; |
| + const ir = i.getBoundingClientRect(); |
| + for (const [side, n] of [["after", i.nextSibling], ["before", i.previousSibling]]) { |
| + if (!n || n.nodeType !== 3 || !n.textContent.trim()) continue; |
| + const r = document.createRange(); |
| + if (side === "after") { r.setStart(n, n.textContent.search(/\S/)); r.setEnd(n, n.textContent.length); } |
| + else { r.setStart(n, 0); r.setEnd(n, n.textContent.replace(/\s+$/, "").length); } |
| + const tx = r.getBoundingClientRect(); |
| + if (tx.width === 0) continue; |
| + const gap = side === "after" ? (dir === "rtl" ? ir.left - tx.right : tx.left - ir.right) : (dir === "rtl" ? tx.left - ir.right : ir.left - tx.right); |
| + out.Gaps.push({Name: name(c), Dir: dir, Side: side, Gap: Math.round(gap * 10) / 10}); |
| + } |
| + } |
| + } |
| + } |
| + document.documentElement.dir = was; |
| + document.querySelectorAll("details[open]").forEach(d => { d.open = false; }); |
| return JSON.stringify(out); |
| })` |
| @@ -180,7 +305,13 @@ type floorReading struct { |
| Closed []targetReading |
| Overlays [][]targetReading |
| Lists map[string][]targetReading |
| + Failed []string |
| Unmeasured []string |
| + Counts map[string]int |
| + Gaps []struct { |
| + Name, Dir, Side string |
| + Gap float64 |
| + } |
| } |
| // TestEveryExampleStateMeetsTheFloors is the floors on the examples. |
| @@ -191,22 +322,33 @@ type floorReading struct { |
| // |
| // - every interactive element the tap inventory names is at least |
| // 44 pixels on the phone and 32 on the desktop, whole in the |
| -// viewport and hittable, overlays and lists included; |
| +// viewport and hittable, overlays and lists included, a stretched |
| +// link measured as the row or tile it stretches over; |
| // - every text control is at least 16px on the phone; |
| -// - every icon inside a control is 16px, a caret's 12; |
| +// - every icon inside a control is 16px, a caret's 12, and keeps at |
| +// least 3px between its edge and the word beside it, in both |
| +// directions of text; |
| // - the page loads with no console error, no failed request and no |
| -// JS value leaked to the screen (the rig's screen gate). |
| +// JS value leaked to the screen (the rig's screen gate); |
| +// - with no script, the same idioms are measured on each state as |
| +// with scripts, the three idioms a script builds aside, so a |
| +// component whose controls need a script to appear fails here. |
| // |
| // Every inventory idiom a state renders must have been measured on it, |
| // so an idiom cannot drop out of the floor because its example moved; |
| -// the idioms no example renders at all are logged, not failed: the |
| -// seeded sizing page holds those. The drive asserts that it visited |
| -// every manifest state on every leg, and that the manifest lists more |
| -// than a hundred, so an emptied manifest cannot pass by measuring |
| -// nothing. |
| +// the idioms no example renders at all are pinned in floorGaps, so a |
| +// new gap fails. The drive checks, against the manifest's own list, |
| +// that the server served every state's page on every leg, and that |
| +// the manifest lists more than a hundred, so an emptied manifest or a |
| +// loop that skipped cannot pass by measuring nothing. |
| func TestEveryExampleStateMeetsTheFloors(t *testing.T) { |
| - states, handler := floorPages(t) |
| + states, srv := floorPages(t) |
| + want := manifestStates(t) |
| + if len(want) <= 100 { |
| + t.Fatalf("the manifest lists %d non-layout states, want more than 100", len(want)) |
| + } |
| seen := map[string]int{} |
| + withScripts := map[string]map[string]bool{} |
| var carets, icons int |
| for _, leg := range []struct { |
| name string |
| @@ -224,14 +366,16 @@ func TestEveryExampleStateMeetsTheFloors(t *testing.T) { |
| if leg.coarse { |
| opts = append(opts, harness.WithCoarsePointer()) |
| } |
| - rig := harness.New(t, func(string) http.Handler { return handler }, opts...) |
| + rig := harness.New(t, func(string) http.Handler { return srv }, opts...) |
| ctx, cancel := context.WithTimeout(rig.Context(), 20*time.Minute) |
| defer cancel() |
| mustRun(t, ctx, chromedp.EmulateViewport(leg.w, leg.h)) |
| probes, exempt, keyed := floorProbes(leg.w >= 800) |
| - visited, measured := 0, 0 |
| + before := srv.snapshot() |
| + measured := 0 |
| for _, st := range states { |
| - where := fmt.Sprintf("%s, %s/%s", leg.name, st.Name, st.State) |
| + key := st.Name + "/" + st.State |
| + where := fmt.Sprintf("%s, %s", leg.name, key) |
| path := st.Path |
| if leg.noscript { |
| path = st.NoScript |
| @@ -240,7 +384,6 @@ func TestEveryExampleStateMeetsTheFloors(t *testing.T) { |
| settleUntil(t, ctx, `document.readyState === "complete"`) |
| mustRun(t, ctx, chromedp.Evaluate(`new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r)))`, nil, awaitPromise)) |
| requirePointer(t, ctx, leg.coarse) |
| - visited++ |
| if leg.coarse { |
| var raw string |
| @@ -277,39 +420,77 @@ func TestEveryExampleStateMeetsTheFloors(t *testing.T) { |
| assertTargetsAt(t, where+", "+name, l, leg.floor) |
| measured += len(l) |
| } |
| + for _, name := range got.Failed { |
| + t.Errorf("%s: %s never opened; its options were not measured", where, name) |
| + } |
| for _, sel := range got.Unmeasured { |
| t.Errorf("%s: %s (%s) is rendered and was never measured; the idiom dropped out of the floor", where, sel, keyed[sel]) |
| } |
| - var counts map[string]int |
| - at(t, ctx, `JSON.stringify(Object.fromEntries(`+mustJSON(t, strings.Split(probes, ", "))+`.map(sel => [sel, [...document.querySelectorAll(sel)].filter(e => e.hasAttribute("data-measured")).length])))`, &counts) |
| - for sel, n := range counts { |
| + for _, g := range got.Gaps { |
| + if g.Gap < 3 { |
| + t.Errorf("%s, %s: in %s the icon is %.1fpx from the word %s it; it touches the word", where, g.Dir, g.Name, g.Gap, g.Side) |
| + } |
| + } |
| + here := map[string]bool{} |
| + for sel, n := range got.Counts { |
| seen[sel] += n |
| + if n > 0 { |
| + here[sel] = true |
| + } |
| + } |
| + switch { |
| + case leg.noscript: |
| + with := withScripts[key] |
| + if with == nil { |
| + t.Fatalf("%s: no record of what the scripted leg measured on this state", where) |
| + } |
| + for sel := range with { |
| + if _, built := scriptBuilt[sel]; !here[sel] && !built { |
| + t.Errorf("%s: %s (%s) is measured with scripts and not without; the component does not work with no script", where, sel, keyed[sel]) |
| + } |
| + } |
| + for sel := range here { |
| + if !with[sel] { |
| + t.Errorf("%s: %s (%s) is measured without scripts and not with them; the scripted page hides a control the plain one shows", where, sel, keyed[sel]) |
| + } |
| + } |
| + case !leg.coarse: |
| + withScripts[key] = here |
| } |
| rig.Screen("body", where) |
| } |
| - if visited != len(states) { |
| - t.Errorf("%s: visited %d states, the manifest lists %d", leg.name, visited, len(states)) |
| + after := srv.snapshot() |
| + missing := 0 |
| + for key, st := range want { |
| + path := st.Path |
| + if leg.noscript { |
| + path = st.NoScript |
| + } |
| + if after[path]-before[path] < 1 { |
| + missing++ |
| + t.Errorf("%s: the manifest's %s was never served on this leg", leg.name, key) |
| + } |
| } |
| if measured < 100 { |
| - t.Errorf("%s: measured %d controls over %d states; the instrument read next to nothing", leg.name, measured, visited) |
| + t.Errorf("%s: measured %d controls over %d states; the instrument read next to nothing", leg.name, measured, len(want)-missing) |
| } |
| - t.Logf("%s: %d states, %d controls measured", leg.name, visited, measured) |
| + t.Logf("%s: %d of the manifest's %d states served, %d controls measured", leg.name, len(want)-missing, len(want), measured) |
| }) |
| } |
| if carets == 0 || icons == 0 { |
| t.Errorf("read %d caret icons and %d other icons in controls across every state; the examples must show at least one of each", carets, icons) |
| } |
| - var never []string |
| for _, e := range tapInventory { |
| if strings.HasPrefix(e.Probe, "elsewhere:") { |
| continue |
| } |
| sel := strings.TrimPrefix(strings.TrimPrefix(e.Probe, "narrow:"), "modal:") |
| - if seen[sel] == 0 { |
| - never = append(never, fmt.Sprintf("%s (%s)", sel, e.Idiom)) |
| + _, pinned := floorGaps[sel] |
| + switch { |
| + case seen[sel] == 0 && !pinned: |
| + t.Errorf("no example state renders %s (%s), and it is not a pinned gap; the examples lost the idiom", sel, e.Idiom) |
| + case seen[sel] > 0 && pinned: |
| + t.Errorf("%s (%s) is pinned as a gap in floorGaps but an example renders it; drop the pin", sel, e.Idiom) |
| } |
| } |
| - if len(never) > 0 { |
| - t.Logf("no example state renders %d of the inventory's idioms, which the seeded sizing page holds: %s", len(never), strings.Join(never, "; ")) |
| - } |
| } |
diff --git a/check/markup_v3_browser_test.go b/check/markup_v3_browser_test.go| index 4bf22d4..87e944b 100644 |
| --- a/check/markup_v3_browser_test.go |
| +++ b/check/markup_v3_browser_test.go |
| @@ -161,7 +161,7 @@ const digestJS = `(function (props) { |
| var cs = getComputedStyle(el, origins[o]); |
| for (var p = 0; p < props.length; p++) parts.push(cs.getPropertyValue(props[p])); |
| } |
| - var s = parts.join(""), h = 5381; |
| + var s = parts.join("\u0001"), h = 5381; |
| for (var c = 0; c < s.length; c++) h = ((h * 33) ^ s.charCodeAt(c)) >>> 0; |
| out.push(el.tagName + "|" + h.toString(16)); |
| } |
diff --git a/hack/test-weights.txt b/hack/test-weights.txt| index 3a37aa9..9e520c2 100644 |
| --- a/hack/test-weights.txt |
| +++ b/hack/test-weights.txt |
| @@ -5,29 +5,48 @@ |
| # coverage: every listed test still runs in exactly one slice. Refresh |
| # from a `go test -v` run: |
| # grep '^--- ' log | sed -E 's/^--- [A-Z]+: ([^ ]+) \(([0-9.]+)s\)/\1 \2/' |
| -browser check TestEveryThemeAndShellRendersAsTheBaseline 57 |
| -browser check TestEveryThemeRendersAsItDidExceptDay 41 |
| -browser check TestEveryExampleStateMeetsTheFloors 26 |
| -browser check TestTheLightStaysWithinItsFrameBudget 18 |
| +# Measured 2026-10-27 on docs-t1, one full run of check with full |
| +# Chromium, every test at 2s or more. |
| +browser check TestEveryExampleStateMeetsTheFloors 58 |
| +browser check TestEveryThemeRendersAsItDidExceptDay 45 |
| +browser check TestEveryThemeAndShellRendersAsTheBaseline 37 |
| +browser check TestBusyButtonDrive 22 |
| +browser check TestTheLightStaysWithinItsFrameBudget 17 |
| +browser check TestAnyAccentKeepsTheFillLabelReadable 13 |
| browser check TestFlatThemesFollowAnAppsOverrides 13 |
| -browser check TestAnyAccentKeepsTheFillLabelReadable 12 |
| -browser check TestASwitchSlidesAndSnaps 9 |
| -browser check TestAPostItemsSpinnerNeverMovesTheMenu 8 |
| -browser check TestEveryTapTargetIsAtLeast44Pixels 8 |
| -browser check TestTheWholeRowIsTheTarget 8 |
| +browser check TestASwitchSlidesAndSnaps 8 |
| +browser check TestADropdownOpensUnderItsOwnSummary 7 |
| browser check TestAReleasedKnobSettlesWithAFirmCurve 7 |
| -browser check TestKeysAndTouchLeavePointerMode 7 |
| +browser check TestARowMenuOpensBesideItsOwnKebab 7 |
| +browser check TestAPostItemsSpinnerNeverMovesTheMenu 6 |
| browser check TestAPreferenceToggleForgetsSettledCards 6 |
| +browser check TestKeysAndTouchLeavePointerMode 6 |
| browser check TestACardBreathes 5 |
| -browser check TestBothSpellingsComputeTheSameStyles 5 |
| +browser check TestEveryTapTargetIsAtLeast44Pixels 5 |
| browser check TestLightJSOnAnOldThemeWritesOntoNothingItPaints 5 |
| -browser check TestReducedMotionIsTotal 5 |
| -browser check TestTheCalendarDocksAndItsDaysAreTaps 5 |
| browser check TestTheSheenFollowsADraggedKnob 5 |
| browser check TestALeaveAfterAQueuedMoveNeverRelightsACard 4 |
| -browser check TestTheHeaderRuleIsTheThemesTintedHairline 4 |
| -browser check TestControlsInARowKeepTheirOwnBoxes 3 |
| -browser check TestEnterCommitsWithoutSubmittingTheForm 3 |
| -browser check TestTextControlsAreSixteenPixelsOnSmallOrTouchScreens 3 |
| +browser check TestATblockHeadDragsLikeASwitch 4 |
| +browser check TestBothSpellingsComputeTheSameStyles 4 |
| +browser check TestReducedMotionIsTotal 4 |
| +browser check TestTheSlideKnowsWhichWayItIsGoing 4 |
| +browser check TestTheWholeRowIsTheTarget 4 |
| +browser check TestANarrowRowGivesItsTitleTheWidth 3 |
| +browser check TestMediaGridFramesAreEvenAndCaptionsStayInTheTile 3 |
| +browser check TestTheBackControlReusesHistoryWhenItCanProveWhatIsBehind 3 |
| +browser check TestTheBulkBarNeverWrapsItsText 3 |
| browser check TestTheLightFollowsThePointerAndSettles 3 |
| +browser check TestMediaGridTileIsTheTarget 2 |
| +browser check TestPaginationIsOneBalancedLineOnAPhone 2 |
| +browser check TestSelectFollowsRequiredAndExtras 2 |
| +browser check TestSelectKeepsItsDividerAndPrompt 2 |
| +browser check TestSelectKeepsPaceWithTyping 2 |
| +browser check TestSelectOpensUpWhenThereIsNoRoomBelow 2 |
| +browser check TestSelectOptionClassStylesRowAndBox 2 |
| +browser check TestSelectPlacement 2 |
| +browser check TestSelectRebuildKeepsWhatIsInFlight 2 |
| +browser check TestSelectRebuildsWhenItsOptionsChange 2 |
| +browser check TestSelectStepsAsideWhenReplaced 2 |
| +browser check TestSelectTypingIsCheap 2 |
| +browser check TestTheCalendarDocksAndItsDaysAreTaps 2 |
| browser check TestTheTrackColourFollowsTheKnob 2 |