Skip to content

Mechanize UI color token enforcement check - #614

Open
ArthurArthurArthur0817 wants to merge 2 commits into
sysprog21:mainfrom
ArthurArthurArthur0817:check-css-tokens
Open

ArthurArthurArthur0817 wants to merge 2 commits into
sysprog21:mainfrom
ArthurArthurArthur0817:check-css-tokens

Conversation

@ArthurArthurArthur0817

@ArthurArthurArthur0817 ArthurArthurArthur0817 commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Problem and behavior

UI colors must be defined as design tokens in :root to ensure consistency across light and dark themes. However, this rule was not mechanized, allowing hardcoded color literals (Hex/RGB/HSL) to slip into web/styles.css, web/task.css, and web/recording/recording.css.

This PR converts all existing out-of-scope literals into semantic variables within :root and adds a strict mechanical checker (scripts/check-css-tokens.py) to prevent future violations.

Notes

The checker script parses scopes like [data-theme="light"] and @media (prefers-color-scheme: light) identically to :root, recognizing them as valid token definition blocks where color literals are permitted. Standard CSS keywords (like black) and var(...) usages are ignored.

Instead of creating an allowlist for the existing technical debt, all 17 prior violations were resolved in the same change. Literals like #d7f7e5 on .agent-pill.ready, #1e1e1e on .editor-panel, and hardcoded mask or box-shadow values were extracted into new semantic tokens. This allows the checker to operate with a strict zero-tolerance baseline.

Testing

tests/scripts/test_check_css_tokens.py adds coverage for valid token definitions, permitted comments, and exact interception of prohibited literal formats. Integrated the checker into scripts/test.sh as css_tokens_gate. The tests fail if a hardcoded literal is introduced outside a definition block. ./scripts/test.sh passes; cargo-audit, cargo-deny, actionlint, and GitHub CLI-dependent tests were skipped locally or deferred to CI.

Close #578


Summary by cubic

Mechanizes UI color definitions so hardcoded Hex/RGB/HSL literals can't reappear in the CSS files under web/.

Adds scripts/check-css-tokens.py with unit tests in tests/test_check_css_tokens.py, wired into scripts/test.sh as css-tokens-tests and css-tokens gates. Replaces existing literals in web/styles.css, web/task.css, and web/recording/recording.css with semantic tokens in :root, except two .turn-ring mask gradients in web/styles.css that now use the black keyword. Also swaps the cube's hardcoded #11110f stroke and fill in web/cube-view.js for var(--cube-ink).

Closes #578.

Written for commit e8e68bd. Summary will update on new commits.

View guided diff

cubic-dev-ai[bot]

This comment was marked as resolved.

@ArthurArthurArthur0817
ArthurArthurArthur0817 force-pushed the check-css-tokens branch 2 times, most recently from 5b0898d to 64cdc76 Compare November 7, 2026 11:00
jserv

This comment was marked as outdated.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 6 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/test_check_css_tokens.py">

<violation number="1" location="tests/test_check_css_tokens.py:29">
P1: `test_parser_regressions` always fails because the checker reports two literals in this fixture while the assertion expects zero. Make the fixture use non-literal values (or update the expected count if these are intended violations) so the required `css-tokens-tests` gate can pass.</violation>
</file>

<file name="scripts/check-css-tokens.py">

<violation number="1" location="scripts/check-css-tokens.py:1">
P3: Several added lines contain trailing spaces, which makes the patch fail `git diff --check` and the repository’s formatting gate. Remove the trailing whitespace from the new script.</violation>
</file>

Requires human review: Auto-approval blocked because this review re-detected 1 unresolved issue already reported by Cubic.
Tip: Review your code locally with the cubic CLI to iterate faster.

View guided diff | Re-trigger cubic

}
.card { color: var(--brand-violet); }
""", encoding="utf-8")
self.assertEqual(check_css_tokens.check_css_file(self.test_file), 0)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: test_parser_regressions always fails because the checker reports two literals in this fixture while the assertion expects zero. Make the fixture use non-literal values (or update the expected count if these are intended violations) so the required css-tokens-tests gate can pass.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At tests/test_check_css_tokens.py, line 29:

<comment>`test_parser_regressions` always fails because the checker reports two literals in this fixture while the assertion expects zero. Make the fixture use non-literal values (or update the expected count if these are intended violations) so the required `css-tokens-tests` gate can pass.</comment>

<file context>
@@ -0,0 +1,70 @@
+            }
+            .card { color: var(--brand-violet); }
+        """, encoding="utf-8")
+        self.assertEqual(check_css_tokens.check_css_file(self.test_file), 0)
+
+    def test_invalid_standard_properties(self):
</file context>

Comment thread scripts/check-css-tokens.py Resolved
Comment thread web/styles.css Resolved
@@ -0,0 +1,45 @@
#!/usr/bin/env python3

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: Several added lines contain trailing spaces, which makes the patch fail git diff --check and the repository’s formatting gate. Remove the trailing whitespace from the new script.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. When an issue isn't valid or won't be fixed in this PR, reply in its thread with the reason and then resolve the thread. At scripts/check-css-tokens.py:

<comment>Several added lines contain trailing spaces, which makes the patch fail `git diff --check` and the repository’s formatting gate. Remove the trailing whitespace from the new script.</comment>

<file context>
@@ -0,0 +1,45 @@
+#!/usr/bin/env python3
+"""Enforces that CSS color literals are only used as custom property values."""
+
+import sys
+import re
+from pathlib import Path
+
+COMMENT_RE = re.compile(r'/\*.*?\*/', re.DOTALL)
+
</file context>

Comment thread scripts/test.sh
gate audit-tests unittest_gate "$ROOT/tests/test_audit.py"
gate rust-style-tests unittest_gate "$ROOT/tests/test_check_rust_style.py"
gate css-tokens-tests unittest_gate "$ROOT/tests/test_check_css_tokens.py"
gate css-tokens "$PYTHON" "$ROOT/scripts/check-css-tokens.py"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gate fails at this head on the PR's own web/styles.css: the checker reports @media (prefers-color-scheme: light) and @media (prefers-color-scheme: dark) as violations. DECLARATION_RE in scripts/check-css-tokens.py reads prefers-color-scheme: light) { .editor-stack:not([data-theme="dark"]) { --code-bg: #ffffff as one declaration named prefers-color-scheme, so the first token in each block is flagged, and a selector with a pseudo-class does the same (.card:hover { --card-color: #fff; } is reported). Match declarations only inside a rule's braces, after its selector or at-rule prelude, and add both shapes to tests/test_check_css_tokens.py.

Comment thread scripts/test.sh Resolved
jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as outdated.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

jserv

This comment was marked as resolved.

Extracted hardcoded color literals into semantic custom properties
inside the :root scope to ensure consistency and prepare for strict
token enforcement.

Change-Id: If3c8d4de6dd43e23ef2888c86de2f323e54f3b8e
jserv

This comment was marked as resolved.

@ArthurArthurArthur0817
ArthurArthurArthur0817 force-pushed the check-css-tokens branch 2 times, most recently from 6bffcf3 to 28b9d54 Compare November 9, 2026 06:51
jserv

This comment was marked as outdated.

Comment thread scripts/test.sh
gate audit-tests unittest_gate "$ROOT/tests/test_audit.py"
gate rust-style-tests unittest_gate "$ROOT/tests/test_check_rust_style.py"
gate css-tokens-tests unittest_gate "$ROOT/tests/test_check_css_tokens.py"
gate css-tokens "$PYTHON" "$ROOT/scripts/check-css-tokens.py"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gate fails only through main() calling sys.exit(1), and no test drives main(): replacing that sys.exit(1) with pass leaves all six cases in tests/test_check_css_tokens.py passing, and the gate still exits 0 on today's stylesheets, so a regression there would turn the gate into one that can never fail. Add a case that points the scan at a temporary tree (for example by patching check_css_tokens.ROOT to a directory holding web/sub/x.css with color: #f00;) and asserts SystemExit with code 1, which also pins that web/ is walked recursively.

jserv

This comment was marked as outdated.

jserv

This comment was marked as outdated.

jserv

This comment was marked as outdated.

Added a strict mechanical checker that scans CSS files for hardcoded
color literals. Excludes custom property definitions and is integrated
into test.sh alongside unit tests.

Change-Id: I0bd897637b2ff51231646c5df6acfb107f315354
# Match hardcoded color literals: #hex, or functions
COLOR_RE = re.compile(
r"(#[0-9a-fA-F]{3,8}\b|"
r"\b(?:rgb|rgba|hsl|hsla|oklch|oklab|lab|lch|hwb|color)\([^)]+\))",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Named colors pass this pattern, so the three color: white rules on .pill-button.danger, .run-button and .final-code button in web/styles.css stay hardcoded, even though --on-accent exists for exactly that text (white on --accent-fill or --danger). The .turn-ring mask in this PR also passes only because #000 became black. Switch those three rules to var(--on-accent) here. As a follow-up, add the named colors (keeping transparent and currentColor allowed) to COLOR_RE and exempt mask and -webkit-mask, where only alpha matters.

Comment on lines +35 to +37
text = COMMENT_RE.sub(
lambda m: "\n" * m.group().count("\n"), path.read_text(encoding="utf-8")
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strings and url() are only removed per declaration, after DECLARATION_RE has already split at ; and }. content: "a; b: #fff"; therefore reports a violation on a property named b, and so would any url("data:image/svg+xml;utf8,...") carrying a fill='#fff'. QUOTED_RE also stops at an escaped quote ("\"#fff") and misses URL(. Blank them across the whole text before splitting, keeping newlines so the line count holds, then call COLOR_RE.search(val) directly, make QUOTED_RE r'"(?:\\.|[^"\\])*"|\'(?:\\.|[^\'\\])*\'|url\([^)]*\)' with re.IGNORECASE, and add a test with a ; inside content.

Suggested change
text = COMMENT_RE.sub(
lambda m: "\n" * m.group().count("\n"), path.read_text(encoding="utf-8")
)
text = COMMENT_RE.sub(
lambda m: "\n" * m.group().count("\n"), path.read_text(encoding="utf-8")
)
text = QUOTED_RE.sub(lambda m: "\n" * m.group().count("\n"), text)

@jserv

jserv commented Nov 9, 2026

Copy link
Copy Markdown
Contributor

Some review comments on this pull request are not settled yet. Please refine the change per the review comments listed below, reply on each thread with what you changed or why you did not, and mark each thread resolved yourself once it is settled (Resolve conversation, at the bottom of the thread). The pull request stays blocked while review threads are open.

Not addressed at the current head:

Addressed at the current head; reply with what changed and resolve:

Checked at e8e68bd.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Mechanize UI color token enforcement check

2 participants