From ca076183ad5e9a9d302eefe60370528c30b90d63 Mon Sep 17 00:00:00 2001 From: "Shuhao Zhang (Tony)" Date: Sat, 15 Aug 2026 17:51:53 +0800 Subject: [PATCH 1/3] test: gate cross-route theme contrast --- .github/workflows/ci.yml | 10 + CHANGELOG.md | 11 + docs/theme-system.md | 54 ++++ pyproject.toml | 2 +- src/sage_faculty_twin/__init__.py | 2 +- src/sage_faculty_twin/api.py | 4 +- src/sage_faculty_twin/web/index.html | 7 +- src/sage_faculty_twin/web/styles.css | 281 ++++++++++-------- tests/browser/onboarding-responsive.spec.js | 248 +++++++++++++++- ...eme-dark-1280x800-status-firefox-linux.png | Bin 0 -> 80940 bytes ...heme-dark-390x844-status-firefox-linux.png | Bin 0 -> 49841 bytes ...me-light-1280x800-status-firefox-linux.png | Bin 0 -> 84008 bytes ...eme-light-390x844-status-firefox-linux.png | Bin 0 -> 54359 bytes tests/test_frontend_contract.py | 19 ++ uv.lock | 2 +- 15 files changed, 499 insertions(+), 141 deletions(-) create mode 100644 docs/theme-system.md create mode 100644 tests/browser/onboarding-responsive.spec.js-snapshots/theme-dark-1280x800-status-firefox-linux.png create mode 100644 tests/browser/onboarding-responsive.spec.js-snapshots/theme-dark-390x844-status-firefox-linux.png create mode 100644 tests/browser/onboarding-responsive.spec.js-snapshots/theme-light-1280x800-status-firefox-linux.png create mode 100644 tests/browser/onboarding-responsive.spec.js-snapshots/theme-light-390x844-status-firefox-linux.png diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6202d13..17ab830 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -46,6 +46,16 @@ jobs: run: python -m pytest tests/test_frontend_contract.py -q - name: Multi-viewport browser regression test run: npm run test:browser + - name: Upload visual baselines and failure diffs + if: always() + uses: actions/upload-artifact@v4 + with: + name: theme-release-gate + if-no-files-found: ignore + path: | + tests/browser/*-snapshots/*.png + test-results/**/*.png + test-results/**/*.zip test: runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 5605eca..38a3a89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog +## v4.6.29 - 2026-08-15 + +- Unified public chat, Support, status, settings, account, onboarding, and + operational state colors behind one light/dark semantic token contract. +- Split info/success/warning/error foreground, surface, and opaque border roles + so components cannot accidentally reuse a background token as text. +- Added a browser release gate for computed alpha-composited contrast across + desktop and 390x844, plus checked-in visual baselines and CI diff artifacts. +- Fixed previously untested low-contrast borders on recommendation chips and + settings cards, and added an explicit accessible name to account settings. + ## Unreleased ## v4.6.28 - 2026-08-15 diff --git a/docs/theme-system.md b/docs/theme-system.md new file mode 100644 index 0000000..95e3f97 --- /dev/null +++ b/docs/theme-system.md @@ -0,0 +1,54 @@ +# Theme system and visual release gate + +The public UI has one semantic color contract in the `:root` and +`html[data-theme="light"]` blocks of `web/styles.css`. Components must consume +those tokens; they must not encode a light or dark palette locally. + +## Token roles + +- `--bg`, `--bg-deep`: page canvas. +- `--surface`, `--surface-solid`, `--surface-soft`, `--surface-muted`, + `--surface-raised`, `--surface-inset`: elevation and containment. +- `--text-primary`, `--text-secondary`, `--muted`: text hierarchy. +- `--line`, `--line-strong`: decorative separators. +- `--control-border`, `--focus-ring`: interactive boundaries and keyboard focus. +- `--info-*`, `--success-*`, `--warning-*`, `--error-*`: separate + `surface`, `text`, and `border` roles. Never reuse a surface token as text. +- `--action-*`, `--send-*`: composer actions and their state changes. + +SVG icons use `currentColor`. Selected, processing, disabled, error, and +success states require a non-color cue such as a border, ring, icon, or label. + +## Release gate + +`tests/browser/onboarding-responsive.spec.js` exercises both themes at desktop +and 390x844. It audits final computed colors after alpha composition for the +chat landing page, a completed answer with Support references, system status, +settings, account forms, and success/warning/error states. + +The gate enforces: + +- normal text contrast of at least 4.5:1; +- tested control boundaries of at least 3:1; +- no horizontal overflow at either viewport; +- checked-in Firefox screenshot baselines for the light/dark status route. + +Run the gate with: + +```bash +npm ci +npx playwright install firefox +npx playwright test tests/browser/onboarding-responsive.spec.js +``` + +Intentional visual changes require local review before updating snapshots with +`--update-snapshots`. CI uploads the baselines and failure diffs for review. + +## New component checklist + +1. Use semantic tokens only; do not add component-local black/white RGBA colors. +2. Verify default, hover, focus-visible, selected, processing, disabled, + loading, empty, success, warning, and error states where applicable. +3. Add the component's rendered selectors to the computed-style audit. +4. Check light/dark at desktop and 390x844, including keyboard focus. +5. Update a screenshot baseline only after inspecting the rendered image. diff --git a/pyproject.toml b/pyproject.toml index af64dc2..16b6f88 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "sage-mate" -version = "4.6.28" +version = "4.6.29" description = "Sage Mate multi-profile local and hosted assistant built on SAGE and vllm-hust" readme = "README.md" license = {file = "LICENSE"} diff --git a/src/sage_faculty_twin/__init__.py b/src/sage_faculty_twin/__init__.py index 16a18be..44d79d7 100644 --- a/src/sage_faculty_twin/__init__.py +++ b/src/sage_faculty_twin/__init__.py @@ -1,5 +1,5 @@ """Sage Mate application package.""" -__version__ = "4.6.28" +__version__ = "4.6.29" __all__ = ["__version__"] diff --git a/src/sage_faculty_twin/api.py b/src/sage_faculty_twin/api.py index 327a54b..8977a9a 100644 --- a/src/sage_faculty_twin/api.py +++ b/src/sage_faculty_twin/api.py @@ -1269,7 +1269,7 @@ async def home() -> FileResponse: @llm_app.api_route("/styles.css", methods=["GET", "HEAD"], include_in_schema=False) @llm_app.api_route("/styles.4217.css", methods=["GET", "HEAD"], include_in_schema=False) -@llm_app.api_route("/styles.4220.css", methods=["GET", "HEAD"], include_in_schema=False) +@llm_app.api_route("/styles.4221.css", methods=["GET", "HEAD"], include_in_schema=False) async def styles() -> FileResponse: return frontend_asset("styles.css") @@ -1284,7 +1284,7 @@ async def homepage_redirect() -> RedirectResponse: @llm_app.api_route("/app.js", methods=["GET", "HEAD"], include_in_schema=False) @llm_app.api_route("/app.4217.js", methods=["GET", "HEAD"], include_in_schema=False) @llm_app.api_route("/app.4218.js", methods=["GET", "HEAD"], include_in_schema=False) -@llm_app.api_route("/app.4220.js", methods=["GET", "HEAD"], include_in_schema=False) +@llm_app.api_route("/app.4221.js", methods=["GET", "HEAD"], include_in_schema=False) async def app_js() -> FileResponse: return frontend_asset("app.js") diff --git a/src/sage_faculty_twin/web/index.html b/src/sage_faculty_twin/web/index.html index 805638b..adcca49 100644 --- a/src/sage_faculty_twin/web/index.html +++ b/src/sage_faculty_twin/web/index.html @@ -21,7 +21,7 @@ - +