- Registration is currently
-
- {{ registrationOpen ? "open" : "closed" }}
-
-
-
- When closed, new users can only be added by an administrator.
-
-
-
-
-
-
-
-
Invite User
-
-
Send an invitation link to allow someone to register, even when public registration is closed.
-
-
-
Pending Invitations
-
-
-
-
Email
-
Sent
-
Expires
-
Actions
-
-
-
-
-
{{ inv.email }}
-
{{ fmtDate(inv.created_at) }}
-
{{ fmtDate(inv.expires_at) }}
-
-
-
-
-
-
-
-
-
-
-
Users
-
-
Loading users...
-
-
No users found.
-
-
-
-
-
Username
-
Email
-
Role
-
Joined
-
Actions
-
-
-
-
-
{{ u.username }}
-
{{ u.email || "—" }}
-
-
- {{ u.role }}
-
-
-
{{ fmtDate(u.created_at) }}
-
-
- You
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/scribe/services/coverage.py b/src/scribe/services/coverage.py
index 6dfd381..eabda24 100644
--- a/src/scribe/services/coverage.py
+++ b/src/scribe/services/coverage.py
@@ -156,6 +156,12 @@ def _block_sha(lines: list[str]) -> str:
return hashlib.sha1("\n".join(kept).encode("utf-8")).hexdigest()[:16]
+def _declaration_count(lines: list[str]) -> int:
+ """How many `prop: value` declarations a CSS block body carries."""
+ body = " ".join(lines)
+ return sum(1 for part in body.replace("}", "").split(";") if ":" in part)
+
+
def extract_definitions(text: str) -> list[Definition]:
"""Every definition this text makes, with signature + fingerprint.
@@ -202,6 +208,15 @@ def extract_definitions(text: str) -> list[Definition]:
hashed = head + block[1:]
if not any(x.strip() for x in hashed):
hashed = block
+ # A SINGLE declaration is not a shape (#2903): `color: var(--fs-
+ # text-tertiary)` under .text-muted, .task-mark and .pin-badge-auto
+ # is three meanings sharing one line, not three copies of one
+ # rule — the first pay-down found 5-file "families" of exactly
+ # this and nobody would consolidate them. Keep the selector in the
+ # hash for one-liners, so they group only with same-name copies;
+ # two declarations and up stay selector-agnostic.
+ elif _declaration_count(hashed) < 2:
+ hashed = block
else:
hashed = block
out.append(Definition(
diff --git a/tests/test_pattern_coverage.py b/tests/test_pattern_coverage.py
index ab70205..c2a6ae4 100644
--- a/tests/test_pattern_coverage.py
+++ b/tests/test_pattern_coverage.py
@@ -484,12 +484,18 @@ def test_extract_definitions_fingerprints_each_block():
d = {x.name: x for x in extract_definitions(css)}
assert d["closed-msg"].body_sha == d["error-block"].body_sha != d["other"].body_sha
# One-line rules hash their own declarations — never the empty string
- # (first deploy grouped 68 unrelated one-liners as one copy).
- one = ".a { color: red; }\n\n.b { color: red; }\n\n.c { color: blue; }\n\n.d {\n color: red;\n}\n"
+ # (first deploy grouped 68 unrelated one-liners as one copy) — and a
+ # SINGLE declaration is not a shape (#2903): it keeps its selector in the
+ # hash, so `.a { color: red }` groups only with another `.a`, never with
+ # `.b { color: red }`. Two declarations and up stay selector-agnostic.
+ one = ".a { color: red; }\n\n.b { color: red; }\n\n.c { color: blue; }\n\n.a {\n color: red;\n}\n"
e = {x.name: x for x in extract_definitions(one)}
import hashlib
- assert e["a"].body_sha == e["b"].body_sha != e["c"].body_sha
+ assert e["a"].body_sha != e["b"].body_sha != e["c"].body_sha
assert e["a"].body_sha != hashlib.sha1(b"").hexdigest()[:16]
+ two = ".a { color: red; margin: 0; }\n.b {\n color: red;\n margin: 0;\n}\n"
+ f = {x.name: x for x in extract_definitions(two)}
+ assert f["a"].body_sha == f["b"].body_sha
def test_coverage_line_names_the_proposers_standing():