diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6202d13..be23c48 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,6 +38,8 @@ jobs: run: pip install -e '.[dev]' - name: Install frontend browser dependencies run: npm ci + - name: Install deterministic CJK screenshot font + run: sudo apt-get update && sudo apt-get install -y fonts-noto-cjk - name: Install Playwright Firefox run: npx playwright install --with-deps firefox - name: Validate frontend script syntax @@ -46,6 +48,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..1ffaaa1 --- /dev/null +++ b/docs/theme-system.md @@ -0,0 +1,55 @@ +# 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 +sudo apt-get install fonts-noto-cjk +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 @@ - +