diff --git a/controller/internal/web/settings_split_test.go b/controller/internal/web/settings_split_test.go index fad2460..d2d3bd0 100644 --- a/controller/internal/web/settings_split_test.go +++ b/controller/internal/web/settings_split_test.go @@ -252,3 +252,41 @@ func TestStorageAgentDownNote(t *testing.T) { t.Error("enrichment JS lacks the warn-note error path") } } + +// TestNoEmojiInTemplates (D1 §10, Group G): no emoji/pictographs in web templates. Go-side +// codepoint scan (the D0 grep-based gate false-negatived multibyte emoji on Windows). +func TestNoEmojiInTemplates(t *testing.T) { + allow := map[rune]bool{} + for _, r := range "✓✗✔✘•●○■▶" { + allow[r] = true + } + isEmoji := func(r rune) bool { + if allow[r] { + return false + } + switch { + case r >= 0x1F300 && r <= 0x1FAFF, + r >= 0x2600 && r <= 0x26FF, + r >= 0x2700 && r <= 0x27BF, + r >= 0xFE00 && r <= 0xFE0F, + r >= 0x1F1E6 && r <= 0x1F1FF: + return true + } + return false + } + entries, _ := templateFS.ReadDir("templates") + for _, e := range entries { + if !strings.HasSuffix(e.Name(), ".html") { + continue + } + b, err := templateFS.ReadFile("templates/" + e.Name()) + if err != nil { + t.Fatal(err) + } + for _, r := range string(b) { + if isEmoji(r) { + t.Errorf("%s contains emoji %q (U+%04X)", e.Name(), r, r) + } + } + } +} diff --git a/controller/internal/web/templates/backups.html b/controller/internal/web/templates/backups.html index 7734cb2..e915d0f 100644 --- a/controller/internal/web/templates/backups.html +++ b/controller/internal/web/templates/backups.html @@ -434,7 +434,7 @@ {{end}} {{if .Tier2SizeHuman}}{{.Tier2SizeHuman}}{{end}} {{.BackupContents}} - 📁 +
Beállítás
@@ -743,7 +743,7 @@ function onRestoreAppChange() { var hasVolumes = opt.getAttribute('data-has-volumes') === 'true'; if (hasHDD || hasVolumes) { - typeInfo.innerHTML = '🔄 Teljes visszaállítás: adatbázis + konfiguráció + felhasználói adatok a kiválasztott pillanatképből.'; + typeInfo.innerHTML = 'Teljes visszaállítás: adatbázis + konfiguráció + felhasználói adatok a kiválasztott pillanatképből.'; typeInfo.className = 'restore-info'; } else if (hasDB) { typeInfo.innerHTML = 'Adatbázis és konfiguráció visszaállítása — az alkalmazásnak nincs külön felhasználói adata.'; diff --git a/controller/internal/web/templates/debug.html b/controller/internal/web/templates/debug.html index ac146eb..986a493 100644 --- a/controller/internal/web/templates/debug.html +++ b/controller/internal/web/templates/debug.html @@ -362,7 +362,7 @@ function renderDiagnostic(d) { html += '

Ütemező

'; d.scheduler.forEach(function(j) { var type = j.type === 'daily' ? j.schedule : (j.interval || '-'); - html += ''; + html += ''; }); html += '
NévTípusUtolsó futásFut
' + j.name + '' + type + '' + (j.last_run ? fmtTime(j.last_run) : '-') + '' + (j.running ? '🔄' : '-') + '
' + j.name + '' + type + '' + (j.last_run ? fmtTime(j.last_run) : '-') + '' + (j.running ? 'fut' : '-') + '
'; } @@ -835,7 +835,7 @@ function renderAppBundles(bundles) { html += '' + (b.exported_at || '-') + ''; html += '' + (b.size_human || '-') + ''; html += '' + escapeHtml(b.drive_label || b.drive_path) + ''; - html += '' + (b.encrypted ? '🔒' : '-') + ''; + html += '' + (b.encrypted ? 'titkosított' : '-') + ''; html += '' + (b.has_db ? 'igen' : '-') + ''; html += '' + (b.needs_hdd ? 'igen' : '-') + ''; html += '' + escapeHtml(b.path) + ''; diff --git a/controller/internal/web/templates/deploy.html b/controller/internal/web/templates/deploy.html index 7ce2ff3..1c26ad0 100644 --- a/controller/internal/web/templates/deploy.html +++ b/controller/internal/web/templates/deploy.html @@ -69,7 +69,7 @@ {{if .OtherStoragePaths}} - 📦 Mozgatás másik tárolóra + Mozgatás másik tárolóra {{end}} @@ -565,7 +565,7 @@ {{range $.StoragePaths}} {{end}} diff --git a/controller/internal/web/templates/storage.html b/controller/internal/web/templates/storage.html index 60f810d..8f2b6b1 100644 --- a/controller/internal/web/templates/storage.html +++ b/controller/internal/web/templates/storage.html @@ -102,7 +102,7 @@
{{.Name}} {{if .SizeHuman}}{{.SizeHuman}}{{end}} - 📦 Mozgatás + Mozgatás
{{end}} @@ -151,7 +151,7 @@ {{$src := .Path}} diff --git a/controller/scripts/emoji_gate.py b/controller/scripts/emoji_gate.py new file mode 100644 index 0000000..2a5a937 --- /dev/null +++ b/controller/scripts/emoji_gate.py @@ -0,0 +1,69 @@ +# -*- coding: utf-8 -*- +"""D1 §10 Python emoji gate — Windows grep silently fails to match multibyte emoji (the D0 +grep-gate zero was a false negative). This scans templates by Unicode codepoint. + +Run from controller/: python scripts/emoji_gate.py +Exit 1 if any emoji/pictograph remains in web+setup templates. +""" +import io, os, sys, unicodedata + +ROOTS = [ + os.path.join("internal", "web", "templates"), + os.path.join("internal", "setup", "templates"), +] + +# Codepoint ranges that count as "emoji / pictographs / dingbats" for UI-copy purposes. +# Deliberately does NOT flag Hungarian letters, typographic quotes/dashes, the middle dot (·), +# arrows used as affordances (→ ↗ ↻ ↑ ↓), the multiplication sign (×), or box-drawing. +def is_emoji(ch): + o = ord(ch) + ranges = [ + (0x1F300, 0x1FAFF), # Misc symbols & pictographs, emoticons, transport, supplemental, symbols-ext + (0x2600, 0x26FF), # Misc symbols (☀ ⚙ ⚠ ☁ …) + (0x2700, 0x27BF), # Dingbats (✅ ✂ ✈ ✏ ✓? no — see allow) + (0x1F000, 0x1F0FF), # Mahjong/dominoes/cards + (0xFE00, 0xFE0F), # Variation selectors (emoji presentation) + (0x1F1E6, 0x1F1FF), # Regional indicators + ] + if any(a <= o <= b for a, b in ranges): + return True + return False + +# Dingbat codepoints that are legitimate UI glyphs (checkmarks/crosses used as plain text marks, +# not emoji). We keep these OUT of the ban — they render as monochrome text, not color emoji. +ALLOW = set("✓✗✔✘•●○■▶") # ✓ ✗ ✔ ✘ • ● ○ ■ ▶ + + +def scan(path): + hits = [] + for lineno, line in enumerate(io.open(path, encoding="utf-8"), 1): + for ch in line: + if ch in ALLOW: + continue + if is_emoji(ch): + try: + name = unicodedata.name(ch) + except ValueError: + name = "U+%04X" % ord(ch) + hits.append((lineno, ch, name)) + return hits + + +def main(): + total = 0 + for root in ROOTS: + for fn in sorted(os.listdir(root)): + if not fn.endswith(".html"): + continue + path = os.path.join(root, fn) + for lineno, ch, name in scan(path): + total += 1 + print("%s:%d %s %s" % (fn, lineno, ch, name)) + if total: + print("EMOJI GATE FAILED: %d emoji found" % total) + sys.exit(1) + print("emoji gate OK — no emoji in web/setup templates") + + +if __name__ == "__main__": + main()