Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions scripts/cmux-next/check-overlay-glass.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,17 @@
swiftui-solid a SwiftUI .background(...) of white, black or a solid palette surface, or
.opacity(1).

Hover cards (a file that hands a body to the app's one hover card window, `HoverCardBody(...)`, or
reports a card's lines, `HoverCardTextReporting`: the tab, link chip, sidebar workspace, group, pull
request and What's New cards). They show in `HoverCardPanel`, the bare themed glass, so a body draws
on that glass and nothing else:
hover-own-window a body that builds its own window or material (NSPanel, NSPopover, NSWindow,
NSVisualEffectView, OverlaySurfaceView, makeOverlayPanel) instead of the one
glass hover card window.
hover-body-fill a body's backgroundColor / fillColor / .background(...) that is not glass-safe:
only Palette.hoverFill, clear, nil, or a color at an alpha under 0.6 (a pill's
tint), so no body lays a solid card over the panel's glass.

A hit passes when the file and rule are in ALLOWED below (with the reason the surface stays solid),
or the line, or the comment block right above it, says `// glass-opaque-allow: <reason>`. Reduce
Transparency's solid paths are the usual reason.
Expand Down Expand Up @@ -54,6 +65,39 @@
),
}

# Hover card bodies (see the docstring), and the files that host them rather than draw one.
HOVER_BODY = re.compile(r"\bHoverCardBody\(|\bHoverCardTextReporting\b")
HOVER_HOSTS = {
SOURCES + "/CmuxNextDesign/HoverCards/HoverCardPanel.swift", # the one glass window itself
SOURCES + "/CmuxNextDesign/HoverCards/HoverCardSource.swift", # defines HoverCardBody
SOURCES + "/CmuxNextDesign/HoverCards/HoverCardRow.swift", # the shared rows and the gallery's glass
}
HOVER_RULES: dict[str, re.Pattern[str]] = {
"hover-own-window": re.compile(
r"\b(NSPanel|NSPopover|NSWindow|NSVisualEffectView|OverlaySurfaceView)\(|\bmakeOverlayPanel\("
),
}
HOVER_FILL = re.compile(r"\b(?:backgroundColor|fillColor)\s*=\s*(?P<rhs>[^\n]+)|\.background\((?P<call>[^\n]*)")
# One alternative of a fill (a ternary's branches are checked one by one), `.cgColor` dropped.
HOVER_SAFE = re.compile(
r"^(Palette\.hoverFill|(NSColor)?\.clear|Color\.clear|nil|[\w.]+\.withAlphaComponent\(0?\.[0-5]\d*\))$"
)


def hover_fill_unsafe(line: str) -> bool:
"""A hover card body's fill that is not glass-safe (`hover-body-fill`)."""
match = HOVER_FILL.search(line)
if not match:
return False
rhs = (match.group("rhs") or match.group("call") or "").strip()
rhs = re.sub(r"\)\s*$", "", rhs) if match.group("call") is not None else rhs
rhs = rhs.replace(".cgColor", "").strip()
if "?" in rhs:
rhs = rhs.split("?", 1)[1]
branches = [part.strip() for part in re.split(r"\s:\s|\?\?", rhs)]
return any(not HOVER_SAFE.match(branch) for branch in branches if branch)


ESCAPE = re.compile(r"//\s*glass-opaque-allow:\s*\S")
COMMENT = re.compile(r"^\s*(//|\*|/\*)")

Expand Down Expand Up @@ -115,10 +159,20 @@ def scan(root: str) -> list[str]:
with open(path, encoding="utf-8", errors="replace") as handle:
lines = handle.read().split("\n")
allowed = ALLOWED.get(rel, {})
hover = rel not in HOVER_HOSTS and any(HOVER_BODY.search(text) for text in lines)
for index, line in enumerate(lines):
if COMMENT.match(line):
continue
code = line.split("//", 1)[0]
if hover:
hits = [rule for rule, pattern in HOVER_RULES.items() if pattern.search(code)]
if hover_fill_unsafe(code):
hits.append("hover-body-fill")
for rule in hits:
if rule in allowed:
used.add((rel, rule))
elif not escaped(lines, index):
findings.append(f"{rel}:{index + 1}: [{rule}] {line.strip()}")
for rule, pattern in RULES.items():
if not pattern.search(code):
continue
Expand Down
26 changes: 26 additions & 0 deletions scripts/cmux-next/tests/check-overlay-glass.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,32 @@ expect_pass comment '/// The popup surface (`Palette.popupSurface`) only under R
expect_pass escaped ' // glass-opaque-allow: Reduce Transparency draws every surface solid.
layer?.backgroundColor = (opaque ? Palette.elevatedBackground : veil).cgColor'

# Hover card bodies (a file with HoverCardBody or HoverCardTextReporting) draw on the one glass
# hover card window: no window or material of their own, and no fill a solid card could hide in.
expect_fail hover-window hover-own-window 'final class DemoCardView: NSView, HoverCardTextReporting {
let popover = NSPopover()
}'
expect_fail hover-surface hover-own-window ' let body = HoverCardBody(view: Glass.makeOverlayPanel(content: card), placement: .beside, themeAnchor: self) {}'
expect_fail hover-fill hover-body-fill 'final class DemoCardView: NSView, HoverCardTextReporting {
func applyColors() { layer?.backgroundColor = Palette.background.cgColor }
}'
expect_fail hover-ternary hover-body-fill 'final class DemoCardView: NSView, HoverCardTextReporting {
func applyColors() { layer?.backgroundColor = hovered ? Palette.hoverFill.cgColor : Palette.textPrimary.cgColor }
}'
expect_pass hover-glass 'final class DemoCardView: NSView, HoverCardTextReporting {
func applyColors() {
thumbnail.layer?.backgroundColor = Palette.hoverFill.cgColor
layer?.backgroundColor = hovered ? Palette.hoverFill.cgColor : NSColor.clear.cgColor
pill.layer?.backgroundColor = color.withAlphaComponent(0.14).cgColor
}
}'
expect_pass hover-escaped 'final class DemoCardView: NSView, HoverCardTextReporting {
// glass-opaque-allow: a status dot, not a card fill.
func applyColors() { dot.layer?.backgroundColor = tone.cgColor }
}'
# Only hover card bodies: the same fill elsewhere is not this rule'"'"'s business.
expect_pass not-hover ' badge.layer?.backgroundColor = Palette.accent.cgColor'

# A listed file that no longer paints the solid fill is a stale allowance.
stale="$tmp/Packages/macOS/CmuxNext/Sources/CmuxNextTerminal"
mkdir -p "$stale"
Expand Down