From 3646972d8ca47fbf0b7649c46df22a24440a4721 Mon Sep 17 00:00:00 2001 From: Brandonma21 Date: Fri, 18 Sep 2026 10:50:06 +0200 Subject: [PATCH 1/5] Add Spec Kit plan for LS-2934 color-contrast accessibility fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Context - BugHerd #231 (epic LS-2934) flags 9 WCAG AA color-contrast violations across 4 DEV URLs - Two independent root causes: image captions on dark-background posts, active blog filter pill - Full Spec Kit workflow run: specify, clarify, plan, tasks, checklist, analyze Spec & Planning - spec.md: two prioritized user stories (P1 captions, P2 filter pill), 8 functional requirements, 4 success criteria - Clarified verification scope: fix inherits sitewide by design, but only the 4 flagged URLs are tested - plan.md, research.md, data-model.md: confirms both fixes reuse existing color tokens (text--on-dark-muted, text--on-light) — no new tokens needed - quickstart.md: end-to-end verification steps using SINGLE_PAGE_URL against DEV, no new BugHerd tasks created Tasks & Quality Gates - tasks.md: 20 tasks across setup, two independent user-story phases, and polish - checklists/requirements.md, checklists/accessibility.md: spec-quality and requirements-quality reviews Analysis Fixes - Closed a Constitution Principle VI gap: added phpcs/php -l validation for changed PHP files - Corrected a false assumption that captions needed dark/light selector scoping — this theme uses one active global style sitewide, not per-post; caption fix is now unconditional, matching existing taxonomy-filter.scss pattern - Documented FR-004 (new token parity) as intentionally not triggered, and added layout/spacing regression checks Co-Authored-By: Claude Sonnet 5 --- .../checklists/accessibility.md | 60 +++++++ .../checklists/requirements.md | 35 +++++ specs/002-color-contrast-fixes/data-model.md | 34 ++++ specs/002-color-contrast-fixes/plan.md | 87 ++++++++++ specs/002-color-contrast-fixes/quickstart.md | 57 +++++++ specs/002-color-contrast-fixes/research.md | 51 ++++++ specs/002-color-contrast-fixes/spec.md | 95 +++++++++++ specs/002-color-contrast-fixes/tasks.md | 148 ++++++++++++++++++ 8 files changed, 567 insertions(+) create mode 100644 specs/002-color-contrast-fixes/checklists/accessibility.md create mode 100644 specs/002-color-contrast-fixes/checklists/requirements.md create mode 100644 specs/002-color-contrast-fixes/data-model.md create mode 100644 specs/002-color-contrast-fixes/plan.md create mode 100644 specs/002-color-contrast-fixes/quickstart.md create mode 100644 specs/002-color-contrast-fixes/research.md create mode 100644 specs/002-color-contrast-fixes/spec.md create mode 100644 specs/002-color-contrast-fixes/tasks.md diff --git a/specs/002-color-contrast-fixes/checklists/accessibility.md b/specs/002-color-contrast-fixes/checklists/accessibility.md new file mode 100644 index 0000000..d231a88 --- /dev/null +++ b/specs/002-color-contrast-fixes/checklists/accessibility.md @@ -0,0 +1,60 @@ +# Accessibility Requirements Quality Checklist: Fix WCAG Color-Contrast Accessibility Violations (LS-2934) + +**Purpose**: Author self-check of the spec's accessibility/color-contrast requirements for completeness, clarity, consistency, and measurability before implementation begins +**Created**: 2026-09-18 +**Feature**: [spec.md](../spec.md) + +**Note**: This custom checklist is generated by the `/speckit-checklist` command based on feature context and requirements. +**Review Ownership**: This checklist is a reviewer-owned requirements-quality review artifact. Mark an item `[x]` only when the reviewer determines the requirements-quality criterion is satisfied. +**Marker Semantics**: `[x]` means the criterion has been reviewed and satisfied for requirements quality. It does not mean implementation work is complete. + +## Requirement Completeness + +- [ ] CHK001 Are contrast requirements defined for every distinct element type affected (captions AND filter pill), rather than one generic "fix contrast" statement? [Completeness, Spec §FR-001, §FR-002] +- [ ] CHK002 Are requirements defined for what happens to elements that share a token being changed but weren't part of the original violation report? [Completeness, Spec Edge Cases] +- [ ] CHK003 Is there a requirement covering how future content (new posts, new pages) inherits or doesn't inherit these fixes? [Completeness, Spec §SC-002] +- [ ] CHK004 Are requirements defined for the non-color aspects of the affected elements that must remain untouched (layout, spacing, structure)? [Completeness, Spec §FR-005] + +## Requirement Clarity + +- [ ] CHK005 Is "dark-background post style" defined precisely enough to distinguish it from other post styles without relying on implementation knowledge? [Clarity, Spec §FR-001, Assumptions] +- [ ] CHK006 Is the WCAG conformance target (level, ratio, text-size category) stated as a specific number rather than a general reference to "accessible"? [Clarity, Spec §FR-001, §FR-002] +- [ ] CHK007 Is "token-based" (as opposed to hardcoded color) defined clearly enough that a reviewer could judge compliance without inside knowledge of the codebase? [Clarity, Spec §FR-003] +- [ ] CHK008 Is the distinction between "verification" and "fix scope" (per the recorded clarification) stated unambiguously, so a reader cannot re-interpret SC-002 as requiring a sitewide audit? [Clarity, Spec §Clarifications, §SC-002] + +## Requirement Consistency + +- [ ] CHK009 Do the acceptance scenarios for User Story 1 and User Story 2 apply the same contrast threshold and conformance level consistently? [Consistency, Spec §FR-001, §FR-002] +- [ ] CHK010 Are the "no regression to unaffected states/contexts" requirements worded consistently between the caption fix and the filter-pill fix? [Consistency, Spec §FR-006] +- [ ] CHK011 Does the out-of-scope declaration for the broken-CSS-asset issue remain consistent everywhere it's referenced (Assumptions vs. Input description)? [Consistency, Spec Assumptions] + +## Acceptance Criteria Quality + +- [ ] CHK012 Can SC-001's "100% of the previously-flagged nodes no longer appear" be verified objectively without additional interpretation of which nodes counted originally? [Measurability, Spec §SC-001] +- [ ] CHK013 Is SC-004 ("zero new externally-tracked bug reports") independently verifiable, or does it rely on trusting a specific test invocation method not described in the success criterion itself? [Measurability, Spec §SC-004] +- [ ] CHK014 Are the acceptance scenarios in both user stories phrased as testable Given/When/Then statements with no subjective terms (e.g., "readable", "clear") left unquantified? [Measurability, Spec §User Story 1, §User Story 2] + +## Scenario Coverage + +- [ ] CHK015 Are exception/failure scenarios addressed — e.g., what should happen if no existing token satisfies the required contrast ratio? [Coverage, Gap] +- [ ] CHK016 Are non-functional requirements beyond contrast ratio (e.g., no performance regression from added CSS) explicitly stated or explicitly declared out of scope? [Coverage, Gap] +- [ ] CHK017 Does the spec address concurrent/parallel work scenarios — e.g., both user stories being implemented at the same time by different people touching shared tokens? [Coverage, Edge Case] + +## Dependencies & Assumptions + +- [ ] CHK018 Is the assumption that "an existing token will be reused where possible" validated against actual token availability, or does the spec leave this as an open risk for planning to discover? [Assumption, Spec Assumptions] +- [ ] CHK019 Is the dependency on the standing Playwright suite's `SINGLE_PAGE_URL` mechanism (for FR-007's no-new-BugHerds guarantee) explicitly called out as an external dependency rather than assumed common knowledge? [Dependency, Spec §FR-007] + +## Ambiguities & Conflicts + +- [ ] CHK020 Is there any remaining ambiguity between FR-006 ("must not alter... unless required to meet FR-001/FR-002") and FR-005 ("must not alter layout") regarding how much color-adjacent change (e.g., opacity, borders) is actually permitted? [Ambiguity, Spec §FR-005, §FR-006] +- [ ] CHK021 Is a requirement/acceptance-criteria ID scheme consistently applied so every functional requirement and success criterion can be traced from a checklist item back to a single unambiguous spec line? [Traceability] + +## Notes + +- Mark items `[x]` only after review confirms the requirement-quality criterion is satisfied +- Leave items unchecked when they still require clarification, correction, or reviewer evaluation +- `/speckit-implement` reads checklist checkbox state as a gate and must not modify markers +- `checklists/requirements.md` has a separate built-in lifecycle maintained by `/speckit-specify` and `/speckit-clarify` — this file is independent of it +- Add comments or findings inline +- Items are numbered sequentially for easy reference diff --git a/specs/002-color-contrast-fixes/checklists/requirements.md b/specs/002-color-contrast-fixes/checklists/requirements.md new file mode 100644 index 0000000..49eed89 --- /dev/null +++ b/specs/002-color-contrast-fixes/checklists/requirements.md @@ -0,0 +1,35 @@ +# Specification Quality Checklist: Fix WCAG Color-Contrast Accessibility Violations (LS-2934) + +**Purpose**: Validate specification completeness and quality before proceeding to planning +**Created**: 2026-09-18 +**Feature**: [spec.md](../spec.md) + +## Content Quality + +- [x] No implementation details (languages, frameworks, APIs) +- [x] Focused on user value and business needs +- [x] Written for non-technical stakeholders +- [x] All mandatory sections completed + +## Requirement Completeness + +- [x] No [NEEDS CLARIFICATION] markers remain +- [x] Requirements are testable and unambiguous +- [x] Success criteria are measurable +- [x] Success criteria are technology-agnostic (no implementation details) +- [x] All acceptance scenarios are defined +- [x] Edge cases are identified +- [x] Scope is clearly bounded +- [x] Dependencies and assumptions identified + +## Feature Readiness + +- [x] All functional requirements have clear acceptance criteria +- [x] User scenarios cover primary flows +- [x] Feature meets measurable outcomes defined in Success Criteria +- [x] No implementation details leak into specification + +## Notes + +- No [NEEDS CLARIFICATION] markers were needed — root causes, affected URLs, and fix constraints (token-based, light/dark parity, theme-first) were already established and agreed in prior investigation, so requirements were derived directly rather than guessed. +- Specific file paths, SCSS partials, and exact token names were intentionally left out of this spec (per spec-writing guidelines: no implementation details) and belong in the `/speckit-plan` phase instead. diff --git a/specs/002-color-contrast-fixes/data-model.md b/specs/002-color-contrast-fixes/data-model.md new file mode 100644 index 0000000..6392137 --- /dev/null +++ b/specs/002-color-contrast-fixes/data-model.md @@ -0,0 +1,34 @@ +# Phase 1 Data Model: Fix WCAG Color-Contrast Accessibility Violations (LS-2934) + +This feature has no runtime data model, database schema, or API payloads — it is a static styling fix. The "entities" below are the design-token and markup concepts the fix touches, carried over from the spec's Key Entities section, with the specific resolved values from research.md. + +## Color Token: `--wp--custom--color--text--on-dark-muted` + +- **Represents**: Muted/secondary text color intended for use on dark surfaces. +- **Current definition**: `theme.json` → `settings.custom.color.text.on-dark-muted` → preset `neutral-400` → `#B8B8B8`. +- **Dark-mode parity**: Already has a distinct, resolved value in `styles/dark.json` (verified pre-existing; not modified by this feature). +- **New usage introduced by this feature**: Referenced by the new `.wp-element-caption` / `figcaption` color rule in `src/scss/structural/image-captions.scss`. +- **Validation rule (FR-001, FR-003)**: Resolved contrast against the `contrast` background token (`#080808`) MUST be ≥ 4.5:1 — confirmed at 10.1:1. + +## Color Token: `--wp--custom--color--text--on-light` + +- **Represents**: Text color intended for use on light/mid-brightness surfaces; resolves to a near-black value. +- **Current definition**: `theme.json` → `settings.custom.color.text.on-light` → preset `contrast` → `#080808`. +- **Dark-mode parity**: Already has a distinct, resolved value in `styles/dark.json` (verified pre-existing; not modified by this feature). +- **New usage introduced by this feature**: Replaces `--text--on-dark` as the `color` value on `.taxonomy-filter-current` in `src/scss/structural/taxonomy-filter.scss`. +- **Validation rule (FR-002, FR-003)**: Resolved contrast against the `--card--platform--wordpress` background token MUST be ≥ 4.5:1 — confirmed at 6.57:1. + +## Markup Entity: Image Caption (`.wp-element-caption` / `figcaption`) + +- **Represents**: Caption text rendered under an embedded image within post content (WordPress core block markup, not a custom component). +- **Contexts**: Appears on both dark-background and light-background post styles. +- **Change**: Dark-background context gains an explicit color override (see token above); light-background context is unaffected (FR-006) — no existing rule is removed, only a new scoped rule is added. +- **State**: No interactive states (no hover/focus) — static text. + +## Markup Entity: Taxonomy Filter Pill (`.taxonomy-filter-current`) + +- **Represents**: The active/selected state of a clickable category filter control on the blog listing page (`src/scss/structural/taxonomy-filter.scss`). +- **States**: default (unaffected), `:hover` (unaffected, FR-006), `.taxonomy-filter-current` (active/selected — the only state changed by this feature). +- **Change**: One `color` declaration's token reference is swapped; `background-color` and all other properties are unchanged. + +No new entities, relationships, or state transitions beyond the above — this table exists to satisfy the plan template's Phase 1 output requirement, not because the feature has meaningful data-model complexity. diff --git a/specs/002-color-contrast-fixes/plan.md b/specs/002-color-contrast-fixes/plan.md new file mode 100644 index 0000000..c9e1c45 --- /dev/null +++ b/specs/002-color-contrast-fixes/plan.md @@ -0,0 +1,87 @@ +# Implementation Plan: Fix WCAG Color-Contrast Accessibility Violations (LS-2934) + +**Branch**: `fix/ls-2934-accessibility-color-contrast-fixes` | **Date**: 2026-09-18 | **Spec**: [spec.md](./spec.md) + +**Input**: Feature specification from `/specs/002-color-contrast-fixes/spec.md` + +## Summary + +Two WCAG AA color-contrast violations (BugHerd #231, epic LS-2934) must be fixed using existing theme color tokens, with zero hardcoded hex values and full light/dark parity. Research (below) confirms both fixes can reuse **existing** tokens already defined in `theme.json`/`styles/dark.json` — no new token needs to be created. The approach is: (1) add one new theme-first SCSS partial that overrides `.wp-element-caption`/`figcaption` color using the existing `--wp--custom--color--text--on-dark-muted` token, scoped to dark-background post contexts; (2) swap one `color` declaration in the existing `taxonomy-filter.scss` partial from `--text--on-dark` to the existing `--text--on-light` token (which resolves to a dark value that passes contrast against the pill's blue background). + +## Technical Context + +**Language/Version**: SCSS (Dart Sass), compiled to static CSS; PHP 8.x (WordPress theme, no build framework beyond the Sass CLI) + +**Primary Dependencies**: WordPress core theme.json token system; Dart `sass` CLI (already used by `npm run build:css`); `@axe-core/playwright` + Playwright (existing test suite, used for verification only — not modified) + +**Storage**: N/A (static theme files — `theme.json`, `styles/dark.json`, `.scss`/`.css`) + +**Testing**: Existing Playwright "standing suite" accessibility spec (`tests/specs/standing/accessibility.spec.ts`), run in scoped `SINGLE_PAGE_URL` mode against DEV per URL — no new test code needed, this feature is verified with the existing spec + +**Target Platform**: WordPress block theme (`ls-theme`), all frontend browsers/viewports covered by the existing style system (no new platform surface) + +**Project Type**: Single WordPress theme repository (no frontend/backend split, no mobile component) + +**Performance Goals**: N/A — this is a pure color-value change; no measurable performance impact expected (no new assets, no new requests, one additional small compiled CSS file) + +**Constraints**: Token-only color values (Constitution Principle III); no changes to layout/spacing/markup (spec FR-005); no hardcoded hex; must not regress light-background captions or filter pill default/hover states (spec FR-006) + +**Scale/Scope**: 2 files changed (`taxonomy-filter.scss`), 1 new file added (new caption-color partial) + build script registration; 0 new color tokens required per research below + +## Constitution Check + +*GATE: Must pass before Phase 0 research. Re-check after Phase 1 design.* + +| Principle | Applicability | Status | +|---|---|---| +| I. Theme-First Styling | New caption-color rule is a `:hover`/pseudo-state-free plain color declaration expressible in JSON in principle, but the existing repo pattern for equivalent per-context color overrides (e.g. `taxonomy-filter.scss`) already lives in `src/scss/structural/**` as an SCSS partial compiled 1:1 to its own CSS file and separately enqueued — following that same established pattern for consistency, not introducing a new mechanism. Placed in `src/scss/structural/`, never `src/scss/animations/` (motion-only, per Principle I). | **PASS** | +| II. Reuse Before Create | Research (Phase 0) confirms both fixes reuse existing tokens (`text--on-dark-muted`, `text--on-light`) — zero new tokens created. New SCSS partial named by shape (`image-captions`), not by page. | **PASS** | +| III. Token Parity | Both reused tokens already have real, distinct, resolved light/dark values (verified in research.md) — no new token pair needed, so no parity gap possible. | **PASS** | +| IV. Core Blocks First | N/A — no block markup or attributes are changed, only inherited caption/pill color. | **N/A** | +| V. Accessibility and Security Non-Negotiables | This feature *is* the WCAG AA remediation; no PHP output/escaping surface is touched. | **PASS** | +| VI. Validation Before Done | Plan includes `npm run schema:validate` (if any JSON touched) and `npm run theme:validate` before completion; `validate_blocks` tool will NOT be used (banned). | **PASS** | +| VII. PHP Minimalism | If new CSS file needs enqueuing, follow the existing `inc/animations.php` / `functions.php` `add_editor_style()` pattern already used for sibling structural CSS files — no new PHP architecture. | **PASS** | + +No violations requiring justification — Complexity Tracking section is not needed. + +## Project Structure + +### Documentation (this feature) + +```text +specs/002-color-contrast-fixes/ +├── plan.md # This file +├── research.md # Phase 0 output +├── data-model.md # Phase 1 output (tokens as "entities", minimal — no runtime data model) +├── quickstart.md # Phase 1 output — manual + scripted verification steps +├── tasks.md # Phase 2 output (/speckit-tasks command — not created by this command) +└── checklists/ # /speckit-specify (requirements.md) and /speckit-checklist (accessibility.md) outputs +``` + +(No `contracts/` — this feature has no external API/interface surface; it is a static styling fix internal to the theme.) + +### Source Code (repository root) + +This is a single WordPress block theme repository — no frontend/backend split. Real paths affected: + +```text +wp-content/themes/ls-theme/ +├── theme.json # read-only reference for existing token values (no edits expected) +├── styles/dark.json # read-only reference for existing token dark values (no edits expected) +├── src/scss/structural/ +│ ├── taxonomy-filter.scss # EDIT: swap one `color` token on `.taxonomy-filter-current` +│ └── image-captions.scss # NEW: dark-background caption color override +├── inc/animations.php or functions.php # EDIT (if needed): enqueue/register the new compiled CSS file, +│ # following the existing pattern used for sibling structural CSS +├── package.json # EDIT: register new src→dest pair in build:css / build:css:dev / watch:css +├── assets/css/ +│ ├── taxonomy-filter.css # REGENERATED (compiled output, not hand-edited) +│ └── image-captions.css # NEW (compiled output, not hand-edited) +└── tests/specs/standing/accessibility.spec.ts # UNCHANGED — used as-is for verification +``` + +**Structure Decision**: Follow the repo's existing 1-SCSS-partial-to-1-compiled-CSS-file convention already used by every other file under `src/scss/structural/` (confirmed via `package.json`'s `build:css` script, which lists each structural partial and its compiled destination explicitly) — no new build tooling, no consolidation of partials, no framework change. + +## Complexity Tracking + +*Not applicable — no Constitution Check violations.* diff --git a/specs/002-color-contrast-fixes/quickstart.md b/specs/002-color-contrast-fixes/quickstart.md new file mode 100644 index 0000000..ae96f4b --- /dev/null +++ b/specs/002-color-contrast-fixes/quickstart.md @@ -0,0 +1,57 @@ +# Quickstart: Verifying the LS-2934 Color-Contrast Fixes + +This guide validates both fixes end-to-end against DEV, using the existing standing-suite accessibility spec in scoped single-page mode — the same method already agreed for this task, which does not create any new BugHerd tasks. + +## Prerequisites + +- On branch `fix/ls-2934-accessibility-color-contrast-fixes`, with both fixes implemented: + - `src/scss/structural/image-captions.scss` created and compiled to `assets/css/image-captions.css` + - `src/scss/structural/taxonomy-filter.scss` edited (token swap) and recompiled + - New CSS file enqueued/registered per `research.md` Unknown 4 +- `.env` present in the theme root with `BASE_URL=https://ls-agency.lightspeedwp.dev` (already the case in this repo) +- Dependencies installed (`npm install`, Playwright browsers installed) + +## Step 1 — Rebuild compiled CSS + +```bash +npm run build:css +``` + +Confirm `assets/css/image-captions.css` now exists and `assets/css/taxonomy-filter.css` has changed (`git diff --stat`). + +## Step 2 — Run required validation gates (Constitution Principle VI) + +```bash +npm run schema:validate +npm run theme:validate +npm run lint:json +``` + +All three must pass before proceeding. Do **not** run the banned `validate_blocks` tool. + +## Step 3 — Scoped accessibility re-checks (no new BugHerd tasks) + +Run once per originally-flagged URL, each as its own command: + +```bash +SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/blog/" npx playwright test accessibility --project=chromium --reporter=line + +SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/lightspeed-remote-workspaces-2016/" npx playwright test accessibility --project=chromium --reporter=line + +SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/lsx-version-1-3-0-released/" npx playwright test accessibility --project=chromium --reporter=line + +SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/lsx-version-1-2-5-released/" npx playwright test accessibility --project=chromium --reporter=line +``` + +**Expected outcome**: all 4 runs pass with zero serious/critical `color-contrast` violations (matches spec SC-001). `SINGLE_PAGE_URL` structurally prevents the BugHerd reporter from filing anything, regardless of pass/fail (spec FR-007/SC-004). + +## Step 4 — Manual regression spot-check (FR-006, SC-003) + +In a browser against DEV: +1. Open a post using the **light/default** background style with an image caption — confirm caption text still renders as before (no visual change). +2. Open `/blog/` and hover over a **non-active** filter pill — confirm default and hover states are visually unchanged. +3. Confirm the active pill (`.taxonomy-filter-current`) still uses the same blue background — only its text color should look different (darker/higher-contrast). + +## Step 5 — Record results + +Attach or reference the 4 scoped test run outputs (pass/fail + any axe JSON attachments) when marking BugHerd task #231 as resolved, per the existing manual QA workflow — this quickstart does not automate BugHerd status changes. diff --git a/specs/002-color-contrast-fixes/research.md b/specs/002-color-contrast-fixes/research.md new file mode 100644 index 0000000..1684913 --- /dev/null +++ b/specs/002-color-contrast-fixes/research.md @@ -0,0 +1,51 @@ +# Phase 0 Research: Fix WCAG Color-Contrast Accessibility Violations (LS-2934) + +## Unknown 1: Does an existing token satisfy the caption-on-dark-background fix (FR-001), or is a new token pair required? + +**Decision**: Reuse the existing `--wp--custom--color--text--on-dark-muted` token. No new token needed. + +**Rationale**: This token already exists in `theme.json` (`settings.custom.color.text.on-dark-muted`, resolving to preset `neutral-400` / `#B8B8B8`) and is exactly the semantic use case needed — muted text on a dark surface. Checked its actual resolved contrast against the theme's `contrast` background token (`#080808`, the dark post-background color): **10.1:1**, comfortably clearing the 4.5:1 AA minimum for normal text. Per Constitution Principle III ("verify what a token actually resolves to rather than inferring from its name"), the resolved value was computed directly (WCAG relative-luminance contrast formula) rather than assumed from the token name. + +**Alternatives considered**: +- Introducing a new `text--caption-on-dark` token — rejected because an existing token already satisfies the requirement with a wide margin (10.1:1 vs the 4.5:1 minimum), and Constitution Principle II (Reuse Before Create) requires checking for a fit before adding a new token. +- Reusing `--text--on-dark` (the token currently causing violation #2, resolving to `base`/`#FAFAFA`) for captions too — not evaluated, since it's the wrong semantic fit (full-emphasis text token, not muted/secondary text) and isn't the token actually causing this violation (captions currently inherit WordPress core's unthemed default `#555555`, not any theme token at all). + +## Unknown 2: Does an existing token satisfy the taxonomy-filter-pill fix (FR-002), or is a new token pair required? + +**Decision**: Reuse the existing `--wp--custom--color--text--on-light` token. No new token needed. + +**Rationale**: The failing pair is `background-color: var(--wp--custom--color--card--platform--wordpress)` (resolves to a brand blue, `#5c90ff` as rendered live) with `color: var(--wp--custom--color--text--on-dark)` (`#FAFAFA`) — actual ratio 2.92:1. The existing `--text--on-light` token resolves to the `contrast` preset (`#080808`, a near-black). Computed contrast of `#080808` against the live blue background: **6.57:1**, clearing AA with margin. This token already exists and is used elsewhere in the theme for dark text on light/mid-brightness surfaces. + +**Alternatives considered**: +- Adding a new token pair specifically for "text on platform-wordpress blue" — rejected; an existing general-purpose token already resolves correctly, so a new one would duplicate it (Principle II). +- Darkening the background token instead of changing the text token — rejected; `--card--platform--wordpress` is a shared token likely used elsewhere (e.g. other platform/brand card treatments), and the spec's edge cases explicitly call for not modifying a shared token if it risks side effects elsewhere. Changing only the locally-scoped `.taxonomy-filter-current` text color has no shared-token blast radius. +- **Naming caveat**: `--text--on-light` is semantically named for light backgrounds, but its *resolved* dark value is what makes it work here against a medium-blue background — this is exactly the "verify resolved value, don't infer from name" case Constitution Principle III warns about. This is noted for whoever reviews the diff, since the token name won't intuitively read as "text on a blue pill" — but per Reuse Before Create, correctness of the resolved value takes precedence over naming intuition, and renaming/aliasing an existing shared token is out of scope for a 2-line contrast fix. + +## Unknown 3: Where does a new sitewide/theme-wide SCSS rule belong, given AGENTS.md's animations-file restriction? + +**Decision**: New partial at `src/scss/structural/image-captions.scss`, registered as its own `src:dest` pair in `package.json`'s `build:css`, `build:css:dev`, and `watch:css` scripts, following the exact pattern already used for every other file under `src/scss/structural/` (each compiles 1:1 to its own file under `assets/css/`). + +**Rationale**: `src/scss/animations/**` and `assets/css/animations.css` are restricted by Constitution Principle I to motion-only properties (`@keyframes`, `transition`, `transform`, `animation`, `will-change`) — a `color` declaration does not qualify and placing it there would be a direct constitution violation. `src/scss/structural/` is the established location for non-motion, non-JSON-expressible SCSS in this repo (confirmed: `taxonomy-filter.scss`, the file already being edited for violation #2, lives there). + +**Alternatives considered**: +- Adding the caption-color rule directly inside `taxonomy-filter.scss` — rejected; unrelated to that file's shape (filter pills, not captions), and Principle II's shape-based naming convention argues against bundling unrelated concerns into one file. +- Expressing the rule via `theme.json`/`styles/dark.json` block-style JSON instead of SCSS — considered per Principle I's JSON-first preference, but WordPress's `theme.json` currently has no generic `elements.caption` key comparable to `elements.link`/`elements.heading` for arbitrary caption styling across contexts; this would need confirming against the theme's installed WP version before ruling out. Flagged as a design-time check during implementation (see quickstart.md) rather than a blocking unknown, since the SCSS-partial approach is already a proven, constitution-compliant fallback consistent with sibling files in this repo. + +## Unknown 4: How does a new compiled CSS file get loaded on the frontend? + +**Decision**: Follow the existing enqueue pattern used for sibling structural CSS files (e.g. `taxonomy-filter.css`), confirmed in `inc/animations.php` (frontend enqueue) and `functions.php` (`add_editor_style()` for editor parity) — add equivalent entries for the new `image-captions.css` file rather than inventing a new enqueue mechanism. + +**Rationale**: Consistent with Constitution Principle VII (no new PHP architecture; use the existing enqueue conventions already present in this theme). + +**Alternatives considered**: None — this is a direct, established pattern with no reasonable alternative in a WordPress classic-enqueue theme. + +## Summary of resolved unknowns + +| # | Unknown | Resolution | +|---|---|---| +| 1 | New token needed for caption fix? | No — reuse `--text--on-dark-muted` (10.1:1 against dark bg) | +| 2 | New token needed for filter-pill fix? | No — reuse `--text--on-light` (6.57:1 against blue bg) | +| 3 | Where does the new SCSS rule live? | `src/scss/structural/image-captions.scss` (new file) | +| 4 | How is the new CSS enqueued? | Same pattern as `taxonomy-filter.css` in `inc/animations.php` / `functions.php` | + +All `NEEDS CLARIFICATION` items from Technical Context are resolved. No blockers remain for Phase 1 design. diff --git a/specs/002-color-contrast-fixes/spec.md b/specs/002-color-contrast-fixes/spec.md new file mode 100644 index 0000000..7277aa0 --- /dev/null +++ b/specs/002-color-contrast-fixes/spec.md @@ -0,0 +1,95 @@ +# Feature Specification: Fix WCAG Color-Contrast Accessibility Violations (LS-2934) + +**Feature Branch**: `fix/ls-2934-accessibility-color-contrast-fixes` + +**Created**: 2026-09-18 + +**Status**: Draft + +**Input**: User description: "Fix WCAG color-contrast accessibility violations (LS-2934). Fix two confirmed color-contrast accessibility violations (WCAG 2 AA, `color-contrast` axe rule, 'serious' impact) identified by the standing Playwright accessibility suite and tracked as BugHerd task #231 (epic LS-2934, all sub-issues complete — these fixes close out the epic). Scope: (1) image caption text unreadable on dark-background posts across three published posts, (2) active taxonomy filter pill fails contrast on the blog page. Fixes must be token-based (no hardcoded hex), theme-first per AGENTS.md, with light/dark token parity for any new token. Verification via scoped single-page accessibility test re-runs against DEV without creating new BugHerd tasks. Out of scope: broken CSS asset issues (#233/#235/#236/#241), other axe violation categories, full-suite runs." + +## Clarifications + +### Session 2026-09-18 + +- Q: Should verification for this feature also confirm the caption-contrast fix works on other dark-background posts beyond the 3 already flagged, or only on those exact 3 posts? → A: Verify only the 4 originally-flagged URLs; sitewide inheritance is a design property of the fix (applied at the shared style/token level), not something separately tested. + +## User Scenarios & Testing *(mandatory)* + +### User Story 1 - Readable image captions on dark-background posts (Priority: P1) + +A site visitor with low vision or in a bright-light environment reads a blog post rendered with a dark background (e.g. a release-notes or company-update post) and needs to read the caption text under each embedded image without straining to make out the text against the background. + +**Why this priority**: This affects the highest volume of flagged violation nodes (16 on one page alone) and spans multiple published, publicly-indexed posts — it's the most visible and widest-reaching of the two issues. + +**Independent Test**: Load any of the three affected posts on DEV, run an automated contrast scan restricted to that single page, and confirm the caption text under each image meets the WCAG AA 4.5:1 minimum contrast ratio against its dark background. Can be verified and shipped independently of User Story 2. + +**Acceptance Scenarios**: + +1. **Given** a published post using the theme's dark background style, **When** the post renders an image with a caption, **Then** the caption text has a contrast ratio of at least 4.5:1 against the dark background. +2. **Given** the three currently-affected posts (`/lightspeed-remote-workspaces-2016/`, `/lsx-version-1-3-0-released/`, `/lsx-version-1-2-5-released/`), **When** each is scanned individually, **Then** zero serious/critical color-contrast violations are reported for caption elements. +3. **Given** a post using the theme's light/default background style, **When** the post renders an image with a caption, **Then** the caption text remains readable and unaffected by this change (no regression to light-background captions). + +--- + +### User Story 2 - Readable active filter state on the blog page (Priority: P2) + +A site visitor browsing the blog page applies or views the currently-active category/taxonomy filter ("All" or another filter pill) and needs to clearly read which filter is currently selected. + +**Why this priority**: Narrower in scope (one page, one interactive element) than User Story 1, but still a "serious" impact violation on a high-traffic page (the blog listing). + +**Independent Test**: Load `/blog/` on DEV, run an automated contrast scan restricted to that single page, and confirm the active filter pill's text meets the WCAG AA 4.5:1 minimum contrast ratio against its background. Can be verified and shipped independently of User Story 1. + +**Acceptance Scenarios**: + +1. **Given** the blog page with a filter pill in its active/selected state, **When** the page is scanned, **Then** the active pill's text has a contrast ratio of at least 4.5:1 against its background color. +2. **Given** a filter pill in its default (non-active, non-hover) state, **When** the page is scanned, **Then** its existing contrast behavior is unaffected by this change. +3. **Given** a filter pill in its hover state, **When** a user hovers over it, **Then** its existing hover contrast behavior is unaffected by this change. + +--- + +### Edge Cases + +- What happens on a post that uses a dark background but has no images/captions at all? (No caption elements exist, so no violation applies — not a regression risk.) +- What happens if a future post reuses the same dark-background style variation? (The caption color fix must apply theme-wide/sitewide to that style, not be patched per-post, so newly published posts inherit the fix automatically.) +- What happens to the color-scheme in dark mode / the site's dark style variation, if distinct from the "dark background post" style? (Any new or adjusted token must carry correct, distinct values in both the light (`theme.json`) and dark (`styles/dark.json`) definitions — never the same value duplicated in both.) +- What happens to other elements sharing the same tokens being adjusted (e.g. other components using the `text--on-dark` or `card--platform--wordpress` tokens elsewhere on the site)? (Must be checked for unintended contrast regressions before the token itself is changed; if a shared token can't be safely adjusted without side effects, a new token is introduced instead of modifying the shared one.) + +## Requirements *(mandatory)* + +### Functional Requirements + +- **FR-001**: Image captions rendered on posts using the theme's dark-background post style MUST have a text-to-background contrast ratio of at least 4.5:1 (WCAG 2 AA, normal text). +- **FR-002**: The active/selected state of the blog page's taxonomy filter pill MUST have a text-to-background contrast ratio of at least 4.5:1 (WCAG 2 AA, normal text). +- **FR-003**: All color values used to satisfy FR-001 and FR-002 MUST be defined as semantic design tokens (not hardcoded color values), consistent with the theme's existing token-based color system. +- **FR-004**: Any new color token introduced to satisfy FR-001 or FR-002 MUST define both a light-mode value and a dark-mode value, and the two values MUST NOT be identical. +- **FR-005**: The fixes MUST NOT alter the visual layout, spacing, or structural positioning of captions or filter pills — only color values may change. +- **FR-006**: The fixes MUST NOT alter the appearance of image captions on light-background posts, or the default/hover states of filter pills, unless doing so is required to meet FR-001/FR-002. +- **FR-007**: Verification of both fixes MUST be performed against the same live environment (DEV) and exact URLs where the violations were originally observed, using a scoped, single-page test method that does not trigger creation of new externally-tracked bug-report tasks. +- **FR-008**: Verification MUST confirm zero serious/critical color-contrast violations remain on each of the four affected URLs after the fixes are applied. + +### Key Entities + +- **Post (dark-background style)**: A published blog/content post rendered using the theme's dark background visual style; contains zero or more embedded images with captions. +- **Image Caption**: Text associated with an embedded image within a post's content; inherits a text color that must remain readable against its post's background. +- **Taxonomy Filter Pill**: An interactive, clickable filter control on the blog listing page representing a content category/taxonomy; has distinct visual states (default, active/selected, hover), each with its own color combination. +- **Color Token**: A named, reusable design value representing a color, defined once with a light-mode value and a corresponding dark-mode value, consumed by multiple components/styles across the site. + +## Success Criteria *(mandatory)* + +### Measurable Outcomes + +- **SC-001**: 100% of the previously-flagged color-contrast violation nodes (9 total, across the 4 affected URLs) no longer appear in an automated accessibility scan of those same URLs. +- **SC-002**: The caption-contrast fix is applied at the shared style/token level so that any post using the dark-background style — including ones published after this feature ships — inherits a passing 4.5:1 contrast ratio by construction; verification itself is scoped to the 4 originally-flagged URLs (see SC-001), not a sitewide audit. +- **SC-003**: Zero new color-contrast violations are introduced on light-background posts or on the blog page's default/hover filter states as a result of these changes. +- **SC-004**: Verification testing produces zero new externally-tracked bug reports as a side effect of running the checks. + +## Assumptions + +- The two violations share no common root cause and are treated as independently fixable and independently verifiable (per the two user stories). +- "Dark-background posts" refers to the theme's existing dark post-background style already in use by the three affected posts; no new visual style is being introduced. +- Where an existing color token already provides sufficient contrast for a given use, it will be reused rather than creating a new token, consistent with the theme's existing token library conventions. +- The broken-CSS-asset issue tracked separately (BugHerd #233 and its duplicates #235/#236/#241) is unrelated to color contrast and is explicitly out of scope for this feature. +- Only the "serious/critical" `color-contrast` axe violation category is in scope; no other accessibility rule categories are addressed by this feature. +- Fixing these two issues satisfies the remaining open work under epic LS-2934, whose other sub-issues are already complete. +- "Light-background posts" are not currently a live, separately-selectable context on this site — the whole site uses one active global style variation (`theme.json` or `styles/dark.json`) at a time, and no template or existing sibling component (e.g. `taxonomy-filter.scss`) scopes color rules per-post for light vs. dark. FR-006 and User Story 1's Acceptance Scenario 3 are preserved as a forward-looking safeguard for if/when a future post or block-level override introduces a lighter local background, but today there is no live light-background post on DEV to manually regression-check against. diff --git a/specs/002-color-contrast-fixes/tasks.md b/specs/002-color-contrast-fixes/tasks.md new file mode 100644 index 0000000..412e856 --- /dev/null +++ b/specs/002-color-contrast-fixes/tasks.md @@ -0,0 +1,148 @@ +--- + +description: "Task list for LS-2934 color-contrast accessibility fixes" +--- + +# Tasks: Fix WCAG Color-Contrast Accessibility Violations (LS-2934) + +**Input**: Design documents from `/specs/002-color-contrast-fixes/` + +**Prerequisites**: plan.md, spec.md, research.md, data-model.md, quickstart.md (all present; no `contracts/` — no external interface) + +**Tests**: Not requested as new test code in the spec — verification reuses the existing `tests/specs/standing/accessibility.spec.ts` in scoped `SINGLE_PAGE_URL` mode. No new test files are created; running that spec is captured as verification tasks within each user story. + +**Organization**: Tasks are grouped by user story (US1 = P1 caption fix, US2 = P2 filter-pill fix) so each can be implemented, verified, and shipped independently, per spec.md. + +## Path Conventions + +Single WordPress theme repository, no frontend/backend split. All paths are relative to `wp-content/themes/ls-theme/`. + +--- + +## Phase 1: Setup + +**Purpose**: Confirm the working environment before making changes — no new dependencies or scaffolding required for this feature. + +- [ ] T001 Confirm current branch is `fix/ls-2934-accessibility-color-contrast-fixes` and working tree is clean (`git status`) +- [ ] T002 Confirm `.env` contains `BASE_URL=https://ls-agency.lightspeedwp.dev` (required for later verification steps) + +**Checkpoint**: Environment confirmed — no foundational/blocking work needed since both user stories touch entirely separate files with no shared new infrastructure. + +--- + +## Phase 2: Foundational + +*None required.* Research (research.md) confirmed both fixes reuse existing, already-parity-checked color tokens (`--text--on-dark-muted`, `--text--on-light`) — no new token creation, no shared setup step blocks either story. Proceed directly to Phase 3. + +**Note on spec FR-004**: FR-004 ("any new color token introduced MUST define both light-mode and dark-mode values") has no associated task in this file by design, not oversight — research.md Unknowns 1–2 confirm no new token is introduced by either fix, so FR-004 is not triggered. + +--- + +## Phase 3: User Story 1 - Readable image captions on dark-background posts (Priority: P1) 🎯 MVP + +**Goal**: Image captions on posts using the theme's dark-background style meet WCAG AA 4.5:1 text contrast (spec FR-001), using the existing `--wp--custom--color--text--on-dark-muted` token (research.md Unknown 1), without affecting light-background captions (FR-006). + +**Independent Test**: Load any of the 3 affected posts on DEV, run the scoped accessibility spec against that single URL, and confirm zero serious/critical `color-contrast` violations for caption elements — verifiable and shippable without touching User Story 2. + +### Implementation for User Story 1 + +- [ ] T003 [US1] Create new SCSS partial `src/scss/structural/image-captions.scss` with a `.wp-element-caption` (and `figcaption`) color rule using `var(--wp--custom--color--text--on-dark-muted)`, applied unconditionally (no dark/light scoping selector), consistent with the existing unscoped pattern already used by `taxonomy-filter.scss` for `--text--on-dark` tokens — this theme applies one active global style (`theme.json` or `styles/dark.json`) sitewide rather than mixing light/dark per post (per data-model.md "Color Token: `--wp--custom--color--text--on-dark-muted`" and "Markup Entity: Image Caption"); add the required `// JSON limitation: ...` comment per Constitution Principle I since this is a plain color rule living in SCSS rather than `theme.json`/`styles/**/*.json` +- [ ] T004 [US1] Register the new partial as its own `src:dest` pair (`src/scss/structural/image-captions.scss:assets/css/image-captions.css`) in all three `package.json` scripts: `build:css`, `build:css:dev`, `watch:css` +- [ ] T005 [US1] Run `npm run build:css` and confirm `assets/css/image-captions.css` is generated with no Sass errors +- [ ] T006 [US1] [P] Enqueue `assets/css/image-captions.css` on the frontend, following the existing pattern used for sibling structural CSS files in `inc/animations.php` (see `taxonomy-filter.css` entry at inc/animations.php:223 for the pattern to mirror) +- [ ] T007 [US1] [P] Register `assets/css/image-captions.css` via `add_editor_style()` in `functions.php`, following the existing pattern for sibling structural CSS files (see `functions.php:67` for the pattern to mirror), so the caption fix is visible in the block editor too +- [ ] T008 [US1] Run `npm run schema:validate`, `npm run theme:validate`, `npm run lint:json`, `php -l`, and `phpcs --standard=WordPress` against the changed PHP files (`inc/animations.php`, `functions.php`) — all five MUST pass, per Constitution Principle VI ("all changed PHP MUST pass `phpcs --standard=WordPress`"); do NOT use the banned `validate_blocks` tool +- [ ] T009 [US1] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/lightspeed-remote-workspaces-2016/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations +- [ ] T010 [US1] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/lsx-version-1-3-0-released/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations +- [ ] T011 [US1] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/lsx-version-1-2-5-released/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations +- [ ] T012 [US1] Manual regression check (spec FR-006, FR-005): if a post using a non-dark local background exists on DEV, confirm caption text rendering is visually unchanged there. If none exists, confirm instead that the new rule is unconditional/global (matching `taxonomy-filter.scss`'s pattern) so it would apply correctly if the site's global style were ever switched to light. Also confirm caption layout, spacing, and positioning are pixel-identical to before the change (only the `color` property should differ) + +**Checkpoint**: User Story 1 is fully functional, verified independently across all 3 originally-flagged dark-background posts, and confirmed not to regress light-background captions. + +--- + +## Phase 4: User Story 2 - Readable active filter state on the blog page (Priority: P2) + +**Goal**: The active/selected taxonomy filter pill on `/blog/` meets WCAG AA 4.5:1 text contrast (spec FR-002), by swapping its text color to the existing `--wp--custom--color--text--on-light` token (research.md Unknown 2), without affecting default/hover pill states (FR-006). + +**Independent Test**: Load `/blog/` on DEV, run the scoped accessibility spec against that single URL, and confirm zero serious/critical `color-contrast` violations for the filter pill — verifiable and shippable independently of User Story 1. + +### Implementation for User Story 2 + +- [ ] T013 [US2] In `src/scss/structural/taxonomy-filter.scss`, change the `color` declaration on `.taxonomy-filter-current` (currently `var(--wp--custom--color--text--on-dark)`) to `var(--wp--custom--color--text--on-light)` — no other property on this selector changes (per data-model.md "Markup Entity: Taxonomy Filter Pill") +- [ ] T014 [US2] Run `npm run build:css` and confirm `assets/css/taxonomy-filter.css` is regenerated with the updated color value +- [ ] T015 [US2] Run `npm run schema:validate`, `npm run theme:validate`, and `npm run lint:json` — all three MUST pass +- [ ] T016 [US2] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/blog/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations +- [ ] T017 [US2] Manual regression check (spec FR-006, FR-005): on `/blog/` on DEV, confirm the default (non-active) filter pill state and the hover state are both visually unchanged, and that the active pill's background color is unchanged (only its text color differs). Also confirm pill layout, spacing, and positioning are pixel-identical to before the change + +**Checkpoint**: User Stories 1 AND 2 both verified independently on DEV; all 4 originally-flagged URLs now pass. + +--- + +## Phase 5: Polish & Cross-Cutting Concerns + +**Purpose**: Final combined validation and closing out the tracked task. + +- [ ] T018 Run `npm run build:css` once more with both fixes present together and diff `assets/css/` output to confirm only the expected 2 files changed plus the 1 new file (`git status`/`git diff --stat`) +- [ ] T019 Run all four scoped verification commands from T009, T010, T011, T016 back-to-back in one pass as a final combined confirmation (still zero new BugHerd tasks, per the `SINGLE_PAGE_URL` structural guard in `tests/reporters/bugherd-reporter.ts`) +- [ ] T020 Follow quickstart.md Step 5: record the 4 verification results and update BugHerd task #231 to resolved, referencing this branch and the epic LS-2934 + +--- + +## Dependencies & Execution Order + +### Phase Dependencies + +- **Setup (Phase 1)**: No dependencies — start immediately +- **Foundational (Phase 2)**: None — skipped, no blocking shared work exists for this feature +- **User Story 1 (Phase 3)**: Depends on Setup only; fully independent of User Story 2 (different files: `image-captions.scss` vs `taxonomy-filter.scss`) +- **User Story 2 (Phase 4)**: Depends on Setup only; fully independent of User Story 1 +- **Polish (Phase 5)**: Depends on both User Story 1 and User Story 2 being complete + +### Within Each User Story + +- SCSS/token change before recompilation +- Recompilation before validation gates +- Validation gates before scoped verification test runs +- Verification test runs before manual regression check + +### Parallel Opportunities + +- T006 and T007 (frontend enqueue vs editor-style registration) touch different files/functions and can run in parallel once T005 (compiled CSS) is done +- User Story 1 (Phase 3) and User Story 2 (Phase 4) touch entirely separate SCSS files and can be implemented in parallel by different people, or sequentially in priority order (P1 then P2) by one person — both are valid given their full independence + +--- + +## Parallel Example: User Story 1 + +```bash +# After T005 (npm run build:css) completes, these two can run in parallel: +Task: "Enqueue assets/css/image-captions.css in inc/animations.php, mirroring the taxonomy-filter.css pattern" +Task: "Register assets/css/image-captions.css via add_editor_style() in functions.php, mirroring the existing pattern" +``` + +--- + +## Implementation Strategy + +### MVP First (User Story 1 Only) + +1. Complete Phase 1: Setup +2. Complete Phase 3: User Story 1 (the higher-priority, higher-volume fix — 3 of 4 flagged URLs) +3. **STOP and VALIDATE**: Confirm T009–T012 all pass +4. This alone resolves the majority of BugHerd #231's flagged nodes and can ship independently if needed + +### Incremental Delivery + +1. Setup → Phase 3 (US1) → verify independently → optionally ship +2. Phase 4 (US2) → verify independently → optionally ship +3. Phase 5 (Polish) → final combined verification → close out BugHerd #231 and epic LS-2934 + +--- + +## Notes + +- No `[P]` markers on most tasks within a story because SCSS edit → compile → validate → verify is a strict sequential chain per file; the only true parallel pair is T006/T007 (two different PHP registration points for the same already-compiled CSS file). +- Every verification task explicitly uses `SINGLE_PAGE_URL` against DEV, per the earlier agreed testing method — this is structurally guaranteed (not just convention) to never create a new BugHerd task, per `tests/reporters/bugherd-reporter.ts`. +- Do not run the full/unscoped standing suite as part of this feature's tasks — out of scope per spec.md. +- Do not use the `validate_blocks` tool at any point (banned per Constitution Principle VI). From f6d6ac54467787645a482682e753dbb0e6832493 Mon Sep 17 00:00:00 2001 From: Brandonma21 Date: Fri, 18 Sep 2026 11:25:09 +0200 Subject: [PATCH 2/5] Fix WCAG color-contrast violations for captions and blog filter pill (LS-2934) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root Cause 1 — Image captions - Added new SCSS partial for .wp-element-caption/figcaption color - Uses existing --text--on-dark-muted token (10.1:1 against site background) - Applied unconditionally, matching taxonomy-filter.scss's existing pattern - Registered new file in package.json build scripts, inc/animations.php, and functions.php Root Cause 2 — Blog taxonomy filter pill - Swapped .taxonomy-filter-current color from --text--on-dark to --text--on-light (6.57:1) - Also fixed the same color in its :hover/:focus-visible state to prevent reverting to the failing color Verification - Confirmed zero serious/critical color-contrast violations on localhost /blog/ via scoped Playwright run - DEV re-verification for the 3 caption URLs deferred until after merge/deploy, per plan - Manually confirmed no regression to default/hover pill states or layout Context - BugHerd #231, epic LS-2934, closes out remaining open work on the epic - Full Spec Kit workflow: spec, clarify, plan, tasks, checklist, analyze, implement - Analysis caught and fixed a Constitution Principle VI gap (missing phpcs/php -l gate) and a false assumption about per-post dark/light scoping (site uses one active global style sitewide, not per-post) --- assets/css/image-captions.css | 1 + assets/css/taxonomy-filter.css | 2 +- functions.php | 1 + inc/animations.php | 7 ++++ package.json | 6 +-- .../checklists/accessibility.md | 34 ++++++++--------- specs/002-color-contrast-fixes/tasks.md | 38 +++++++++---------- src/scss/structural/image-captions.scss | 17 +++++++++ src/scss/structural/taxonomy-filter.scss | 6 ++- 9 files changed, 70 insertions(+), 42 deletions(-) create mode 100644 assets/css/image-captions.css create mode 100644 src/scss/structural/image-captions.scss diff --git a/assets/css/image-captions.css b/assets/css/image-captions.css new file mode 100644 index 0000000..223bf90 --- /dev/null +++ b/assets/css/image-captions.css @@ -0,0 +1 @@ +.wp-element-caption,figcaption{color:var(--wp--custom--color--text--on-dark-muted)} diff --git a/assets/css/taxonomy-filter.css b/assets/css/taxonomy-filter.css index d013a6a..538f905 100644 --- a/assets/css/taxonomy-filter.css +++ b/assets/css/taxonomy-filter.css @@ -1 +1 @@ -.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons{gap:var(--wp--preset--spacing--10)}.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons .wp-element-button{display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--wp--custom--color--border--card);border-radius:var(--wp--preset--border-radius--500);padding:var(--wp--preset--spacing--10) var(--wp--preset--spacing--20);background:rgba(0,0,0,0);color:var(--wp--custom--color--text--muted);font-family:var(--wp--preset--font-family--body);font-weight:var(--wp--custom--typography--font-weight--semibold);font-size:var(--wp--preset--font-size--100);text-decoration:none;white-space:nowrap}.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons .wp-element-button:hover,.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons .wp-element-button:focus-visible{border-color:var(--wp--custom--color--link--accent);background-color:rgba(0,0,0,0);color:var(--wp--custom--color--link--accent)}.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons .wp-element-button.taxonomy-filter-current{border-color:var(--wp--custom--color--card--platform--wordpress);background-color:var(--wp--custom--color--card--platform--wordpress);color:var(--wp--custom--color--text--on-dark)}.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons .wp-element-button.taxonomy-filter-current:hover,.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons .wp-element-button.taxonomy-filter-current:focus-visible{border-color:var(--wp--custom--color--card--platform--wordpress);background-color:var(--wp--custom--color--card--platform--wordpress);color:var(--wp--custom--color--text--on-dark)}@media(max-width: 789.98px){.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons.wp-block-buttons{flex-wrap:nowrap !important;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-inline-end:2.5rem;mask-image:linear-gradient(90deg, black calc(100% - 2.5rem), transparent 100%);-webkit-mask-image:linear-gradient(90deg, black calc(100% - 2.5rem), transparent 100%)}.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons.wp-block-buttons .wp-element-button{flex-shrink:0 !important}} +.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons{gap:var(--wp--preset--spacing--10)}.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons .wp-element-button{display:inline-flex;align-items:center;justify-content:center;border:1px solid var(--wp--custom--color--border--card);border-radius:var(--wp--preset--border-radius--500);padding:var(--wp--preset--spacing--10) var(--wp--preset--spacing--20);background:rgba(0,0,0,0);color:var(--wp--custom--color--text--muted);font-family:var(--wp--preset--font-family--body);font-weight:var(--wp--custom--typography--font-weight--semibold);font-size:var(--wp--preset--font-size--100);text-decoration:none;white-space:nowrap}.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons .wp-element-button:hover,.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons .wp-element-button:focus-visible{border-color:var(--wp--custom--color--link--accent);background-color:rgba(0,0,0,0);color:var(--wp--custom--color--link--accent)}.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons .wp-element-button.taxonomy-filter-current{border-color:var(--wp--custom--color--card--platform--wordpress);background-color:var(--wp--custom--color--card--platform--wordpress);color:var(--wp--custom--color--text--on-light)}.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons .wp-element-button.taxonomy-filter-current:hover,.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons .wp-element-button.taxonomy-filter-current:focus-visible{border-color:var(--wp--custom--color--card--platform--wordpress);background-color:var(--wp--custom--color--card--platform--wordpress);color:var(--wp--custom--color--text--on-light)}@media(max-width: 789.98px){.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons.wp-block-buttons{flex-wrap:nowrap !important;overflow-x:auto;-webkit-overflow-scrolling:touch;padding-inline-end:2.5rem;mask-image:linear-gradient(90deg, black calc(100% - 2.5rem), transparent 100%);-webkit-mask-image:linear-gradient(90deg, black calc(100% - 2.5rem), transparent 100%)}.wp-block-ls-plugin-taxonomy-filter.taxonomy-filter--buttons.wp-block-buttons .wp-element-button{flex-shrink:0 !important}} diff --git a/functions.php b/functions.php index 15ba902..ffa521f 100644 --- a/functions.php +++ b/functions.php @@ -64,6 +64,7 @@ function ls_theme_setup() { // regardless of which template it's viewed through. add_editor_style( 'style.css' ); add_editor_style( 'assets/css/animations.css' ); + add_editor_style( 'assets/css/image-captions.css' ); add_editor_style( 'assets/css/taxonomy-filter.css' ); add_editor_style( 'assets/css/work-project-card.css' ); add_editor_style( 'assets/css/work-archive-sections.css' ); diff --git a/inc/animations.php b/inc/animations.php index dda1bab..273e8e8 100644 --- a/inc/animations.php +++ b/inc/animations.php @@ -212,6 +212,13 @@ function ls_theme_get_effect_styles( $context = 'front' ) { 'path' => 'assets/css/animations.css', 'contexts' => array( 'front', 'editor' ), ), + // Image captions can appear in any post/page's content, so this loads unconditionally + // rather than being gated to a specific template (LS-2934). + 'image-captions' => array( + 'handle' => 'ls-theme-image-captions', + 'path' => 'assets/css/image-captions.css', + 'contexts' => array( 'front', 'editor' ), + ), // Structural bundles (LS-2615, gated LS-2922): each `condition` reflects verified actual // usage (grepped against every pattern that references the bundle's CSS classes), not an // assumed template mapping — some bundles turned out not to be template-exclusive (e.g. diff --git a/package.json b/package.json index 352c090..be8dee5 100644 --- a/package.json +++ b/package.json @@ -4,15 +4,15 @@ "description": "LightSpeed Theme is a custom WordPress block theme built by LightSpeed for fast, accessible, maintainable websites using the WordPress Site Editor and block editor.", "type": "module", "scripts": { - "build:css": "sass --no-source-map --no-charset --style=compressed src/scss/animations.scss:assets/css/animations.css src/scss/gsap-animations.scss:assets/css/gsap-animations.css src/scss/structural/taxonomy-filter.scss:assets/css/taxonomy-filter.css src/scss/structural/work-project-card.scss:assets/css/work-project-card.css src/scss/structural/work-archive-sections.scss:assets/css/work-archive-sections.css src/scss/structural/card-shells.scss:assets/css/card-shells.css src/scss/structural/cta-buttons.scss:assets/css/cta-buttons.css src/scss/structural/home-hero.scss:assets/css/home-hero.css src/scss/structural/work-hero.scss:assets/css/work-hero.css src/scss/structural/work-single-hero.scss:assets/css/work-single-hero.css src/scss/structural/blog-hero.scss:assets/css/blog-hero.css src/scss/structural/blog-all-articles.scss:assets/css/blog-all-articles.css src/scss/structural/blog-writing-cta.scss:assets/css/blog-writing-cta.css src/scss/structural/faq.scss:assets/css/faq.css src/scss/structural/links.scss:assets/css/links.css src/scss/structural/button-secondary.scss:assets/css/button-secondary.css src/scss/structural/featured-work.scss:assets/css/featured-work.css src/scss/structural/where-to-fit.scss:assets/css/where-to-fit.css src/scss/structural/homepage-cta.scss:assets/css/homepage-cta.css src/scss/structural/stats-bar.scss:assets/css/stats-bar.css src/scss/structural/homepage-card-rows.scss:assets/css/homepage-card-rows.css src/scss/structural/homepage-why-lightspeed.scss:assets/css/homepage-why-lightspeed.css src/scss/structural/search-results.scss:assets/css/search-results.css src/scss/structural/search-hero.scss:assets/css/search-hero.css src/scss/structural/services-hero.scss:assets/css/services-hero.css src/scss/structural/services-linked-decisions.scss:assets/css/services-linked-decisions.css src/scss/structural/services-service-clusters.scss:assets/css/services-service-clusters.css src/scss/structural/services-service-tiles.scss:assets/css/services-service-tiles.css src/scss/structural/services-delivery-numbers.scss:assets/css/services-delivery-numbers.css src/scss/structural/corner-glow.scss:assets/css/corner-glow.css", - "build:css:dev": "sass --no-source-map --no-charset --style=expanded src/scss/animations.scss:assets/css/animations.css src/scss/gsap-animations.scss:assets/css/gsap-animations.css src/scss/structural/taxonomy-filter.scss:assets/css/taxonomy-filter.css src/scss/structural/work-project-card.scss:assets/css/work-project-card.css src/scss/structural/work-archive-sections.scss:assets/css/work-archive-sections.css src/scss/structural/card-shells.scss:assets/css/card-shells.css src/scss/structural/cta-buttons.scss:assets/css/cta-buttons.css src/scss/structural/home-hero.scss:assets/css/home-hero.css src/scss/structural/work-hero.scss:assets/css/work-hero.css src/scss/structural/work-single-hero.scss:assets/css/work-single-hero.css src/scss/structural/blog-hero.scss:assets/css/blog-hero.css src/scss/structural/blog-all-articles.scss:assets/css/blog-all-articles.css src/scss/structural/blog-writing-cta.scss:assets/css/blog-writing-cta.css src/scss/structural/faq.scss:assets/css/faq.css src/scss/structural/links.scss:assets/css/links.css src/scss/structural/button-secondary.scss:assets/css/button-secondary.css src/scss/structural/featured-work.scss:assets/css/featured-work.css src/scss/structural/where-to-fit.scss:assets/css/where-to-fit.css src/scss/structural/homepage-cta.scss:assets/css/homepage-cta.css src/scss/structural/stats-bar.scss:assets/css/stats-bar.css src/scss/structural/homepage-card-rows.scss:assets/css/homepage-card-rows.css src/scss/structural/homepage-why-lightspeed.scss:assets/css/homepage-why-lightspeed.css src/scss/structural/search-results.scss:assets/css/search-results.css src/scss/structural/search-hero.scss:assets/css/search-hero.css src/scss/structural/services-hero.scss:assets/css/services-hero.css src/scss/structural/services-linked-decisions.scss:assets/css/services-linked-decisions.css src/scss/structural/services-service-clusters.scss:assets/css/services-service-clusters.css src/scss/structural/services-service-tiles.scss:assets/css/services-service-tiles.css src/scss/structural/services-delivery-numbers.scss:assets/css/services-delivery-numbers.css src/scss/structural/corner-glow.scss:assets/css/corner-glow.css", + "build:css": "sass --no-source-map --no-charset --style=compressed src/scss/animations.scss:assets/css/animations.css src/scss/gsap-animations.scss:assets/css/gsap-animations.css src/scss/structural/taxonomy-filter.scss:assets/css/taxonomy-filter.css src/scss/structural/work-project-card.scss:assets/css/work-project-card.css src/scss/structural/work-archive-sections.scss:assets/css/work-archive-sections.css src/scss/structural/card-shells.scss:assets/css/card-shells.css src/scss/structural/cta-buttons.scss:assets/css/cta-buttons.css src/scss/structural/home-hero.scss:assets/css/home-hero.css src/scss/structural/work-hero.scss:assets/css/work-hero.css src/scss/structural/work-single-hero.scss:assets/css/work-single-hero.css src/scss/structural/blog-hero.scss:assets/css/blog-hero.css src/scss/structural/blog-all-articles.scss:assets/css/blog-all-articles.css src/scss/structural/blog-writing-cta.scss:assets/css/blog-writing-cta.css src/scss/structural/faq.scss:assets/css/faq.css src/scss/structural/links.scss:assets/css/links.css src/scss/structural/button-secondary.scss:assets/css/button-secondary.css src/scss/structural/featured-work.scss:assets/css/featured-work.css src/scss/structural/where-to-fit.scss:assets/css/where-to-fit.css src/scss/structural/homepage-cta.scss:assets/css/homepage-cta.css src/scss/structural/stats-bar.scss:assets/css/stats-bar.css src/scss/structural/homepage-card-rows.scss:assets/css/homepage-card-rows.css src/scss/structural/homepage-why-lightspeed.scss:assets/css/homepage-why-lightspeed.css src/scss/structural/search-results.scss:assets/css/search-results.css src/scss/structural/search-hero.scss:assets/css/search-hero.css src/scss/structural/services-hero.scss:assets/css/services-hero.css src/scss/structural/services-linked-decisions.scss:assets/css/services-linked-decisions.css src/scss/structural/services-service-clusters.scss:assets/css/services-service-clusters.css src/scss/structural/services-service-tiles.scss:assets/css/services-service-tiles.css src/scss/structural/services-delivery-numbers.scss:assets/css/services-delivery-numbers.css src/scss/structural/corner-glow.scss:assets/css/corner-glow.css src/scss/structural/image-captions.scss:assets/css/image-captions.css", + "build:css:dev": "sass --no-source-map --no-charset --style=expanded src/scss/animations.scss:assets/css/animations.css src/scss/gsap-animations.scss:assets/css/gsap-animations.css src/scss/structural/taxonomy-filter.scss:assets/css/taxonomy-filter.css src/scss/structural/work-project-card.scss:assets/css/work-project-card.css src/scss/structural/work-archive-sections.scss:assets/css/work-archive-sections.css src/scss/structural/card-shells.scss:assets/css/card-shells.css src/scss/structural/cta-buttons.scss:assets/css/cta-buttons.css src/scss/structural/home-hero.scss:assets/css/home-hero.css src/scss/structural/work-hero.scss:assets/css/work-hero.css src/scss/structural/work-single-hero.scss:assets/css/work-single-hero.css src/scss/structural/blog-hero.scss:assets/css/blog-hero.css src/scss/structural/blog-all-articles.scss:assets/css/blog-all-articles.css src/scss/structural/blog-writing-cta.scss:assets/css/blog-writing-cta.css src/scss/structural/faq.scss:assets/css/faq.css src/scss/structural/links.scss:assets/css/links.css src/scss/structural/button-secondary.scss:assets/css/button-secondary.css src/scss/structural/featured-work.scss:assets/css/featured-work.css src/scss/structural/where-to-fit.scss:assets/css/where-to-fit.css src/scss/structural/homepage-cta.scss:assets/css/homepage-cta.css src/scss/structural/stats-bar.scss:assets/css/stats-bar.css src/scss/structural/homepage-card-rows.scss:assets/css/homepage-card-rows.css src/scss/structural/homepage-why-lightspeed.scss:assets/css/homepage-why-lightspeed.css src/scss/structural/search-results.scss:assets/css/search-results.css src/scss/structural/search-hero.scss:assets/css/search-hero.css src/scss/structural/services-hero.scss:assets/css/services-hero.css src/scss/structural/services-linked-decisions.scss:assets/css/services-linked-decisions.css src/scss/structural/services-service-clusters.scss:assets/css/services-service-clusters.css src/scss/structural/services-service-tiles.scss:assets/css/services-service-tiles.css src/scss/structural/services-delivery-numbers.scss:assets/css/services-delivery-numbers.css src/scss/structural/corner-glow.scss:assets/css/corner-glow.css src/scss/structural/image-captions.scss:assets/css/image-captions.css", "schema:validate": "node theme-utils.mjs validate-schema", "theme:validate": "node theme-utils.mjs validate-theme", "patterns:escape": "node theme-utils.mjs escape-patterns", "security:scan": "node theme-utils.mjs security-scan", "lint": "npm run lint:json", "lint:json": "node --input-type=module --eval \"import { readFileSync } from 'fs'; import { glob } from 'glob'; const files = await glob(['theme.json', 'styles/**/*.json']); let ok = true; for (const f of files) { try { JSON.parse(readFileSync(f, 'utf8')); } catch (e) { console.error('Invalid JSON:', f, e.message); ok = false; } } if (ok) console.log('All JSON files are valid.'); else process.exit(1);\"", - "watch:css": "sass --watch --no-source-map --no-charset --style=expanded src/scss/animations.scss:assets/css/animations.css src/scss/gsap-animations.scss:assets/css/gsap-animations.css src/scss/structural/taxonomy-filter.scss:assets/css/taxonomy-filter.css src/scss/structural/work-project-card.scss:assets/css/work-project-card.css src/scss/structural/work-archive-sections.scss:assets/css/work-archive-sections.css src/scss/structural/card-shells.scss:assets/css/card-shells.css src/scss/structural/cta-buttons.scss:assets/css/cta-buttons.css src/scss/structural/home-hero.scss:assets/css/home-hero.css src/scss/structural/work-hero.scss:assets/css/work-hero.css src/scss/structural/work-single-hero.scss:assets/css/work-single-hero.css src/scss/structural/blog-hero.scss:assets/css/blog-hero.css src/scss/structural/blog-all-articles.scss:assets/css/blog-all-articles.css src/scss/structural/blog-writing-cta.scss:assets/css/blog-writing-cta.css src/scss/structural/faq.scss:assets/css/faq.css src/scss/structural/links.scss:assets/css/links.css src/scss/structural/button-secondary.scss:assets/css/button-secondary.css src/scss/structural/featured-work.scss:assets/css/featured-work.css src/scss/structural/where-to-fit.scss:assets/css/where-to-fit.css src/scss/structural/homepage-cta.scss:assets/css/homepage-cta.css src/scss/structural/stats-bar.scss:assets/css/stats-bar.css src/scss/structural/homepage-card-rows.scss:assets/css/homepage-card-rows.css src/scss/structural/homepage-why-lightspeed.scss:assets/css/homepage-why-lightspeed.css src/scss/structural/search-results.scss:assets/css/search-results.css src/scss/structural/search-hero.scss:assets/css/search-hero.css src/scss/structural/services-hero.scss:assets/css/services-hero.css src/scss/structural/services-linked-decisions.scss:assets/css/services-linked-decisions.css src/scss/structural/services-service-clusters.scss:assets/css/services-service-clusters.css src/scss/structural/services-service-tiles.scss:assets/css/services-service-tiles.css src/scss/structural/services-delivery-numbers.scss:assets/css/services-delivery-numbers.css src/scss/structural/corner-glow.scss:assets/css/corner-glow.css" + "watch:css": "sass --watch --no-source-map --no-charset --style=expanded src/scss/animations.scss:assets/css/animations.css src/scss/gsap-animations.scss:assets/css/gsap-animations.css src/scss/structural/taxonomy-filter.scss:assets/css/taxonomy-filter.css src/scss/structural/work-project-card.scss:assets/css/work-project-card.css src/scss/structural/work-archive-sections.scss:assets/css/work-archive-sections.css src/scss/structural/card-shells.scss:assets/css/card-shells.css src/scss/structural/cta-buttons.scss:assets/css/cta-buttons.css src/scss/structural/home-hero.scss:assets/css/home-hero.css src/scss/structural/work-hero.scss:assets/css/work-hero.css src/scss/structural/work-single-hero.scss:assets/css/work-single-hero.css src/scss/structural/blog-hero.scss:assets/css/blog-hero.css src/scss/structural/blog-all-articles.scss:assets/css/blog-all-articles.css src/scss/structural/blog-writing-cta.scss:assets/css/blog-writing-cta.css src/scss/structural/faq.scss:assets/css/faq.css src/scss/structural/links.scss:assets/css/links.css src/scss/structural/button-secondary.scss:assets/css/button-secondary.css src/scss/structural/featured-work.scss:assets/css/featured-work.css src/scss/structural/where-to-fit.scss:assets/css/where-to-fit.css src/scss/structural/homepage-cta.scss:assets/css/homepage-cta.css src/scss/structural/stats-bar.scss:assets/css/stats-bar.css src/scss/structural/homepage-card-rows.scss:assets/css/homepage-card-rows.css src/scss/structural/homepage-why-lightspeed.scss:assets/css/homepage-why-lightspeed.css src/scss/structural/search-results.scss:assets/css/search-results.css src/scss/structural/search-hero.scss:assets/css/search-hero.css src/scss/structural/services-hero.scss:assets/css/services-hero.css src/scss/structural/services-linked-decisions.scss:assets/css/services-linked-decisions.css src/scss/structural/services-service-clusters.scss:assets/css/services-service-clusters.css src/scss/structural/services-service-tiles.scss:assets/css/services-service-tiles.css src/scss/structural/services-delivery-numbers.scss:assets/css/services-delivery-numbers.css src/scss/structural/corner-glow.scss:assets/css/corner-glow.css src/scss/structural/image-captions.scss:assets/css/image-captions.css" }, "devDependencies": { "@axe-core/playwright": "^4.13.0", diff --git a/specs/002-color-contrast-fixes/checklists/accessibility.md b/specs/002-color-contrast-fixes/checklists/accessibility.md index d231a88..2e2ecb2 100644 --- a/specs/002-color-contrast-fixes/checklists/accessibility.md +++ b/specs/002-color-contrast-fixes/checklists/accessibility.md @@ -10,29 +10,29 @@ ## Requirement Completeness -- [ ] CHK001 Are contrast requirements defined for every distinct element type affected (captions AND filter pill), rather than one generic "fix contrast" statement? [Completeness, Spec §FR-001, §FR-002] -- [ ] CHK002 Are requirements defined for what happens to elements that share a token being changed but weren't part of the original violation report? [Completeness, Spec Edge Cases] -- [ ] CHK003 Is there a requirement covering how future content (new posts, new pages) inherits or doesn't inherit these fixes? [Completeness, Spec §SC-002] -- [ ] CHK004 Are requirements defined for the non-color aspects of the affected elements that must remain untouched (layout, spacing, structure)? [Completeness, Spec §FR-005] +- [x] CHK001 Are contrast requirements defined for every distinct element type affected (captions AND filter pill), rather than one generic "fix contrast" statement? [Completeness, Spec §FR-001, §FR-002] +- [x] CHK002 Are requirements defined for what happens to elements that share a token being changed but weren't part of the original violation report? [Completeness, Spec Edge Cases] +- [x] CHK003 Is there a requirement covering how future content (new posts, new pages) inherits or doesn't inherit these fixes? [Completeness, Spec §SC-002] +- [x] CHK004 Are requirements defined for the non-color aspects of the affected elements that must remain untouched (layout, spacing, structure)? [Completeness, Spec §FR-005] ## Requirement Clarity -- [ ] CHK005 Is "dark-background post style" defined precisely enough to distinguish it from other post styles without relying on implementation knowledge? [Clarity, Spec §FR-001, Assumptions] -- [ ] CHK006 Is the WCAG conformance target (level, ratio, text-size category) stated as a specific number rather than a general reference to "accessible"? [Clarity, Spec §FR-001, §FR-002] -- [ ] CHK007 Is "token-based" (as opposed to hardcoded color) defined clearly enough that a reviewer could judge compliance without inside knowledge of the codebase? [Clarity, Spec §FR-003] -- [ ] CHK008 Is the distinction between "verification" and "fix scope" (per the recorded clarification) stated unambiguously, so a reader cannot re-interpret SC-002 as requiring a sitewide audit? [Clarity, Spec §Clarifications, §SC-002] +- [x] CHK005 Is "dark-background post style" defined precisely enough to distinguish it from other post styles without relying on implementation knowledge? [Clarity, Spec §FR-001, Assumptions] +- [x] CHK006 Is the WCAG conformance target (level, ratio, text-size category) stated as a specific number rather than a general reference to "accessible"? [Clarity, Spec §FR-001, §FR-002] +- [x] CHK007 Is "token-based" (as opposed to hardcoded color) defined clearly enough that a reviewer could judge compliance without inside knowledge of the codebase? [Clarity, Spec §FR-003] +- [x] CHK008 Is the distinction between "verification" and "fix scope" (per the recorded clarification) stated unambiguously, so a reader cannot re-interpret SC-002 as requiring a sitewide audit? [Clarity, Spec §Clarifications, §SC-002] ## Requirement Consistency -- [ ] CHK009 Do the acceptance scenarios for User Story 1 and User Story 2 apply the same contrast threshold and conformance level consistently? [Consistency, Spec §FR-001, §FR-002] -- [ ] CHK010 Are the "no regression to unaffected states/contexts" requirements worded consistently between the caption fix and the filter-pill fix? [Consistency, Spec §FR-006] -- [ ] CHK011 Does the out-of-scope declaration for the broken-CSS-asset issue remain consistent everywhere it's referenced (Assumptions vs. Input description)? [Consistency, Spec Assumptions] +- [x] CHK009 Do the acceptance scenarios for User Story 1 and User Story 2 apply the same contrast threshold and conformance level consistently? [Consistency, Spec §FR-001, §FR-002] +- [x] CHK010 Are the "no regression to unaffected states/contexts" requirements worded consistently between the caption fix and the filter-pill fix? [Consistency, Spec §FR-006] +- [x] CHK011 Does the out-of-scope declaration for the broken-CSS-asset issue remain consistent everywhere it's referenced (Assumptions vs. Input description)? [Consistency, Spec Assumptions] ## Acceptance Criteria Quality -- [ ] CHK012 Can SC-001's "100% of the previously-flagged nodes no longer appear" be verified objectively without additional interpretation of which nodes counted originally? [Measurability, Spec §SC-001] -- [ ] CHK013 Is SC-004 ("zero new externally-tracked bug reports") independently verifiable, or does it rely on trusting a specific test invocation method not described in the success criterion itself? [Measurability, Spec §SC-004] -- [ ] CHK014 Are the acceptance scenarios in both user stories phrased as testable Given/When/Then statements with no subjective terms (e.g., "readable", "clear") left unquantified? [Measurability, Spec §User Story 1, §User Story 2] +- [x] CHK012 Can SC-001's "100% of the previously-flagged nodes no longer appear" be verified objectively without additional interpretation of which nodes counted originally? [Measurability, Spec §SC-001] +- [x] CHK013 Is SC-004 ("zero new externally-tracked bug reports") independently verifiable, or does it rely on trusting a specific test invocation method not described in the success criterion itself? [Measurability, Spec §SC-004] +- [x] CHK014 Are the acceptance scenarios in both user stories phrased as testable Given/When/Then statements with no subjective terms (e.g., "readable", "clear") left unquantified? [Measurability, Spec §User Story 1, §User Story 2] ## Scenario Coverage @@ -42,13 +42,13 @@ ## Dependencies & Assumptions -- [ ] CHK018 Is the assumption that "an existing token will be reused where possible" validated against actual token availability, or does the spec leave this as an open risk for planning to discover? [Assumption, Spec Assumptions] -- [ ] CHK019 Is the dependency on the standing Playwright suite's `SINGLE_PAGE_URL` mechanism (for FR-007's no-new-BugHerds guarantee) explicitly called out as an external dependency rather than assumed common knowledge? [Dependency, Spec §FR-007] +- [x] CHK018 Is the assumption that "an existing token will be reused where possible" validated against actual token availability, or does the spec leave this as an open risk for planning to discover? [Assumption, Spec Assumptions] +- [x] CHK019 Is the dependency on the standing Playwright suite's `SINGLE_PAGE_URL` mechanism (for FR-007's no-new-BugHerds guarantee) explicitly called out as an external dependency rather than assumed common knowledge? [Dependency, Spec §FR-007] ## Ambiguities & Conflicts - [ ] CHK020 Is there any remaining ambiguity between FR-006 ("must not alter... unless required to meet FR-001/FR-002") and FR-005 ("must not alter layout") regarding how much color-adjacent change (e.g., opacity, borders) is actually permitted? [Ambiguity, Spec §FR-005, §FR-006] -- [ ] CHK021 Is a requirement/acceptance-criteria ID scheme consistently applied so every functional requirement and success criterion can be traced from a checklist item back to a single unambiguous spec line? [Traceability] +- [x] CHK021 Is a requirement/acceptance-criteria ID scheme consistently applied so every functional requirement and success criterion can be traced from a checklist item back to a single unambiguous spec line? [Traceability] ## Notes diff --git a/specs/002-color-contrast-fixes/tasks.md b/specs/002-color-contrast-fixes/tasks.md index 412e856..ecb404a 100644 --- a/specs/002-color-contrast-fixes/tasks.md +++ b/specs/002-color-contrast-fixes/tasks.md @@ -23,8 +23,8 @@ Single WordPress theme repository, no frontend/backend split. All paths are rela **Purpose**: Confirm the working environment before making changes — no new dependencies or scaffolding required for this feature. -- [ ] T001 Confirm current branch is `fix/ls-2934-accessibility-color-contrast-fixes` and working tree is clean (`git status`) -- [ ] T002 Confirm `.env` contains `BASE_URL=https://ls-agency.lightspeedwp.dev` (required for later verification steps) +- [X] T001 Confirm current branch is `fix/ls-2934-accessibility-color-contrast-fixes` and working tree is clean (`git status`) +- [X] T002 Confirm `.env` contains `BASE_URL=https://ls-agency.lightspeedwp.dev` (required for later verification steps) **Checkpoint**: Environment confirmed — no foundational/blocking work needed since both user stories touch entirely separate files with no shared new infrastructure. @@ -46,15 +46,15 @@ Single WordPress theme repository, no frontend/backend split. All paths are rela ### Implementation for User Story 1 -- [ ] T003 [US1] Create new SCSS partial `src/scss/structural/image-captions.scss` with a `.wp-element-caption` (and `figcaption`) color rule using `var(--wp--custom--color--text--on-dark-muted)`, applied unconditionally (no dark/light scoping selector), consistent with the existing unscoped pattern already used by `taxonomy-filter.scss` for `--text--on-dark` tokens — this theme applies one active global style (`theme.json` or `styles/dark.json`) sitewide rather than mixing light/dark per post (per data-model.md "Color Token: `--wp--custom--color--text--on-dark-muted`" and "Markup Entity: Image Caption"); add the required `// JSON limitation: ...` comment per Constitution Principle I since this is a plain color rule living in SCSS rather than `theme.json`/`styles/**/*.json` -- [ ] T004 [US1] Register the new partial as its own `src:dest` pair (`src/scss/structural/image-captions.scss:assets/css/image-captions.css`) in all three `package.json` scripts: `build:css`, `build:css:dev`, `watch:css` -- [ ] T005 [US1] Run `npm run build:css` and confirm `assets/css/image-captions.css` is generated with no Sass errors -- [ ] T006 [US1] [P] Enqueue `assets/css/image-captions.css` on the frontend, following the existing pattern used for sibling structural CSS files in `inc/animations.php` (see `taxonomy-filter.css` entry at inc/animations.php:223 for the pattern to mirror) -- [ ] T007 [US1] [P] Register `assets/css/image-captions.css` via `add_editor_style()` in `functions.php`, following the existing pattern for sibling structural CSS files (see `functions.php:67` for the pattern to mirror), so the caption fix is visible in the block editor too -- [ ] T008 [US1] Run `npm run schema:validate`, `npm run theme:validate`, `npm run lint:json`, `php -l`, and `phpcs --standard=WordPress` against the changed PHP files (`inc/animations.php`, `functions.php`) — all five MUST pass, per Constitution Principle VI ("all changed PHP MUST pass `phpcs --standard=WordPress`"); do NOT use the banned `validate_blocks` tool -- [ ] T009 [US1] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/lightspeed-remote-workspaces-2016/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations -- [ ] T010 [US1] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/lsx-version-1-3-0-released/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations -- [ ] T011 [US1] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/lsx-version-1-2-5-released/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations +- [X] T003 [US1] Create new SCSS partial `src/scss/structural/image-captions.scss` with a `.wp-element-caption` (and `figcaption`) color rule using `var(--wp--custom--color--text--on-dark-muted)`, applied unconditionally (no dark/light scoping selector), consistent with the existing unscoped pattern already used by `taxonomy-filter.scss` for `--text--on-dark` tokens — this theme applies one active global style (`theme.json` or `styles/dark.json`) sitewide rather than mixing light/dark per post (per data-model.md "Color Token: `--wp--custom--color--text--on-dark-muted`" and "Markup Entity: Image Caption"); add the required `// JSON limitation: ...` comment per Constitution Principle I since this is a plain color rule living in SCSS rather than `theme.json`/`styles/**/*.json` +- [X] T004 [US1] Register the new partial as its own `src:dest` pair (`src/scss/structural/image-captions.scss:assets/css/image-captions.css`) in all three `package.json` scripts: `build:css`, `build:css:dev`, `watch:css` +- [X] T005 [US1] Run `npm run build:css` and confirm `assets/css/image-captions.css` is generated with no Sass errors +- [X] T006 [US1] [P] Enqueue `assets/css/image-captions.css` on the frontend, following the existing pattern used for sibling structural CSS files in `inc/animations.php` (see `taxonomy-filter.css` entry at inc/animations.php:223 for the pattern to mirror) +- [X] T007 [US1] [P] Register `assets/css/image-captions.css` via `add_editor_style()` in `functions.php`, following the existing pattern for sibling structural CSS files (see `functions.php:67` for the pattern to mirror), so the caption fix is visible in the block editor too +- [X] T008 [US1] Run `npm run schema:validate`, `npm run theme:validate`, `npm run lint:json`, `php -l`, and `phpcs --standard=WordPress` against the changed PHP files (`inc/animations.php`, `functions.php`) — all five MUST pass, per Constitution Principle VI ("all changed PHP MUST pass `phpcs --standard=WordPress`"); do NOT use the banned `validate_blocks` tool. RESULT: schema:validate, lint:json, php -l, and phpcs on `inc/animations.php` all pass clean; phpcs on `functions.php` reports 20 pre-existing errors in unrelated commented-out code (lines 109-122, untouched by this change) — out of scope for this fix; `theme:validate` fails only on a pre-existing, unrelated repo gap (`styles/light.json` has never existed, confirmed via git history) — not caused by or related to this feature +- [ ] T009 [US1] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/lightspeed-remote-workspaces-2016/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations. DEFERRED: attempted on 2026-09-18, still fails because DEV serves the currently-deployed theme, not this local branch — user has decided to re-run this after the PR is merged and deployed, not before +- [ ] T010 [US1] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/lsx-version-1-3-0-released/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations. DEFERRED: same reason as T009 — re-run post-merge +- [ ] T011 [US1] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/lsx-version-1-2-5-released/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations. DEFERRED: same reason as T009 — re-run post-merge - [ ] T012 [US1] Manual regression check (spec FR-006, FR-005): if a post using a non-dark local background exists on DEV, confirm caption text rendering is visually unchanged there. If none exists, confirm instead that the new rule is unconditional/global (matching `taxonomy-filter.scss`'s pattern) so it would apply correctly if the site's global style were ever switched to light. Also confirm caption layout, spacing, and positioning are pixel-identical to before the change (only the `color` property should differ) **Checkpoint**: User Story 1 is fully functional, verified independently across all 3 originally-flagged dark-background posts, and confirmed not to regress light-background captions. @@ -69,11 +69,11 @@ Single WordPress theme repository, no frontend/backend split. All paths are rela ### Implementation for User Story 2 -- [ ] T013 [US2] In `src/scss/structural/taxonomy-filter.scss`, change the `color` declaration on `.taxonomy-filter-current` (currently `var(--wp--custom--color--text--on-dark)`) to `var(--wp--custom--color--text--on-light)` — no other property on this selector changes (per data-model.md "Markup Entity: Taxonomy Filter Pill") -- [ ] T014 [US2] Run `npm run build:css` and confirm `assets/css/taxonomy-filter.css` is regenerated with the updated color value -- [ ] T015 [US2] Run `npm run schema:validate`, `npm run theme:validate`, and `npm run lint:json` — all three MUST pass -- [ ] T016 [US2] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/blog/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations -- [ ] T017 [US2] Manual regression check (spec FR-006, FR-005): on `/blog/` on DEV, confirm the default (non-active) filter pill state and the hover state are both visually unchanged, and that the active pill's background color is unchanged (only its text color differs). Also confirm pill layout, spacing, and positioning are pixel-identical to before the change +- [X] T013 [US2] In `src/scss/structural/taxonomy-filter.scss`, change the `color` declaration on `.taxonomy-filter-current` (currently `var(--wp--custom--color--text--on-dark)`) to `var(--wp--custom--color--text--on-light)` — no other property on this selector changes (per data-model.md "Markup Entity: Taxonomy Filter Pill"). NOTE: also updated the identical `color` declaration inside `.taxonomy-filter-current:hover, &:focus-visible` (same active-pill state re-asserting its own colors against the generic `.wp-element-button:hover` rule) — left unfixed, hovering the active pill would have visibly reverted to the failing color +- [X] T014 [US2] Run `npm run build:css` and confirm `assets/css/taxonomy-filter.css` is regenerated with the updated color value +- [X] T015 [US2] Run `npm run schema:validate`, `npm run theme:validate`, and `npm run lint:json` — all three MUST pass. RESULT: schema:validate and lint:json pass; theme:validate fails only on the same pre-existing, unrelated `styles/light.json` gap noted in T008 +- [X] T016 [US2] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/blog/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations. ADAPTED: DEV verification deferred until post-merge (see T009-T011); ran instead against `http://localhost:8882/blog/` (this exact URL exists locally, unlike the 3 caption URLs) — **1 passed**, zero serious/critical color-contrast violations +- [X] T017 [US2] Manual regression check (spec FR-006, FR-005): on `/blog/` (localhost:8882, since DEV isn't updated yet), confirm the default (non-active) filter pill state and the hover state are both visually unchanged, and that the active pill's background color is unchanged (only its text color differs). Also confirm pill layout, spacing, and positioning are pixel-identical to before the change. RESULT: visually confirmed via browser — active "All" pill now shows dark text on blue (legible), hovering a non-active pill ("Accessibility") shows unchanged accent border/text hover state, no layout shift **Checkpoint**: User Stories 1 AND 2 both verified independently on DEV; all 4 originally-flagged URLs now pass. @@ -83,9 +83,9 @@ Single WordPress theme repository, no frontend/backend split. All paths are rela **Purpose**: Final combined validation and closing out the tracked task. -- [ ] T018 Run `npm run build:css` once more with both fixes present together and diff `assets/css/` output to confirm only the expected 2 files changed plus the 1 new file (`git status`/`git diff --stat`) -- [ ] T019 Run all four scoped verification commands from T009, T010, T011, T016 back-to-back in one pass as a final combined confirmation (still zero new BugHerd tasks, per the `SINGLE_PAGE_URL` structural guard in `tests/reporters/bugherd-reporter.ts`) -- [ ] T020 Follow quickstart.md Step 5: record the 4 verification results and update BugHerd task #231 to resolved, referencing this branch and the epic LS-2934 +- [X] T018 Run `npm run build:css` once more with both fixes present together and diff `assets/css/` output to confirm only the expected 2 files changed plus the 1 new file (`git status`/`git diff --stat`). RESULT: confirmed — `assets/css/taxonomy-filter.css` modified, `assets/css/image-captions.css` new, no other compiled CSS changed +- [ ] T019 Run all four scoped verification commands from T009, T010, T011, T016 back-to-back in one pass as a final combined confirmation (still zero new BugHerd tasks, per the `SINGLE_PAGE_URL` structural guard in `tests/reporters/bugherd-reporter.ts`). PARTIAL: T016's equivalent (localhost `/blog/`) passed; T009-T011 (DEV, the 3 caption URLs) remain deferred until post-merge, per user decision — this task can't be fully completed until then +- [ ] T020 Follow quickstart.md Step 5: record the 4 verification results and update BugHerd task #231 to resolved, referencing this branch and the epic LS-2934. DEFERRED: user will re-run DEV verification after this PR is merged, then update BugHerd — not done as part of this implementation pass --- diff --git a/src/scss/structural/image-captions.scss b/src/scss/structural/image-captions.scss new file mode 100644 index 0000000..0d98727 --- /dev/null +++ b/src/scss/structural/image-captions.scss @@ -0,0 +1,17 @@ +/********** Image Captions **********/ + +/* + * JSON limitation: theme.json has no generic `elements.caption` key comparable to + * `elements.link`/`elements.heading` for styling `.wp-element-caption` sitewide — see + * AGENTS.md Theme-First Approach. WordPress core's own `.wp-element-caption` default + * (#555) is unthemed and fails WCAG AA contrast (2.68:1) against this site's active + * global background token (`--wp--custom--color--surface--canvas`, currently the dark + * `contrast` value). Applied unconditionally, matching taxonomy-filter.scss's existing + * unscoped use of `--text--on-dark` tokens — this theme runs one active global style + * sitewide rather than mixing light/dark per post, so no dark/light scoping selector + * is needed (LS-2934). + */ +.wp-element-caption, +figcaption { + color: var(--wp--custom--color--text--on-dark-muted); +} diff --git a/src/scss/structural/taxonomy-filter.scss b/src/scss/structural/taxonomy-filter.scss index 1ad4bb8..07c4046 100644 --- a/src/scss/structural/taxonomy-filter.scss +++ b/src/scss/structural/taxonomy-filter.scss @@ -37,13 +37,15 @@ &.taxonomy-filter-current { border-color: var(--wp--custom--color--card--platform--wordpress); background-color: var(--wp--custom--color--card--platform--wordpress); - color: var(--wp--custom--color--text--on-dark); + // LS-2934: swapped from --text--on-dark (#FAFAFA, 2.92:1) to --text--on-light (#080808, + // 6.57:1 against this background) to meet WCAG AA — see research.md Unknown 2. + color: var(--wp--custom--color--text--on-light); &:hover, &:focus-visible { border-color: var(--wp--custom--color--card--platform--wordpress); background-color: var(--wp--custom--color--card--platform--wordpress); - color: var(--wp--custom--color--text--on-dark); + color: var(--wp--custom--color--text--on-light); } } } From 371818bb19d22bcdc722a98769088f31df4de3ad Mon Sep 17 00:00:00 2001 From: Brandonma21 Date: Fri, 18 Sep 2026 11:42:42 +0200 Subject: [PATCH 3/5] Add CHANGELOG entry for LS-2934 color-contrast fixes Context - Links the PR (#61) per this repo's per-PR changelog convention Co-Authored-By: Claude Sonnet 5 --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0052660..06b304f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 --- +## [Unreleased] — Fix WCAG color-contrast violations for captions and blog filter pill (LS-2934) + +### Fixed + +- `src/scss/structural/image-captions.scss` (new): image captions (`.wp-element-caption`/`figcaption`) previously rendered WordPress core's unthemed default text colour (`#555555`) against this theme's dark background (`#080808`), a 2.68:1 contrast ratio. Now uses the existing `--text--on-dark-muted` token (10.1:1), applied sitewide. +- `src/scss/structural/taxonomy-filter.scss`: the active/selected state of the blog page's category filter pill (`.taxonomy-filter-current`) paired light text with its blue background at a 2.92:1 ratio. Swapped to the existing `--text--on-light` token (6.57:1 against the same background), including its hover/focus state. + +([#61](https://github.com/lightspeedwp/ls-theme/pull/61)) + +--- + ## [Unreleased] — Fix editor validation errors and Icon block attributes on Services patterns (LS-4207) ### Fixed From 0d47963c4fdf5b96c5973c00940d2ce0c9cbc492 Mon Sep 17 00:00:00 2001 From: Brandonma21 Date: Fri, 18 Sep 2026 12:10:46 +0200 Subject: [PATCH 4/5] Address CodeRabbit review findings on PR #61 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixes - src/scss/structural/taxonomy-filter.scss: added missing blank line before the LS-2934 comment, fixing a Stylelint scss/double-slash-comment-empty-line-before violation - src/scss/structural/image-captions.scss: expanded the code comment to document the light/dark scoping tradeoff explicitly for future reviewers, no behavior change Documentation corrections - specs/002-color-contrast-fixes/data-model.md: corrected the taxonomy filter pill's state description — hover/focus-visible are changed by this feature, not unaffected - specs/002-color-contrast-fixes/quickstart.md: documented the accepted theme:validate baseline failure (pre-existing missing styles/light.json) instead of implying all three validation commands must pass unconditionally - specs/002-color-contrast-fixes/tasks.md: reverted T016 to unchecked and reworded its checkpoint — the local /blog/ run was informational only and does not satisfy the DEV verification FR-007 requires; matches the same deferred pattern already used for T009-T011 Context - All changes are corrections/clarifications responding to CodeRabbit's automated review on lightspeedwp/ls-theme#61 — no functional/compiled CSS output changed - One finding (unconditional caption color scoping) was evaluated and not changed in code: confirmed via theme.json/styles/dark.json that this theme has no per-post light/dark distinction today, and the sibling taxonomy-filter.scss already applies its own dark-context tokens unconditionally — documented as a deliberate, already-recorded tradeoff rather than a defect --- specs/002-color-contrast-fixes/data-model.md | 4 ++-- specs/002-color-contrast-fixes/quickstart.md | 2 +- specs/002-color-contrast-fixes/tasks.md | 4 ++-- src/scss/structural/image-captions.scss | 4 +++- src/scss/structural/taxonomy-filter.scss | 1 + 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/specs/002-color-contrast-fixes/data-model.md b/specs/002-color-contrast-fixes/data-model.md index 6392137..e47e069 100644 --- a/specs/002-color-contrast-fixes/data-model.md +++ b/specs/002-color-contrast-fixes/data-model.md @@ -28,7 +28,7 @@ This feature has no runtime data model, database schema, or API payloads — it ## Markup Entity: Taxonomy Filter Pill (`.taxonomy-filter-current`) - **Represents**: The active/selected state of a clickable category filter control on the blog listing page (`src/scss/structural/taxonomy-filter.scss`). -- **States**: default (unaffected), `:hover` (unaffected, FR-006), `.taxonomy-filter-current` (active/selected — the only state changed by this feature). -- **Change**: One `color` declaration's token reference is swapped; `background-color` and all other properties are unchanged. +- **States**: default (unaffected), default `:hover`/`:focus-visible` (unaffected, FR-006), `.taxonomy-filter-current` and its own `:hover`/`:focus-visible` (active/selected — the states changed by this feature, since the active pill's hover/focus sub-rule re-asserts the same color as its base state). +- **Change**: The `color` declaration's token reference is swapped on both `.taxonomy-filter-current` and its `:hover`/`:focus-visible` sub-rule; `background-color` and all other properties are unchanged. No new entities, relationships, or state transitions beyond the above — this table exists to satisfy the plan template's Phase 1 output requirement, not because the feature has meaningful data-model complexity. diff --git a/specs/002-color-contrast-fixes/quickstart.md b/specs/002-color-contrast-fixes/quickstart.md index ae96f4b..4a9f01f 100644 --- a/specs/002-color-contrast-fixes/quickstart.md +++ b/specs/002-color-contrast-fixes/quickstart.md @@ -27,7 +27,7 @@ npm run theme:validate npm run lint:json ``` -All three must pass before proceeding. Do **not** run the banned `validate_blocks` tool. +`schema:validate` and `lint:json` must pass before proceeding. `theme:validate` is expected to fail on one pre-existing, unrelated finding — `styles/light.json` has never existed in this repo (confirmed via `git log`) — which is not caused by or related to this feature; any other `theme:validate` failure should be treated as a real blocker. Do **not** run the banned `validate_blocks` tool. ## Step 3 — Scoped accessibility re-checks (no new BugHerd tasks) diff --git a/specs/002-color-contrast-fixes/tasks.md b/specs/002-color-contrast-fixes/tasks.md index ecb404a..b1a4295 100644 --- a/specs/002-color-contrast-fixes/tasks.md +++ b/specs/002-color-contrast-fixes/tasks.md @@ -72,10 +72,10 @@ Single WordPress theme repository, no frontend/backend split. All paths are rela - [X] T013 [US2] In `src/scss/structural/taxonomy-filter.scss`, change the `color` declaration on `.taxonomy-filter-current` (currently `var(--wp--custom--color--text--on-dark)`) to `var(--wp--custom--color--text--on-light)` — no other property on this selector changes (per data-model.md "Markup Entity: Taxonomy Filter Pill"). NOTE: also updated the identical `color` declaration inside `.taxonomy-filter-current:hover, &:focus-visible` (same active-pill state re-asserting its own colors against the generic `.wp-element-button:hover` rule) — left unfixed, hovering the active pill would have visibly reverted to the failing color - [X] T014 [US2] Run `npm run build:css` and confirm `assets/css/taxonomy-filter.css` is regenerated with the updated color value - [X] T015 [US2] Run `npm run schema:validate`, `npm run theme:validate`, and `npm run lint:json` — all three MUST pass. RESULT: schema:validate and lint:json pass; theme:validate fails only on the same pre-existing, unrelated `styles/light.json` gap noted in T008 -- [X] T016 [US2] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/blog/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations. ADAPTED: DEV verification deferred until post-merge (see T009-T011); ran instead against `http://localhost:8882/blog/` (this exact URL exists locally, unlike the 3 caption URLs) — **1 passed**, zero serious/critical color-contrast violations +- [ ] T016 [US2] Verify: run `SINGLE_PAGE_URL="https://ls-agency.lightspeedwp.dev/blog/" npx playwright test accessibility --project=chromium --reporter=line` against DEV and confirm zero serious/critical `color-contrast` violations. DEFERRED: DEV verification deferred until post-merge, same as T009-T011 — required to satisfy FR-007, which specifically calls for verification against DEV. INFORMATIONAL ONLY (does not satisfy this task): ran the equivalent check against `http://localhost:8882/blog/` (this exact URL exists locally, unlike the 3 caption URLs) as a local sanity check — 1 passed, zero serious/critical color-contrast violations - [X] T017 [US2] Manual regression check (spec FR-006, FR-005): on `/blog/` (localhost:8882, since DEV isn't updated yet), confirm the default (non-active) filter pill state and the hover state are both visually unchanged, and that the active pill's background color is unchanged (only its text color differs). Also confirm pill layout, spacing, and positioning are pixel-identical to before the change. RESULT: visually confirmed via browser — active "All" pill now shows dark text on blue (legible), hovering a non-active pill ("Accessibility") shows unchanged accent border/text hover state, no layout shift -**Checkpoint**: User Stories 1 AND 2 both verified independently on DEV; all 4 originally-flagged URLs now pass. +**Checkpoint**: User Stories 1 AND 2 are both implemented and locally sanity-checked; DEV verification of all 4 originally-flagged URLs is deferred until post-merge (see T009-T011, T016). --- diff --git a/src/scss/structural/image-captions.scss b/src/scss/structural/image-captions.scss index 0d98727..ed86487 100644 --- a/src/scss/structural/image-captions.scss +++ b/src/scss/structural/image-captions.scss @@ -9,7 +9,9 @@ * `contrast` value). Applied unconditionally, matching taxonomy-filter.scss's existing * unscoped use of `--text--on-dark` tokens — this theme runs one active global style * sitewide rather than mixing light/dark per post, so no dark/light scoping selector - * is needed (LS-2934). + * is needed today. If a future post or block ever introduces a local light-background + * override, this rule (and taxonomy-filter.scss's equivalent) would need a matching + * light-context override at that point — see spec.md Assumptions, LS-2934. */ .wp-element-caption, figcaption { diff --git a/src/scss/structural/taxonomy-filter.scss b/src/scss/structural/taxonomy-filter.scss index 07c4046..958f129 100644 --- a/src/scss/structural/taxonomy-filter.scss +++ b/src/scss/structural/taxonomy-filter.scss @@ -37,6 +37,7 @@ &.taxonomy-filter-current { border-color: var(--wp--custom--color--card--platform--wordpress); background-color: var(--wp--custom--color--card--platform--wordpress); + // LS-2934: swapped from --text--on-dark (#FAFAFA, 2.92:1) to --text--on-light (#080808, // 6.57:1 against this background) to meet WCAG AA — see research.md Unknown 2. color: var(--wp--custom--color--text--on-light); From 7297e35103049ba690dea0a238100cdfc5a4090f Mon Sep 17 00:00:00 2001 From: Brandonma21 Date: Mon, 21 Sep 2026 14:30:29 +0200 Subject: [PATCH 5/5] Rephrase CHANGELOG sentence to retrigger CodeRabbit review Context - No functional change; wording-only edit to LS-2934 entry - Needed a new commit on PR #61 to trigger CodeRabbit re-review Changed - Restructured the taxonomy-filter bullet's opening clause in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 06b304f..4f0d9e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - `src/scss/structural/image-captions.scss` (new): image captions (`.wp-element-caption`/`figcaption`) previously rendered WordPress core's unthemed default text colour (`#555555`) against this theme's dark background (`#080808`), a 2.68:1 contrast ratio. Now uses the existing `--text--on-dark-muted` token (10.1:1), applied sitewide. -- `src/scss/structural/taxonomy-filter.scss`: the active/selected state of the blog page's category filter pill (`.taxonomy-filter-current`) paired light text with its blue background at a 2.92:1 ratio. Swapped to the existing `--text--on-light` token (6.57:1 against the same background), including its hover/focus state. +- `src/scss/structural/taxonomy-filter.scss`: the blog page's category filter pill, in its active/selected state (`.taxonomy-filter-current`), paired light text with its blue background at a 2.92:1 ratio. Swapped to the existing `--text--on-light` token (6.57:1 against the same background), including its hover/focus state. ([#61](https://github.com/lightspeedwp/ls-theme/pull/61))