[object Object]

← back to Thesetdecorator

TSD a11y/SEO: add sr-only h1 to 11 zero-h1 content pages (DTD verdict B)

91e0945aa1e782273b956134b4f8acf7d90649f8 · 2026-06-03 10:44:22 -0700 · Steve

11 pages (art-prints, best-sellers, catalog, films, print, roster, sdsa,
trades, videos, vintage, wall-treatments) had NO h1 anywhere — their visible
title was a bare h2. Added <h1 class=sr-only> with the page name (from the
first h2, entity-decoded) immediately inside <main>. Guarantees exactly one
h1 per page with ZERO rendered change (visible h2 untouched) — the
autonomous-safe choice (no visual QA available). Verified: 0 zero-h1 pages,
one h1 each, heading order clean, idempotent. Visible-h1 promotion (DTD opt A)
queued for Steve. Idempotent scripts/add-sr-only-h1.py.

Files touched

Diff

commit 91e0945aa1e782273b956134b4f8acf7d90649f8
Author: Steve <steve@designerwallcoverings.com>
Date:   Wed Jun 3 10:44:22 2026 -0700

    TSD a11y/SEO: add sr-only h1 to 11 zero-h1 content pages (DTD verdict B)
    
    11 pages (art-prints, best-sellers, catalog, films, print, roster, sdsa,
    trades, videos, vintage, wall-treatments) had NO h1 anywhere — their visible
    title was a bare h2. Added <h1 class=sr-only> with the page name (from the
    first h2, entity-decoded) immediately inside <main>. Guarantees exactly one
    h1 per page with ZERO rendered change (visible h2 untouched) — the
    autonomous-safe choice (no visual QA available). Verified: 0 zero-h1 pages,
    one h1 each, heading order clean, idempotent. Visible-h1 promotion (DTD opt A)
    queued for Steve. Idempotent scripts/add-sr-only-h1.py.
---
 public/art-prints.html      |  1 +
 public/best-sellers.html    |  1 +
 public/catalog.html         |  1 +
 public/films.html           |  1 +
 public/print.html           |  1 +
 public/roster.html          |  1 +
 public/sdsa.html            |  1 +
 public/trades.html          |  1 +
 public/videos.html          |  1 +
 public/vintage.html         |  1 +
 public/wall-treatments.html |  1 +
 scripts/add-sr-only-h1.py   | 51 +++++++++++++++++++++++++++++++++++++++++++++
 12 files changed, 62 insertions(+)

diff --git a/public/art-prints.html b/public/art-prints.html
index 7c06d62..5afafa7 100644
--- a/public/art-prints.html
+++ b/public/art-prints.html
@@ -66,6 +66,7 @@
   </header>
 
   <main id="main">
+    <h1 class="sr-only">Art Prints for Sale</h1>
     <section class="section">
       <header class="rail-header">
         <h2>Art Prints for Sale</h2>
diff --git a/public/best-sellers.html b/public/best-sellers.html
index ebce612..1a21e4e 100644
--- a/public/best-sellers.html
+++ b/public/best-sellers.html
@@ -70,6 +70,7 @@
   </header>
 
   <main id="main">
+    <h1 class="sr-only">Best Sellers — Production-Vetted Wallcoverings</h1>
     <section class="section">
       <header class="rail-header">
         <h2>Best Sellers — Production-Vetted Wallcoverings</h2>
diff --git a/public/catalog.html b/public/catalog.html
index 1bac8af..00cc682 100644
--- a/public/catalog.html
+++ b/public/catalog.html
@@ -49,6 +49,7 @@
   </header>
 
   <main id="main">
+    <h1 class="sr-only">Cleared Artwork &amp; Designs</h1>
     <section class="section">
       <header class="rail-header">
         <h2>Cleared Artwork &amp; Designs</h2>
diff --git a/public/films.html b/public/films.html
index 2133f9f..eab9a90 100644
--- a/public/films.html
+++ b/public/films.html
@@ -87,6 +87,7 @@
   </header>
 
   <main id="main">
+    <h1 class="sr-only">Film Sources — Documented Pieces &amp; Where They Came From</h1>
     <section class="section">
       <header class="rail-header">
         <h2>Film Sources — Documented Pieces &amp; Where They Came From</h2>
diff --git a/public/print.html b/public/print.html
index 8c1fe6b..adede6f 100644
--- a/public/print.html
+++ b/public/print.html
@@ -49,6 +49,7 @@
   </header>
 
   <main id="main" class="flow">
+    <h1 class="sr-only">Fast Print + Delivery</h1>
     <header class="rail-header"><h2>Fast Print + Delivery</h2></header>
 
     <section class="step" aria-labelledby="step-art">
diff --git a/public/roster.html b/public/roster.html
index 09372e6..6356eb0 100644
--- a/public/roster.html
+++ b/public/roster.html
@@ -52,6 +52,7 @@
   </header>
 
   <main id="main">
+    <h1 class="sr-only">Set Decorator Directory</h1>
     <section class="section">
       <header class="rail-header">
         <h2>Set Decorator Directory</h2>
diff --git a/public/sdsa.html b/public/sdsa.html
index 75f4a52..8f0cbca 100644
--- a/public/sdsa.html
+++ b/public/sdsa.html
@@ -52,6 +52,7 @@
   </header>
 
   <main id="main">
+    <h1 class="sr-only">SDSA Member Directory</h1>
     <section class="section">
       <header class="rail-header">
         <h2>SDSA Member Directory</h2>
diff --git a/public/trades.html b/public/trades.html
index 4a8fd9e..fde943a 100644
--- a/public/trades.html
+++ b/public/trades.html
@@ -46,6 +46,7 @@
   </header>
 
   <main id="main">
+    <h1 class="sr-only">Industry News</h1>
     <section class="section">
       <header class="rail-header">
         <h2>Industry News</h2>
diff --git a/public/videos.html b/public/videos.html
index 9ee4742..c2a14b2 100644
--- a/public/videos.html
+++ b/public/videos.html
@@ -49,6 +49,7 @@
   </header>
 
   <main id="main">
+    <h1 class="sr-only">Interviews &amp; Talks</h1>
     <section class="section">
       <header class="rail-header" style="display:block">
         <h2>Interviews & Talks</h2>
diff --git a/public/vintage.html b/public/vintage.html
index 7ce397a..0f544f1 100644
--- a/public/vintage.html
+++ b/public/vintage.html
@@ -61,6 +61,7 @@
     }
   </style>
   <main id="main">
+    <h1 class="sr-only">Vintage Prop Dig</h1>
     <section class="section">
       <header class="rail-header">
         <h2>Vintage Prop Dig</h2>
diff --git a/public/wall-treatments.html b/public/wall-treatments.html
index 7979470..63176de 100644
--- a/public/wall-treatments.html
+++ b/public/wall-treatments.html
@@ -46,6 +46,7 @@
   </header>
 
   <main id="main">
+    <h1 class="sr-only">Wall Treatments — Ombres</h1>
     <section class="section">
       <header class="rail-header">
         <h2>Wall Treatments — Ombres</h2>
diff --git a/scripts/add-sr-only-h1.py b/scripts/add-sr-only-h1.py
new file mode 100644
index 0000000..e7033e7
--- /dev/null
+++ b/scripts/add-sr-only-h1.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+"""Idempotent: add a visually-hidden <h1> to TSD pages that have ZERO h1 (DTD verdict B).
+
+11 content pages used a bare <h2> as their visible main heading and had no <h1>
+anywhere. A page needs exactly one <h1> for SEO (strongest on-page heading
+signal) and a11y (screen-reader main-heading expectation). Chosen approach (DTD
+verdict B, autonomous context with no visual QA): insert <h1 class="sr-only">
+with the page name immediately inside <main id="main"> — guarantees one DOM h1
+with ZERO rendered-pixel change, leaving the visible h2 untouched. A future
+visible-h1 promotion (DTD option A) is queued for when Steve can QA typography.
+
+Page name = the page's first <h2> text (entity-decoded). Idempotent: skips any
+page that already has an <h1>. Reversible: single line inserted.
+    python3 scripts/add-sr-only-h1.py
+"""
+import os, re, glob, html as htmllib
+
+PUB = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "public")
+
+HAS_H1_RE = re.compile(r'<h1[ >]', re.IGNORECASE)
+MAIN_OPEN_RE = re.compile(r'<main\b[^>]*>[ \t]*\n', re.IGNORECASE)
+FIRST_H2_RE = re.compile(r'<h2[^>]*>(.*?)</h2>', re.IGNORECASE | re.DOTALL)
+
+changed, skipped, noanchor, noh2 = [], [], [], []
+for path in sorted(glob.glob(os.path.join(PUB, "*.html"))):
+    name = os.path.basename(path)
+    src = open(path, encoding="utf-8").read()
+    if HAS_H1_RE.search(src):
+        skipped.append(name); continue
+    m2 = FIRST_H2_RE.search(src)
+    if not m2:
+        noh2.append(name); continue           # no visible title h2 — skip, don't guess
+    # page name from first h2 text: strip any inner tags, decode entities, collapse ws
+    title = re.sub(r'<[^>]+>', '', m2.group(1))
+    title = htmllib.unescape(title).strip()
+    title = re.sub(r'\s+', ' ', title)
+    if not title:
+        noh2.append(name); continue
+    mm = MAIN_OPEN_RE.search(src)
+    if not mm:
+        noanchor.append(name); continue
+    line = f'    <h1 class="sr-only">{htmllib.escape(title)}</h1>\n'
+    out = src[:mm.end()] + line + src[mm.end():]
+    open(path, "w", encoding="utf-8").write(out)
+    changed.append(f"{name} (\"{title}\")")
+
+print(f"changed ({len(changed)}):")
+for c in changed: print(f"  + {c}")
+print(f"skipped/already-have-h1: {len(skipped)}")
+if noh2:     print(f"no-h2-title (skipped, not guessed): {', '.join(noh2)}")
+if noanchor: print(f"no <main> anchor: {', '.join(noanchor)}")

← 43bec6d backlog: loop #6 (noindex internal pages) + consolidate depl  ·  back to Thesetdecorator  ·  backlog: loop #7 (sr-only h1) + queue visible-h1 follow-up + 72384df →