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>
92 lines
3.9 KiB
Python
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())
|