# Bathroom Storage, Holders & Racks: research notes

Date: 2026-10-25. **Scope**: wall- or surface-mounted holders, caddies, racks and trays that store or organize items inside a bathroom — towel bars, rings and hooks; shower/tub caddies; toilet paper holders; toothbrush, soap, cup and toilet-brush holders and trays; and over-toilet or corner bathroom shelving units. Excludes: laundry hampers/baskets (sibling `laundry-baskets`, even when placed in a bathroom), soap/lotion pump dispensers themselves (sibling `dispensers` — this register covers the tray or holder a bottle sits in, not the pump bottle), generic storage bins/containers with no bathroom-specific design (siblings `storage_containers`/`containers`), modular dividers and inserts (sibling `organizer_inserts`), and non-bathroom hooks/hangers such as closet or entryway hooks (sibling `hangers`). Where a product genuinely straddles a boundary (e.g. a toilet-brush set: the holder is this leaf, the brush head's bristle material is `cleaning-tool-brushes`), the overlap is called out below rather than swallowed.

Quote labels: **verbatim** = the exact sentence was read with my own eyes in raw text (curl + tag-stripping, read directly, not a model summary). **fetched excerpt** = a WebFetch tool call returned a summary of a page I confirmed loaded (HTTP 200) but did not personally read as raw text. **search excerpt** = from a search-engine result page, never used alone for a JSON number. No search-excerpt-only numbers are in `category.json`.

## Seed

Seed script output, recorded verbatim:

```
=== Example entry (from an unrelated category) ===
category: olive-oil
what this shows: a facet that experts rank first, that almost no listing
carries, and that is expensive to backfill. ... Also shows a derived metric
hanging off a researched facet. [harvest_date facet + months_since_harvest metric]

=== Persona ===
A tailor, who thinks in fit, drape and what you can alter afterwards
```

**Q1 — the bathroom-storage equivalent of `harvest_date`**: the real metal grade behind a "stainless steel" claim. Harvest date matters because the label tells you almost nothing and the true value (months since harvest) decides freshness; the word "stainless" on a bathroom rack is the same kind of empty label — it covers everything from true 18/8 (304) stainless, which resists rust for years, down to thin chrome-plated base metal or an unspecified low grade that can show rust at a cut edge or screw hole. No retail listing states the number; only a brand's full spec page or a material certification does. Kept as the `material_grade` facet, source `researched`, backfill cost `high`, explicitly limited to the top N shelving and towel-bar products (the largest, priciest, most-metal items) rather than backfilled everywhere.

**Q2 — what the tailor notices first**: a tailor cannot do anything useful until they have your measurements, and nothing on a bathroom-storage listing gives the assistant the buyer's own measurements either. A tension rod or over-door hook only works across a specific span, door thickness or tank-lid depth that lives in the buyer's bathroom, not on the product page; get it a half-inch wrong and it will not jam tight or the door will not close. This is exactly the tailor's "fit" problem, transplanted. Kept as the `mount_fit_range` facet (text, researched, limited to tension and over-door subtypes) and as the rationale for the `mounting_method` split coming before weight capacity. I did not keep a literal "take your own measurements" cold-start question, because current memory/session data has no way to capture a tape-measure reading, so there is nothing for the assistant to ask yet beyond noting the fit requirement as an annotation; this is flagged as a data gap rather than invented as a question.

## Sources and what each supports

- **ASTM F2057-23**, "Standard Safety Specification for Clothing Storage Units", `https://www.astm.org/f2057-23.html`. **verbatim** (curled the page, stripped tags, read the Scope section myself): "This safety specification is intended to reduce injuries and deaths of children from hazards associated with tipover of free-standing clothing storage units, including but not limited to chests, chests of drawers, drawer chests, armoires, chifferobes, bureaus, door chests, and dressers, which are 27 in. (686 mm) or greater in height, 30 lb (13.6 kg) or greater in mass, and contain 3.2 ft³ ... of enclosed storage volume." and "1.2 This safety specification does not cover shelving units, such as bookcases or entertainment furniture, ... or laundry storage/sorting units". Supports the judgement that no mandatory U.S. stability standard governs bathroom shelving: the one rule in this space (which CPSC's 16 CFR 1261 adopts by reference) explicitly carves out shelving and laundry-storage furniture and only ever applied to clothing-storage furniture past a height/mass/volume floor that most bathroom pieces do not reach anyway.
- **16 CFR 1261.2**, "Requirements for clothing storage units", `https://www.ecfr.gov/current/title-16/chapter-II/subchapter-C/part-1261/section-1261.2`. **verbatim** (curled with a browser user-agent, `-A "Mozilla/5.0 ..."`, stripped tags, read myself; a bare curl/WebFetch call to eCFR returns a redirect/bot-check, the browser UA does not): "§ 1261.2 Requirements for clothing storage units. Each clothing storage unit that is subject to ASTM F2057-23, Standard Safety Specification for Clothing Storage Units, approved on February 1, 2023, shall comply with ASTM F2057-23. ASTM F2057-23 is incorporated by reference into this section..." This is the second, independent source for judgement 1 that the blind check found reachable: it confirms CPSC's own rule adopts F2057 by reference rather than setting its own shelving standard, so the judgement now rests on two sources (the standard's own scope text and the regulator's text that incorporates it), not one.
- **Wikipedia, "Suction cup"**, `https://en.wikipedia.org/wiki/Suction_cup`. **fetched excerpt** via WebFetch (page confirmed reachable, content summarized by the tool, not independently re-curled since the claim is mechanistic rather than a number): suction cups need a "flat, non-porous surface", and hold time "depends mainly on how long it takes for air or water to leak back into the cavity between the cup and the surface", which in turn depends on "the porosity and flatness of the surface and the properties of the cup's rim." Supports (a) the judgement that suction only seals reliably on smooth, non-porous surfaces (tile, glass) and not on grout, textured tile, or paint, and (b) the `wet_zone` split's rationale and its branch into `drainage_or_ventilated`/`material_grade` — the explicit mention of *water* leaking into the cavity, not just air, directly ties suction failure to how wet the mounting spot actually is, which is the stated mechanism behind "mounted inside the shower fails faster than the same part on a dry wall."
- **Wikipedia, "Stainless steel"**, `https://en.wikipedia.org/wiki/Stainless_steel`. **verbatim** for the chromium threshold (curled and grepped myself): "Stainless steel's resistance to corrosion comes from its chromium content of 10.5% or more, which forms a passive film that protects the material and can self-heal when exposed to oxygen." **fetched excerpt** for the grade composition (WebFetch summary, not re-curled): Type 304/316 are "chromium-nickel alloys", 304 is "the most common type" containing "18% chromium"; Type 201/202 are "chromium-manganese-nickel alloys" that "maximize manganese/nitrogen while minimizing nickel." Supports the `material_grade` facet's definition. I did **not** find a sourced, direct statement ranking 201's corrosion resistance below 304's in humid/wet use specifically (the fetched text gives composition, not a wet-corrosion ranking), so the judgement is worded around the chemistry that is sourced (chromium threshold, what 201 substitutes) rather than asserting "201 rusts faster in a bathroom," which I could not confirm to two sources. The International Stainless Steel Forum page Wikipedia cites for a fuller corrosion-resistance chapter (`worldstainless.org`) returned 404 on the URL I guessed.
- **Wikipedia, "SAE 304 stainless steel"**, `https://en.wikipedia.org/wiki/304_stainless_steel`. **verbatim** (curled with a browser user-agent, stripped tags, read myself): its composition table, sourced to ASTM A276/A276M, gives UNS S30400 as "18.0–20.0" chromium and "8.0–11.0" nickel, %wt; the page also lists "18/8 and 18/10 stainless steel ... in the commercial tableware and fastener industries" as alternate names for the grade. This is the second source for the vocabulary term "18/8 stainless steel": it supports "about 18% chromium and 8% nickel" as the low end of 304's real range, which `wikipedia_stainless_steel` alone did not state (that page gives 18% chromium for 304 but no nickel figure tied to the grade). Added to `category.json`'s `references` as `wikipedia_304_stainless` and cited on `material_grade`'s `evidence`.
- **Zamana household-standard schema part** (internal, `zamana-expertise/data/schema-parts/supplies/household/household-standard.jsonc`). Not a public source; cited because the `mounting_method` and `material` facets deliberately reuse the existing `mounting_install` and `materials` enums as their baseline vocabulary rather than inventing a parallel one, per the brief's "Against the existing add-on schema" instruction.

### What I could not reach (and why)

- **WebSearch**: reported its 200-call session budget was already used before I made a single call for this category, so every lead after that came from direct fetches, guessed URLs on known domains, or links already present in a fetched page. This is a hard limit, not a choice.
- **3M / Command** (`command.com`): every URL, including the bare domain, returned HTTP 403 to both WebFetch and a browser-UA curl. I wanted this for a sourced weight rating and the "not recommended for heavy or humid use" caveat on their strips; I do not have it, so no Command-specific number appears in `category.json`. `wayback/available` returned no snapshot either.
- **CPSC** (`cpsc.gov`): every page I tried (furniture stability guide, clothing-storage-unit page) returned 403 or 404. The regulation itself, via `ecfr.gov`, failed with a bare curl/WebFetch call too, but a browser user-agent (`-A "Mozilla/5.0 ..."`) got a 200 on the blind check's second pass — see the `cfr_1261_2` reference above, now cited on judgement 1.
- **Reddit** (r/HomeImprovement and similar): the fetch tool refuses `reddit.com` outright. I wanted the renter/no-drill-mount FAQ community knowledge from here; it is not represented by a citation, only by my own synthesis in the mechanism section, which I have kept to claims that do not need a number (surface and humidity, not a specific hold time).
- **Retail listings** (Amazon, Target, Home Depot, Lowe's, Kohler) and several D2C brand sites (OXO, Croydex, mDesign, iDesign, Better Living, Umbra): blocked (403) or JS-rendered shells with no server-side content (mDesign bounced to an empty lander; Umbra/Better Living/InterDesign returned pixel-tracker-only bodies to WebFetch). This is exactly the known failure mode from `checks.md` ("retail listings blocked fetches for every author"), so every `source: listing` call in this register is a judgement about what is typically on a title/bullet, not a page I personally confirmed, and I have said so in each `listing_field_hint`/`backfill_note` rather than overclaiming it.
- **Search engines as a fallback**: Bing via WebFetch returned irrelevant unrelated results (my queries matched individual dictionary words instead of the phrase); `html.duckduckgo.com` served a bot CAPTCHA. Neither produced a usable lead.

## Data points used for derived metrics

None. I did not include a `derived_metrics` array. The one candidate I considered — summing per-shelf weight ratings into a "total unit capacity" — assumes brands publish a per-shelf number, which I could not confirm on any reachable spec page, so I dropped it rather than invent the arithmetic. This matches the brief's instruction not to pad a thin register.

## Against the existing add-on schema

`zamana-expertise/data/schema-parts/supplies/household/add-ons/bathroom_storage.jsonc` is currently empty (`{}`, just the category comment). Field-kind vocabulary below follows `zamana-expertise/AGENTS.md`'s four kinds (vertical anchor, category quality currency, hard filter, sizing) and its "dead on sight" list.

| Register facet | Existing field | Proposed kind | Note |
|---|---|---|---|
| `storage_function` | none | vertical anchor for this add-on | New enum, e.g. `item_type` `[towel_bar, towel_ring, towel_hook, shower_caddy, tension_pole_caddy, toilet_paper_holder, toothbrush_or_soap_holder, toilet_brush_holder, over_toilet_shelf_unit, corner_shelf, other]`. Plays the role the materials enum plays for furnishings: the one field every row needs to make sense of the rest. |
| `mounting_method` | `household-standard.jsonc` → `mounting_install` (shared enum) | hard filter | Already exists and is shared across organizers; no new field. Confirms the enum is worth keeping — it is this register's single biggest split. |
| `wet_zone` | none | hard filter | New boolean, bathroom-storage-specific (hangers, laundry baskets etc. have no meaningful "wet zone"), so it belongs in the add-on, not the shared household standard. |
| `material` | `household-standard.jsonc` → `materials` (shared enum) | vertical anchor (reused) | Already exists; no new field. |
| `weight_capacity_lb` | `household-standard.jsonc` → `sizing.weight_capacity {value, unit}` | category quality currency (reused) | Already exists generically; no new field needed, just confirms the category actually uses it. |
| `material_grade` | none | **not a structured field** | Too sparse and too research-heavy to be a dense column (the brief for `supplies-standard.jsonc`'s `high_signal_markdown` exists for exactly this: a line-by-line Jev pass catching a mention like "304 stainless" as a quality claim). Recommend routing to `high_signal_markdown`, not a new schema field. |
| `mount_fit_range` | none | **sizing**, conditionally | If added, belongs inside `sizing` per AGENTS.md rule 2/4 (a bought-by-number compatibility figure, the same logic as "cable length yes, lamp cord length no"), and only for the `item_type` values where it applies (tension, over-door). Lower priority than the others: no sourced range to anchor it yet (see Trimmed detail). |
| `drainage_or_ventilated` | none | **noise — do not add as a text field** | This is a photo-answerable attribute (open wire vs. solid tray is visibly obvious) per AGENTS.md rule 1. Kept in the register because it is real expert reasoning the assistant should use, but the schema should rely on the image pipeline, not a new structured field. |
| `adjustable_fit` | none | **noise — likely derivable, do not add** | If `mount_fit_range` (a min/max span) is ever added, "adjustable" is just "max > min" on that field. Adding a separate boolean would be the "fields derivable from kept fields" case AGENTS.md rule 5 calls dead on sight. |

Since the add-on file started empty, there were no pre-existing fields to flag as noise from the current schema itself; the two noise calls above are pre-emptive, against fields this register itself was tempted to propose.

## Branches

Every leg below is traced to a specific sentence in the register (`category.json`) or above in these notes.

- **`storage_function = towels` → ask `mounting_method`, `weight_capacity_lb`; skip `drainage_or_ventilated`.** The facet's `why_experts_care` says only "A towel bar is judged on span and load" — it does not say "never judged on drainage"; that clause belongs to toilet-paper holders (see the `toilet paper` row below), not towel bars, and an earlier draft of this note spliced the two together with an ellipsis into a sentence that was never written. Dropping `drainage_or_ventilated` here is our own inference, not a quote: a towel bar holds dry or damp folded towels, not standing water, so the facet does not apply; load and mount do, because towels are the heaviest routine load in the category.
- **`storage_function = toiletries` → ask `wet_zone`, `drainage_or_ventilated`, `material`.** The `drainage_or_ventilated` half traces to the facet's `why_experts_care`: "a shower caddy on drainage and shelf count." The `wet_zone` half is our own inference, now stated as such rather than left implicit: toiletry holders (bottles, razors, loofahs) are the subtype most often placed in or near running water. This matches the `wet_zone` facet's own `backfill_note`, which already says the value is "usually inferable from storage_function (toiletries/shower items)" — so the inference is at least consistent with a line already in `category.json`, even though it is not a direct citation.
- **`storage_function = toilet paper` → ask `mounting_method`; skip `wet_zone`, `drainage_or_ventilated`.** Traced to judgement 4: "Toilet paper holders are almost never installed inside the direct wet zone... paper disintegrates on contact with water." If it is never in the wet zone, both the wet-zone question and the drainage question it would otherwise trigger are moot; only how it mounts remains open.
- **`storage_function = open shelving` → ask `mounting_method`, `weight_capacity_lb`, `material_grade`, `wet_zone`.** Traced to the facet's `why_experts_care` ("open shelving on material grade, since it is usually the biggest, most visible, most expensive piece") and to the `material_grade` facet's own backfill note, which limits research to "the top N shelving and towel-bar products." No longer drops `drainage_or_ventilated`: open shelving includes shower shelves (a corner shelf mounted inside the enclosure is a real subtype), so whether drainage matters is decided downstream by `wet_zone`'s own branch, the same way it already is for the `stuck on or suction` mounting branch, rather than ruled out unconditionally here.
- **`mounting_method = drilled into the wall` → ask `weight_capacity_lb`.** Traced to the facet's `why_experts_care`: "The answer sets the ceiling on load rating (screws win)." A real, trustworthy number only exists once drilling is the chosen method; asking it before that is premature.
- **`mounting_method = stuck on or suction` → ask `wet_zone`, `weight_capacity_lb`.** Traced to the `wet_zone` split's rationale ("Suction and adhesive both rely on a seal at the rim; the same part fails faster inside the direct spray") and to judgement 2 (suction needs a smooth, non-porous surface). A no-drill answer makes exactly where it will sit — wet or dry — the next thing that decides whether it will actually hold, which is why `wet_zone` is asked here rather than independently.
- **`mounting_method = hooked over something` → ask `mount_fit_range`.** Traced to the `mount_fit_range` facet's `why_experts_care`: "A tension rod or over-door hook only works across a measured range... the thickness of a door." Hanging-over-something hardware is exactly the subset where a wrong measurement, not a wrong preference, is the failure mode.
- **`mounting_method = freestanding` → skip `mount_fit_range`.** Traced to the same facet's `backfill_note`: "skip this facet entirely for drilled or freestanding items." A freestanding piece is not wedged into any measured gap, so there is no range to fit.
- **`wet_zone = yes` → ask `material`, `material_grade`, `drainage_or_ventilated`.** Traced to the mechanism's closing sentence ("metal parts sit in near-constant moisture, so the chromium and nickel behind stainless matters more than the word itself") together with the `drainage_or_ventilated` facet's `why_experts_care` on standing water and mildew. Being inside the wet zone is precisely the condition that makes material, its real grade, and whether it drains all matter at once.
- **`wet_zone = no` → skip `drainage_or_ventilated`.** Traced to the `drainage_or_ventilated` facet's own definition: it is about letting water run off a wet item. Outside the wet zone there is no standing water to run off, so the question stops applying.

I considered and dropped one leg: `storage_function = toiletries` making `mounting_method` irrelevant, on the theory that most toiletry holders are small and freestanding anyway. I could not trace that to a stated fact — plenty of toiletry holders are wall-mounted (toothbrush cups on an adhesive shelf, soap dishes on a suction mount) — so I kept `mounting_method` live on that branch instead of dropping it.

## Trimmed detail

- The `mount_fit_range` facet has no `range` object in `category.json` because I have no sourced min/max figures for tension-rod span or over-door hook door-thickness (every brand spec page I could reach for a concrete number — OXO, InterDesign, Better Living, Zenna Home, Umbra — returned either a 403, a 404, or a JS shell with no server-rendered product content; `moen.com` loaded but I could not locate its towel-bar installation PDF without a working search engine). The facet exists to flag the *kind* of fit problem; a future pass with working search or direct brand-PDF URLs should add the range rather than me guessing one.
- I considered a fourth split on `material` ("Metal, plastic, wood-look, or woven mesh?") but kept it as a plain facet instead. Run against the generic test, a bare "what material do you want" question would read the same in any household category; what makes it bathroom-storage expertise specifically is the humidity/corrosion consequence, which lives in `why_experts_care` and in the `material_grade` facet, not in the split question itself, so promoting it to a fourth split would have been padding rather than a genuinely different narrowing question.
- I looked for an existing visual grammar from a trade body (ANSI/BHMA has one for door hardware, but nothing bathroom-storage-specific turned up in anything I could reach) and ended up proposing "Mount vs. hold" as a natural chart rather than citing an established one; `origin` is left empty in `category.json` to say so.
- I considered adding a CPSC/ASTM-sourced glass-safety facet for shelf glazing (16 CFR 1201, architectural glazing), since some open shelving uses glass shelves. I dropped it: 16 CFR 1201 governs glazing in architectural openings (doors, windows, shower/tub enclosures), and I could not confirm it extends to a glass shelf resting in a bracket, so adding it would have been exactly the kind of standard-shopping the brief warns against — citing a real regulator for a claim it does not actually make.

## Corrections after the blind check (2026-10-25)

1. **Fabricated quote, `storage_function=towels` branch.** Removed the invented sentence "A towel bar is judged on span and load ... never judged on drainage" from the Branches note above. `category.json`'s actual text is "A towel bar is judged on span and load"; the "never... on moisture" clause belongs to toilet-paper holders. The branch itself (drop `drainage_or_ventilated` for towels) is kept, now labelled plainly as our own inference rather than a quote.
2. **Single-sourced judgement 1.** Fetched 16 CFR 1261.2 myself (curl with a browser user-agent, which the blind check showed gets a 200 where a bare WebFetch/curl call to `ecfr.gov` does not). Added it as reference `cfr_1261_2` and cited it alongside `astm_f2057_scope` on judgement 1, so the claim that no mandatory U.S. stability standard covers bathroom shelving now rests on two independent sources.
3. **Half-invented "18/8 stainless steel" definition.** `wikipedia_stainless_steel` states 18% chromium for 304 but no nickel figure tied to that grade. Fetched the dedicated "SAE 304 stainless steel" Wikipedia page, whose composition table (sourced to ASTM A276/A276M) gives 304 as 18.0–20.0% chromium, 8.0–11.0% nickel. Added as reference `wikipedia_304_stainless`, cited on `material_grade`'s `evidence`. The vocabulary text ("about 18% chromium and 8% nickel") is unchanged because it now matches the low end of a real, two-sourced range.
4. **`drainage_or_ventilated`'s `source` contradicted its own `backfill_note`.** Changed `source` from `"listing"` to `"derived"` (computed from the primary photo, a field already present on the listing, rather than read from text) and folded the old `listing_field_hint` into `backfill_note` so the one surviving field states plainly that this is photo-derived, not text-derived.
5. **`storage_function`'s `source` contradicted its own `backfill_note`.** Changed `source` from `"derived"` to `"listing"` and added a `listing_field_hint` (product title or retailer sub-category), matching the backfill note's description of direct reading, not computation.
6. **`mount_fit_range`'s `backfill_cost` undersold the documented experience.** Raised from `"medium"` to `"high"`; every brand page tried for a concrete tension-rod or over-door range (OXO, InterDesign, Better Living, Zenna Home, Umbra) failed to load, and no range exists anywhere in the register. `backfill_note` now names the five brands tried, matching the "Trimmed detail" section below.
7. **`storage_function=toiletries` branch rested on an unstated assumption.** Made the inference explicit in the Branches note above (toiletry holders are the subtype most often placed near running water) and pointed it at the `wet_zone` facet's own `backfill_note`, which already says the value is "usually inferable from storage_function (toiletries/shower items)" — the assumption is now stated as ours, not implied as a citation.
8. **`storage_function=open shelving`'s `makes_irrelevant: drainage_or_ventilated` was too absolute.** Open shelving includes shower shelves (a corner shelf mounted inside the enclosure), so dropping drainage unconditionally was wrong for a real subtype of the category. Changed the branch in `category.json` to add `wet_zone` to `then_ask` and removed `makes_irrelevant` entirely; whether drainage matters is now decided downstream by `wet_zone`'s own branch, the same pattern already used for the `stuck on or suction` mounting branch.

No findings from the "Required fixes" list were rejected; all eight were applied as described above. The Part B/C observations the check did not list under "Required fixes" (the `toilet paper` value's label collision with the sibling leaf, and the cross-category reuse of the `mounting_method` enum) were left as-is: the check itself grades both as real but minor and not actionable inside this JSON alone.
