Skip to content

Fix the low-contrast ink opacities and pin contrast so they cannot regress #38

Open

Description

@abrcrmb

Task

Three components use text-ink/50 and text-ink/60 for small text. Computed against the paper background, 50% is 3.30:1 — below WCAG AA for body text — and 60% is 4.54:1, which passes by 0.04. Replace them with tokens that clear the bar and add a check so the mistake cannot come back.

Context

  • Tokens: --ink: #141a17 on --paper: #f6f7f5 (light), flipped in dark mode (app/globals.css:15,16,24,25).
  • app/(marketing)/page.tsx:53 — text-ink/50 on the numbered step labels. Composite ≈ #858986 → 3.30:1, fails AA (4.5:1) for normal text.
  • components/indexer/LagNotice.tsx:11 and app/(marketing)/page.tsx:119 — text-ink/60 → composite ≈ #6e7270, 4.54:1. Technically passing, one rounding from failing.
  • My computed numbers, for your starting point: /75 = 7.50:1, /60 = 4.54:1, /50 = 3.30:1. Dark mode /50 ≈ 4.66:1. Verify both schemes yourself — a dark-mode-only pass is not a pass.
  • There is no eslint-plugin-jsx-a11y and no contrast check anywhere; lint is Biome recommended (biome.json:6).

Before you start: mandatory context review

Kinlock moves other people's money, so the docs are binding constraints, not background reading.
They record decisions this task must not silently reopen. Read them in this order and tick each box:

  • docs/ARCHITECTURE_ESSENTIALS.md — start here, it is short (AGENTS.md §1)
  • docs/ARCHITECTURE.md — only the sections named in Context above
  • docs/PRD.md — if this task touches user-facing behaviour or scope
  • AGENTS.md §3 (hard rules), §4 (deferred list), §8 for this repo, §12 (roadmap rule)
  • ROADMAP.md — the row(s) listed under Related roadmap rows, including their Depends on

In your first PR comment, name the hard-rule numbers or doc sections that constrain this task. That
line is how we know the review happened.

Then:

  • If code and docs disagree, or two docs disagree: stop and say so on this issue. Do not pick one
    silently, and do not "fix" either side without approval (AGENTS.md §1).
  • If the task looks like it needs anything from the deferred list (§4), a change to a hard rule (§3),
    or a new dependency (§2): raise it here first. Those need a human; deciding them is not your job.
  • If a row you are told to finish is BLOCKED or depends on a row that is not DONE, say so here
    instead of starting.

Why we need this

  • AGENTS.md §8.3 treats accessibility as required, not polish, and lists contrast explicitly.
  • The step labels on the landing page are the first thing a prospective sender reads; if they are grey mush on a phone in sunlight, the product looks broken before it is judged.
  • 4.54:1 passing by 0.04 means the next well-meaning tweak breaks it silently. Pinning it is the actual fix.

Scope

In scope

  • app/globals.css (a token or two that clear AA in both colour schemes)
  • The three call sites named in Context
  • One check: a unit test computing ratios from the token values, or a documented manual checklist — see Tests

Do not touch (security-sensitive or generated; changing these needs a human first — AGENTS.md §2, docs/project_structure.md §8)

  • app/claim/** and lib/claim-links/** — the claim-link fragment (#r=…&s=…) must never reach a server, log or analytics tool (hard rule 5)
  • next.config.ts, proxy.ts / middleware.ts, CSP headers
  • lib/wallet/** and any direct contract call site — money-moving calls go through the SDK and the wallet adapter only
  • app/send/**, app/locks/**, app/payee/** action logic — release, refund and decline paths stay with maintainers
  • package.json, pnpm-lock.yaml (no new dependencies without approval), .github/workflows/**
  • a full audit of every component (that is the rest of M3-17)", "installing an a11y lint plugin without approval (new dependency)

Suggested implementation

  1. Introduce semantic tokens (e.g. --color-muted at a ratio that clears 4.5:1 in both light and dark) rather than sprinkling /70 everywhere. Tailwind 4 with CSS variables makes this one change.
  2. Replace all text-ink/50 and text-ink/60 usages with the token. Do not leave any behind: the check should forbid the opacity form.
  3. Prefer a test that reads the token values and computes the ratio (WCAG relative luminance is a dozen lines, no dependency) over a screenshot tool. If you would rather use a library, ask here first.
  4. Do not "fix" contrast by darkening the background — the palette is a design decision; propose changes to it on the issue.

A different approach is usually welcome — post it here before building and we will answer. This is a
suggestion, not a spec.

Edge cases and traps

  • Light (--ink: #141a17 on --paper: #f6f7f5) and the dark-mode flip. Passing in one scheme is not a pass; dark /50 is already ~4.66:1 and light /50 is 3.30:1.
  • Body text is a 4.5:1 bar, large text is 3:1. Label which case each token serves.
  • Opacity over a tinted or image-bearing background composites differently from opacity over flat paper — compute the real result, do not eyeball it.
  • The 4.54:1 case passes by 0.04: a tweak that looks harmless will break it silently, which is why the check is part of the deliverable, not a nice-to-have.

Done means

  • No text-ink/50 or text-ink/60 remains in app/ or components/
  • Every text token used for body copy measures ≥ 4.5:1 in light and dark; large-text cases ≥ 3:1
  • A test or a checked-in checklist fails if someone reintroduces an opacity below the bar
  • pnpm lint, typecheck, test, build pass
  • You state in the PR the ratios you measured, per token, per scheme

Tests

Add the contrast case to the existing vitest suite (there are 15 test files, all node-environment .test.ts). A pure function over the token values needs no DOM and no dependency.

Documentation

Note the palette rule in app/globals.css beside the token so the next person does not repeat it.

Verify before you open the PR

pnpm install
pnpm lint
pnpm typecheck
pnpm test
pnpm build

Run every command above and paste the real output in the PR. We do not accept "should pass" —
AGENTS.md §6: "You actually ran the commands."

Opening the pull request

  • Assignment is required before you start. Comment on this issue and wait to be assigned. Do not
    open a PR against an issue someone already holds — if it has been quiet, ask us to release it first.
  • Closes #38 in the PR description, so the issue and the PR link automatically.
  • Branch feat/…, fix/…, docs/…, test/… or chore/…. Commit subject example:
    fix(app): raise muted ink tokens to WCAG AA in both colour schemes
  • Add a Changelog entry; leave M3-17 IN PROGRESS.
  • Follow .github/PULL_REQUEST_TEMPLATE.md (AGENTS.md §7). In "Hard rules / invariants touched",
    give numbers, not prose.
  • Small and focused: one logical change. Do not mix a refactor into it.
  • Do not add the security-sensitive label — that is the maintainer's flag for contract-logic PRs.
  • Do not reformat or touch files outside Scope. Unrelated diff noise costs review time and can
    break the roadmap merge.
  • Never commit to main; open a PR. Never force-push.
  • Evidence for the PR: Attach before/after screenshots of the landing-page step labels and LagNotice, light and dark.

How we review

Two maintainers: one reviews correctness against the hard rules and the roadmap row's Done when, the
other reviews diff scope and whether the tests were actually run. Order is hard-rule correctness → tests
really executed → diff stays inside Scope → style last. First response on a PR: 2 business days.

What fails review fastest: a change outside Scope, a deleted or weakened assertion, a claim with no
command output behind it, and a ROADMAP.md with no Changelog entry (AGENTS.md §12).
For this issue, look first at the ratios you measured per token per scheme, and that the check actually fails when someone reintroduces an opacity below the bar.

Tier and effort

tier/trivial · roughly 1–2 hours. Wave complexity tag: Trivial = 100 points — the published mapping is Trivial 100 · Medium 150 · High 200
(Drips' maintainer guide); the cycle's
own values are confirmed on drips.network/wave/stellar before
the Wave opens. Kinlock's tier rules and what each tier means for review:
docs/wave-issue-format.md.

Related roadmap rows

M3-17 — Accessibility pass (keyboard, labels, contrast) (IN PROGRESS)

M3-17 is IN PROGRESS; this is one increment of it. Leave the row IN PROGRESS and name what is still outstanding in the PR.

Questions

Comment on this issue — a maintainer answers within 2 business days. Say "I'll take this" so we can flag it on
the Wave board and stop two people doing the same work. If you get half way and stall, post what you
have as a draft PR rather than disappearing; a blocked draft is more useful to us than silence.

Activity

  1. added
    accessibilityBarrier affecting people with disabilities
    good first issueSmall, self-contained, no security-critical files
    waveScoped for Drips Wave contributors
    area:appOwnership: kinlock-app
    tier/trivialWave tier: self-contained, under about an hour, one file or two adjacent files
    on Oct 8, 2026
  2. jdjioe5-cpu commented on Oct 17, 2026

    @jdjioe5-cpu

    I'll take this. I have reviewed AGENTS.md §8.3 accessibility requirements and the scope in \�pp/globals.css. Ready to introduce WCAG AA compliant semantic ink tokens across both light and dark schemes, eliminate \ ext-ink/50\ and \ ext-ink/60\ usages, and add the mathematical contrast ratio assertion test suite once assigned.

  3. jdjioe5-cpu commented on Oct 17, 2026

    @jdjioe5-cpu

    I'll take this issue. Preparing semantic tokens (--muted\ / --color-muted) with WCAG AA compliance (contrast ratio $\ge 4.5:1$ in both light and dark modes), replacing all \ ext-ink/50\ and \ ext-ink/60\ occurrences across \�pp/\ and \components/, and adding automated vitest verification.

  4. jdjioe5-cpu commented on Oct 17, 2026

    @jdjioe5-cpu

    Submitted PR #45 addressing this issue.

    Summary of Delivery:

    1. Semantic Tokens: Added --muted: #525a56;\ (light, 6.54:1 contrast ratio) and --muted: #949f99;\ (dark, 6.74:1 contrast ratio) in \�pp/globals.css, clearing WCAG AA $\ge 4.5:1$ with margin. Mapped to Tailwind v4 @theme\ as --color-muted: var(--muted);.
    2. Replaced Raw Opacities: Cleaned up \ ext-ink/50\ and \ ext-ink/60\ across \�pp/(marketing)/page.tsx\ and \components/indexer/LagNotice.tsx\ with \ ext-muted.
    3. Automated Verification: Added \lib/contrast.test.ts\ (49 test cases) verifying exact WCAG 2.1 relative luminance and contrast ratio formulas, demonstrating failure of 50% opacity (3.30:1), and forbidding reintroduction of \ ext-ink/50\ or \ ext-ink/60.
    4. All Checks Passing: \pnpm lint\ (84 files clean), \pnpm typecheck\ (clean), \pnpm test\ (215/215 passed across 16 files), and \pnpm build\ (clean).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    accessibilityBarrier affecting people with disabilitiesarea:appOwnership: kinlock-appgood first issueSmall, self-contained, no security-critical filestier/trivialWave tier: self-contained, under about an hour, one file or two adjacent fileswaveScoped for Drips Wave contributors

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions