Skip to main content

max / balanced_breakfast

4.6 KB · 102 lines History Blame Raw
1 #!/bin/bash
2 # Frontend design-system lint guards.
3 # See _private/docs/balanced_breakfast/design-system.md "Inline-style rules"
4 # and "Cross-cutting rules" for the source of truth.
5 # Exit 0 = clean. Exit non-zero = violations found (printed with file:line).
6
7 set -u
8 ROOT="$(cd "$(dirname "$0")/.." && pwd)"
9 FRONTEND="$ROOT/src-tauri/frontend"
10 SRC_JS="$FRONTEND/js"
11 SRC_HTML="$FRONTEND/index.html"
12 SRC_CSS="$FRONTEND/css/styles.css"
13
14 violations=0
15
16 report() {
17 local rule="$1"; shift
18 local msg="$1"; shift
19 if [ -n "$*" ]; then
20 echo
21 echo "[$rule] $msg"
22 echo "$*"
23 violations=$((violations + 1))
24 fi
25 }
26
27 # 1. no-raw-hex
28 # No raw hex literals in JS or source HTML. HTML entities (&#NNNN;) and the
29 # themes.js theme engine are exempt — themes.js is the only place that
30 # builds CSS values via string concatenation, by charter.
31 hits=$(grep -rnE '#[0-9a-fA-F]{3,8}\b' "$SRC_JS" "$SRC_HTML" 2>/dev/null \
32 | grep -vE '&#[0-9]+;' \
33 | grep -v 'js/themes.js' \
34 | grep -v 'js/tests/' \
35 || true)
36 report "no-raw-hex" "Raw hex literal in JS/HTML — use a CSS class or themed token." "$hits"
37
38 # 2. no-csstext
39 # style.cssText injection is forbidden — it usually means color/border/font
40 # values are being set from JS and the result is unthemeable.
41 hits=$(grep -rn 'cssText' "$SRC_JS" 2>/dev/null | grep -v 'js/tests/' || true)
42 report "no-csstext" "style.cssText injection — move styles into a CSS class." "$hits"
43
44 # 3. no-var-fallback-hex
45 # No var(--token, #fallback). Fallback hex bypasses the theme contract.
46 hits=$(grep -rnE 'var\(--[a-z-]+,\s*#' "$FRONTEND" \
47 --include='*.js' --include='*.html' --include='styles.css' 2>/dev/null || true)
48 report "no-var-fallback-hex" "var(--token, #fallback) — drop the fallback; it bypasses themes." "$hits"
49
50 # 4. no-styled-attrs
51 # No inline style="..." that touches color / background / border / shadow /
52 # font / padding values. Layout-only (display / gap / flex / margin) is
53 # tolerated; the goal is zero on the color and typography axes.
54 hits=$(grep -rnE 'style="[^"]*(color|background|border|shadow|font-size|font-family|padding)' \
55 "$SRC_JS" "$SRC_HTML" 2>/dev/null || true)
56 report "no-styled-attrs" "Inline style= with color/background/border/shadow/font/padding — use a class." "$hits"
57
58 # 5. no-style-color-from-js
59 # No .style.<color-axis-property> assignments. Dynamic positioning
60 # (.style.left / .top / .width / .transform) and display toggles are fine;
61 # color, background, border, font, padding values must come from CSS.
62 hits=$(grep -rnE '\.style\.(color|background|backgroundColor|borderColor|font|fontFamily|fontSize|paddingTop|paddingBottom|paddingLeft|paddingRight|margin|marginTop|marginBottom|marginLeft|marginRight|gap|opacity)\b' \
63 "$SRC_JS" 2>/dev/null | grep -v 'js/tests/' || true)
64 report "no-style-color-from-js" "JS-set color/background/border/font/margin/padding/gap/opacity — use a class." "$hits"
65
66 # 6. no-native-dialogs
67 # window.confirm / window.prompt / window.alert are banned — they're
68 # unstyled on every platform and disabled in iOS WKWebView. Use the
69 # BB.ui.show{Confirm,Prompt}Dialog / BB.ui.showToast helpers instead.
70 hits=$(grep -rnE '\b(window\.)?(confirm|prompt|alert)\s*\(' "$SRC_JS" 2>/dev/null \
71 | grep -vE 'showConfirmDialog|showPromptDialog|confirmAction|confirmDelete|confirmBtn|\.confirm-message|/\*|\*\s|// ' \
72 | grep -v 'js/tests/' || true)
73 report "no-native-dialogs" "window.confirm/prompt/alert are banned — use BB.ui.show{Confirm,Prompt}Dialog or showToast." "$hits"
74
75 # 7. theme-token-coverage
76 # Every :root token in styles.css must either be mapped by themes.js
77 # COLOR_MAP, derived in applyTheme, or carry an /* invariant */ or
78 # /* composition */ annotation on the same line. Catches dangling
79 # tokens that silently fall back to the hardcoded :root value.
80 root_tokens=$(awk '/^:root \{/{flag=1; next} /^\}/{flag=0} flag' "$SRC_CSS" \
81 | grep -oE -- '--[a-z-]+' | sort -u || true)
82 unmapped=""
83 for tok in $root_tokens; do
84 # Allow tokens marked invariant or composition on the declaration line.
85 if grep -E -- "$tok:[^;]*;.*(invariant|composition)" "$SRC_CSS" >/dev/null 2>&1; then continue; fi
86 # Mapped directly or referenced as a property in themes.js?
87 if grep -F -- "'$tok'" "$SRC_JS/themes.js" >/dev/null 2>&1; then continue; fi
88 unmapped="$unmapped$tok"$'\n'
89 done
90 if [ -n "$unmapped" ]; then
91 report "theme-token-coverage" "Token in :root is neither themed nor marked invariant/composition." "$unmapped"
92 fi
93
94 if [ $violations -eq 0 ]; then
95 echo "frontend lint: clean"
96 exit 0
97 else
98 echo
99 echo "frontend lint: $violations rule(s) failed"
100 exit 1
101 fi
102