Checked against the catalog

We read the page. Then we believe the page.

A product enters the feed only after a real product page states the fiber composition, and that statement is entirely natural fiber. Prices, weights, and countries are copied. If a number was not on the page, the field is null. We do not fill it from memory, a forum, or a size chart we liked better. This version of the feed holds 85 products.

What gets recorded

Verification

The pass that built this version opened maker and retailer pages, including Shopify product data where the store publishes it, and checked the rendered copy for the composition line. A title that merely contains "100% cotton" is not enough if the description is empty. A body that says cotton without a percentage stays in the candidate file.

A disclosed synthetic component excludes the product even when the body cloth is excellent. Ash heather that is 2% polyester is excluded as a color, not allowed to stain the cotton colors of the same style. A satin lining whose fiber is unnamed is excluded, because satin in that role is often polyester or acetate and the page did not say silk. Regenerated cellulose — rayon, viscose, modal, lyocell — is rejected.

node scripts/validate.mjs refuses the catalog if any fiber is outside the natural allowlist, if percents do not add to 100, or if the composition line contains polyester, nylon, acrylic, elastane, spandex, rayon, viscose, modal, lyocell, or the rest of that list. It also checks that a derived gram weight, when we compute one for sorting, follows a single published factor: 1 oz/yd² = 33.905747 g/m², rounded to the nearest gram. If the seller printed both units and they disagree by more than 3%, both are kept and the conflict is shown. Merz b. Schwanen's 3S48 is the case. The count has to stay between 80 and 180 so a quiet deletion does not look like a publish.

Weight bands for the filter are ours, not the maker's adjectives. Under 400 g/m² is substantial. From 400 up to but not including 540 is heavy. 540 and above is ultra. Unstated stays unstated. Sorting uses the gram figure the seller printed when both units exist, including when they disagree.

Price bands

These are an editorial reading of the sticker in its own currency. They are not a foreign-exchange table. The upper edge of each band is exclusive.

CurrencyModestMidHighRare air
USDunder 140140–219220–399400 and up
CADunder 160160–279280–449450 and up
GBPunder 9090–189190–339340 and up
EURunder 120120–209210–359360 and up
JPYunder 18,00018,000–27,99928,000–39,99940,000 and up
DKKunder 1,6001,600–2,3992,400–3,9994,000 and up
SEKunder 800800–1,5991,600–2,7992,800 and up

How a listing ages

checkedOn is the day a person, or the Monday pass, last accepted the page. The site computes age from that date. After 45 days the card is marked stale. After 120 days it leaves the default grid. The "Show hidden" box brings it back. A weekly GitHub Action fetches each product URL and opens an issue when a link dies, the composition sentence disappears, the sticker is missing, or the page says sold out. That job does not rewrite prices or fibers by itself. The machine-readable copy of the same files is /data/catalog.json, which is what a Monday recheck can read without scraping the HTML.

What this pass did not do

No lab tests, no burn tests, no mystery shopping beyond the public page. A seller can be wrong. Silence on the rib is not a disclosed blend, and it is also not a lab test. The log of exclusions and the waiting room is kept out.

Open questions

These are in the feed, or next to it, and they want an editor's yes.