Files
Project-SDE-WP-Suite/tests/icon_check.py
n.siegfried b83f2fd8d5 T9.3 - S6: one icon system - monochrome text glyphs, one meaning each
The set mixed colour emoji with dingbats, and the same glyph read as two
things partly BECAUSE emoji render as per-platform artwork. The system chosen:
monochrome text-presentation glyphs - the suite is classic-script vanilla HTML
with no bundler, so an SVG sprite or icon font is a new asset pipeline, while
text glyphs render through the same font stack as the words beside them. The
enforceable form of "renders identically on Windows, macOS and a tablet":
no emoji-range codepoint and no U+FE0F selector anywhere in UI source,
swept by the probe on every run.

Converted: green-check/red-cross emoji in the admin and users consoles to
checkmark/cross, no-entry to circled-slash (blocked/on hold), the lock to the
pencil already meaning "edit with a logged reason" on sign-offs, the star to
the diamond, the folder to the reference marker, the side nav's lightning to
the gear, and the WATCH glyph (U+231A - emoji-presentation BY DEFAULT per
Unicode) to a text-presentation clock face. Dropped where the label already
carried the meaning: lightning on Save & view, the camera on Add photo, the
page/frame pictograms on file rows (the filename is the label). Stale help
copy fixed while its emoji left: it still described the pre-T9.4 "Load
sample" and the pre-T7.10 "Usage Logs" locations.

The meaning-to-icon mapping is in docs/reference/tokens.md - one meaning per
glyph, one glyph per meaning, both directions asserted from the document
itself; the probe also sweeps every page for glyphs not in the approved set,
so an unmapped icon cannot creep in.

Verification (each probe run alone): NEW tests/icon_check.py 5/5.
Regressions: frame_check 38/38, files_check 36/36, a11y_check 22/22,
cards_check 44/44.

Items: S6

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-19 13:15:59 -07:00

92 lines
3.9 KiB
Python

#!/usr/bin/env python3
"""Is there one icon system, with one meaning per glyph? — S6, T9.3.
The set mixed emoji and dingbats, and at least one glyph carried two meanings.
Now: monochrome text-presentation glyphs only, mapped one-to-one in
docs/reference/tokens.md. Emoji render as per-platform colour artwork - which
is WHY the same glyph read as two things - so the enforceable form of "renders
identically on Windows, macOS and a tablet" is: no emoji-range codepoint and
no U+FE0F emoji-presentation selector anywhere in the UI source.
Static sweep - no browser needed. Exit 0 all passed, 1 a failure.
"""
import io
import os
import re
import sys
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from browser_check import chk, _PASS, _FAIL # noqa: E402
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
HTML = os.path.join(ROOT, "html")
# The approved system, verbatim from docs/reference/tokens.md.
APPROVED = {
0x2713, 0x2715, 0x26A0, 0x2298, 0x270E, 0x21BA, 0x21BB, 0x2699,
0x2913, 0x2912, 0x25C6, 0x25B8, 0x24D8, 0x2190, 0x2039, 0x203A,
0x2192, 0x2193, 0x25D4, 0x25A4, 0x25A6, 0x25A7, 0x283F, 0x25BE,
0x2248, 0x2398, 0x29C9, 0x2022, 0x00B7, 0x2298,
0x2302, 0x2315, 0x2399, 0x23FB, 0x25B2, 0x25BC, 0x25C9, 0x25F7,
0x2630, 0x263A, 0x2692, 0x26BF,
}
# Emoji territory: anything here in UI source is a system violation.
def is_emoji(o):
return (0x1F000 <= o <= 0x1FAFF) or o in (0x2705, 0x274C, 0x26D4, 0x2B50,
0x26A1, 0xFE0F, 0x2757, 0x2B55)
def main():
print("\n1. no emoji, anywhere in the UI")
offenders = []
glyphs_seen = set()
for name in sorted(os.listdir(HTML)):
if not name.endswith((".html", ".js")):
continue
src = io.open(os.path.join(HTML, name), encoding="utf-8").read()
for ch in set(src):
o = ord(ch)
if is_emoji(o):
offenders.append((name, "U+%04X" % o))
if o > 0x2000 and o not in (0x2013, 0x2014, 0x2018, 0x2019,
0x201C, 0x201D, 0x2026):
glyphs_seen.add(o)
chk("no emoji-range codepoint and no U+FE0F selector survives in any page",
not offenders, offenders[:8])
print("\n2. the mapping document")
tokens = io.open(os.path.join(ROOT, "docs", "reference", "tokens.md"),
encoding="utf-8").read()
chk("the meaning-to-icon mapping is documented in tokens.md",
"## Icons (S6 / T9.3)" in tokens and "U+2713" in tokens and "U+2298" in tokens)
body = tokens[tokens.find("## Icons"):]
rows = re.findall(r"^\| ([^|]+) \| [^|]+ \| (U\+[0-9A-F]{4}[^|]*) \|", body, re.M)
meanings = [r[0].strip() for r in rows]
chk("no meaning appears twice in the mapping", len(meanings) == len(set(meanings)),
[m for m in meanings if meanings.count(m) > 1])
codes = []
for _, cp in rows:
codes.extend(re.findall(r"U\+([0-9A-F]{4})", cp))
chk("no glyph carries two meanings", len(codes) == len(set(codes)),
[c for c in codes if codes.count(c) > 1])
print("\n3. what the pages use is what the document names")
# Box-drawing comment art (U+2500-257F) and the A7 locale samples in
# wp-format.js are not icons; everything else above U+2200 must be mapped.
unmapped = sorted("U+%04X" % o for o in glyphs_seen
if o >= 0x2200 and o not in APPROVED
and not (0x2500 <= o <= 0x257F)
and not (0x4E00 <= o <= 0xD7FF)) # CJK/Hangul: A7 locale data
chk("every glyph in use above the punctuation range is in the approved set",
not unmapped, unmapped[:10])
print("\n" + "-" * 54)
print("%d/%d checks passed." % (len(_PASS), len(_PASS) + len(_FAIL)))
for f in _FAIL:
print(" - " + f)
return 1 if _FAIL else 0
if __name__ == "__main__":
sys.exit(main())