Skip to content

docs(a11y): add WCAG/POUR grounding, colour contrast, manual-test sections#3443

Merged
ten9876 merged 3 commits into
mainfrom
docs/a11y-additions
Jun 7, 2026
Merged

docs(a11y): add WCAG/POUR grounding, colour contrast, manual-test sections#3443
ten9876 merged 3 commits into
mainfrom
docs/a11y-additions

Conversation

@ten9876

@ten9876 ten9876 commented Jun 6, 2026

Copy link
Copy Markdown
Collaborator

Summary

Follow-up to #3289 closing five gaps surfaced by reviewing docs/a11y.md against the WCAG/POUR-grounded coverage at accessibilitychecker.org/blog/a11y/.

Our existing doc is materially deeper than that article on Qt-specific patterns (live value announcements, throttling, custom-painted widgets, lint mechanics) — but it was missing five concrete things every Qt-desktop a11y guide should have. All five land as pure additions; no existing section is modified.

What's added

  1. "What we're following" section — three-line POUR (Perceivable / Operable / Understandable / Robust) grounding right after the Background. Cites WCAG 2.1 quick-ref. Future contributors asking "why does this rule exist" now get "WCAG 2.1 says so" rather than "because the project says so."

  2. Form-field instructions guidance — added to the existing "Accessible description" bullet. For QSpinBox / QLineEdit / QComboBox, the accessible name answers "what is this?" and the description answers "what do I type?". Tooltips don't reach the screen reader; the description does.

  3. Keyboard-only test — added to the existing "Tab focus" bullet. Tab to the widget from a sibling, activate with Space/Return, no mouse. Links to the existing QLabel anti-pattern section as the structural escape.

  4. Colour contrast section — between "Live value updates" and "Custom-painted widgets." WCAG 4.5:1 normal text / 3:1 large text and non-text components. Cites fix(radio): make disabled Reboot Radio button legible instead of near-invisible (#3334) #3441 (Reboot Radio button) as the canonical disabled-state-blends-into-background case so future readers see why the section exists. WebAIM Contrast Checker + the OS-level "Increase Contrast" mode as the practical verification workflow.

  5. Manual verification section — between "Suppressing the lint" and "CI enforcement." Names the three platform screen readers (VoiceOver, NVDA / Narrator, Orca) with the actual keystrokes to launch them. Lint = static; this = real test. Explicit that "the lint catches what it can detect statically — this is the real test."

Doc shape

Before After
Lines 166 254
Sections 7 9

Section order now flows: motivation → grounding → patterns → escape valves → verification → mechanism.

What I didn't import from the article

  • Legal framing ("legally mandated… legal action or fines"). Out of voice. AetherSDR's a11y motivation is "a blind ham operator filed Phase 2 accessibility: live-region announcements + structural fixes for custom widgets #3288 and we want AetherSDR usable for him" — that's a stronger frame than fear of lawsuits.
  • POUR as the organizing structure of the whole doc. The article is structured around POUR; we'd lose clarity if we reshuffled our concrete Qt sections under POUR headings. Mention WCAG/POUR once near the top, keep our pragmatic structure.
  • Alt text on images / captions on video. We have ~zero of either in AetherSDR — no help text videos, the only "images" are panadapter renders (covered by our custom-painted-widgets section).

Test plan

  • docs/a11y.md parses as valid markdown (9 ## sections, no orphaned formatting).
  • Links checked: WCAG 2.1 quickref, WebAIM contrast checker + articles/contrast, NVDA, Apple Accessibility, Orca docs — all resolve.
  • No code changes — tools/check_a11y.py and the workflow are untouched, so no CI behaviour change.

Refs #3288 #3289

ten9876 added 2 commits June 6, 2026 17:42
…tions

Follow-up to #3289 closing five gaps surfaced by reviewing our doc against
the WCAG/POUR-grounded coverage at accessibilitychecker.org. Our existing
doc is materially deeper than that article on Qt-specific patterns (live
value announcements, throttling, custom-painted widgets, lint mechanics),
but it was missing five concrete things every Qt-desktop a11y guide
should have. All five land as additions; no existing section is changed.

1. "What we're following" — three-line POUR (Perceivable / Operable /
   Understandable / Robust) grounding right after the Background. Cites
   WCAG 2.1 quick-ref. Future contributors asking "why does this rule
   exist" now get "WCAG 2.1 says so" rather than "because the project
   says so."

2. Form-field instructions — added to the existing "Accessible
   description" bullet. For QSpinBox / QLineEdit / QComboBox, the
   accessible name answers "what is this?" and the description answers
   "what do I type?". Tooltips don't reach the screen reader; the
   description does — that's the right home for input semantics.

3. Keyboard-only test — added to the existing "Tab focus" bullet. Tab to
   the widget from a sibling, activate with Space/Return, no mouse. If
   you can't reach it the focus policy is wrong; if Space/Return doesn't
   fire it the widget needs a keyPressEvent (or it should have been a
   QPushButton — links to the QLabel anti-pattern section).

4. Colour contrast — new section between "Live value updates" and
   "Custom-painted widgets." WCAG 4.5:1 normal text / 3:1 large text and
   non-text components. Cites #3441 (Reboot Radio button) as the
   canonical disabled-state-blends-into-background case so future readers
   see why the section exists. WebAIM Contrast Checker + the
   OS-level "Increase Contrast" mode as the practical verification
   workflow.

5. Manual verification — new section between "Suppressing the lint" and
   "CI enforcement." Names the three platform screen readers (VoiceOver,
   NVDA/Narrator, Orca) with the actual keystrokes to launch them, and
   tells contributors to walk the widget the way a screen-reader user
   would. Lint = static; this = real test. Explicit that the lint
   catches what it can detect statically and nothing more.

Doc grows 166 → 254 lines (+88). Section order flows
motivation → grounding → patterns → escape valves → verification →
mechanism.

Principle XI.
Self-review of #3443 surfaced five issues; all five addressed here.

1. Colour contrast section now matches the theme-tokens-first policy
   established in #3441 / #3446 (instead of contradicting it).

   The earlier text read "pick concrete hex values that hit the WCAG
   ratio against the real surrounding background; reserve the dimmest
   theme tokens for genuinely decorative state where invisibility is the
   intent" — which advocated hex over tokens for disabled state and
   would have read as policy by future contributors. The actual policy
   from the #3441/#3446 thread: theme tokens are first choice; hex is a
   valid intermediate state when no semantic token exists, with a
   follow-up issue to add the proper token (mirroring how #3441 +
   #3446 played out). Adds the three-step rule of thumb (existing token
   for this widget × state? token for similar widget? hex + issue?) and
   cites #3441's merge commit and #3446's open status as the canonical
   example.

2. POUR Operable softened from overclaim to aspirational.

   Was: "Operable — keyboard focus and activation paths for every
   interactive widget; no mouse-only interactions." That's false today —
   CHAIN drag-drop, frequency drag, drag-to-pan, custom panadapter
   handlers are all mouse-only. Replaced with "every interactive widget
   reachable by keyboard, with a non-mouse activation path. (AetherSDR
   still has surfaces that are mouse-only today — [list] — which we're
   working toward; new code shouldn't add to the list.)"

3. Tooltip-vs-description claim corrected.

   Was: "right place to put input semantics, not in a separate tooltip
   the screen reader can't reach." Qt does expose tooltips to AT via
   QAccessible::Help — they reach screen readers as help text, fired on
   explicit request. The argument that descriptions are the better home
   for input semantics still stands, but now states the actual reason
   (descriptions are read automatically as part of widget identity;
   tooltips are help-text-on-request).

4. NVDA "say more" mode reference replaced with neutral language.

   "Say more" isn't a standard NVDA mode name (it may be confused with
   VoiceOver's "speak more"). Now says "in verbose announcement modes"
   without committing to a specific NVDA mode label.

5. WebAIM Contrast Checker workflow fixed.

   The previous text said "take a screenshot of the widget against its
   real background and drop it into the WebAIM Contrast Checker" — but
   that tool takes two hex codes, not an image. Reworded to mention the
   eyedropper step (Digital Color Meter on macOS, PowerToys Color
   Picker on Windows, gpick/gcolor3 on Linux) and added a pointer to
   Colour Contrast Analyser (TPGi) for a one-step
   screenshot-to-ratio workflow with integrated eyedropper.

Doc grows 254 → 286 lines net (+74 modifications inside the existing
+89 from the original commit). Section structure unchanged.

Principle XI.
…tters

The per-method opt-out example showed the // a11y-check: skip comment on
the line above the method:

    // a11y-check: skip
    void FooWidget::setInternalCache(float v) { ... }

But tools/check_a11y.py:200-203 only inspects (a) the method definition
line itself and (b) the body between { and }. A comment on the line above
the method is invisible to it, so a contributor copying the example
as-written would still get a value-method-missing-a11y-update warning.

The prose immediately above the example already says correctly "on the
method line or anywhere in its body" — only the code example was wrong.
Replaced with two valid placements (definition line + body line) and
explicit "checker only reads those two locations" so future readers see
why the line above doesn't work.

Principle XI.
@ten9876 ten9876 merged commit f053c51 into main Jun 7, 2026
4 checks passed
@ten9876 ten9876 deleted the docs/a11y-additions branch June 7, 2026 04:56
ten9876 pushed a commit that referenced this pull request Jun 8, 2026
Release-prep for **v26.6.2**, a consolidated release rolling up the
26.6.1 feature wave, the v26.6.1.1 hotfixes, and new work through #3443.

Bumps version 26.6.1.1 → 26.6.2 in `CMakeLists.txt` and the `README.md`
banner, and prepends the v26.6.2 `CHANGELOG.md` entry. No code changes.

Headline content: runtime theming system, three HID controller classes
(StreamDeck+, Ulanzi Dial, Icom RC-28), the packet-radio suite
(KISS-over-TCP TNC #3279, connected-mode BBS terminal #3381, PMS
mailbox), and the Microsoft Store MSIX packaging path
(#3178/#3205/#3225/#3281).

💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat

Co-authored-by: Codex <noreply@openai.com>
nigelfenton added a commit to nigelfenton/AetherSDR that referenced this pull request Jun 10, 2026
Adds a "Scale text" dropdown (8-14 pt) to the per-pan Display overlay
menu, mirroring the aethersdr#1390 grid-spacing pattern: per-pan persisted
setting (DisplayFreqScaleFontPt), signal wiring, state sync on menu
open, and Reset-defaults support.

The fixed FREQ_SCALE_H strip height becomes freqScaleH(), derived from
the configured font at all 28 layout/hit-test sites, so larger labels
get a taller strip instead of clipped ascenders (never below the
existing 20 px at the 8 pt default).

drawFreqScale() also gains a right-column guard: ruler labels are
clamped/skipped clear of the dBm / waterfall-time scale column, fixing
a collision that existed marginally at 8 pt and became obvious at 14.

Reported by KN7K via Discord; first concrete item under the aethersdr#3443
accessibility initiative.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
jensenpat pushed a commit that referenced this pull request Jun 11, 2026
Adds a "Scale text" dropdown (8-14 pt) to the per-pan Display overlay
menu, mirroring the #1390 grid-spacing pattern: per-pan persisted
setting (DisplayFreqScaleFontPt), signal wiring, state sync on menu
open, and Reset-defaults support.

The fixed FREQ_SCALE_H strip height becomes freqScaleH(), derived from
the configured font at all 28 layout/hit-test sites, so larger labels
get a taller strip instead of clipped ascenders (never below the
existing 20 px at the 8 pt default).

drawFreqScale() also gains a right-column guard: ruler labels are
clamped/skipped clear of the dBm / waterfall-time scale column, fixing
a collision that existed marginally at 8 pt and became obvious at 14.

Reported by KN7K via Discord; first concrete item under the #3443
accessibility initiative.

Squashed-from: #3503

Co-authored-by: Nigel Fenton <2791007+nigelfenton@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pat Jensen <jensenpat@users.noreply.github.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
…tions (aethersdr#3443)

## Summary

Follow-up to aethersdr#3289 closing five gaps surfaced by reviewing
`docs/a11y.md` against the WCAG/POUR-grounded coverage at
[accessibilitychecker.org/blog/a11y/](https://www.accessibilitychecker.org/blog/a11y/).

Our existing doc is materially deeper than that article on Qt-specific
patterns (live value announcements, throttling, custom-painted widgets,
lint mechanics) — but it was missing five concrete things every
Qt-desktop a11y guide should have. All five land as pure additions; no
existing section is modified.

## What's added

1. **"What we're following" section** — three-line POUR (Perceivable /
Operable / Understandable / Robust) grounding right after the
Background. Cites WCAG 2.1 quick-ref. Future contributors asking *"why
does this rule exist"* now get *"WCAG 2.1 says so"* rather than
*"because the project says so."*

2. **Form-field instructions guidance** — added to the existing
"Accessible description" bullet. For `QSpinBox` / `QLineEdit` /
`QComboBox`, the accessible name answers *"what is this?"* and the
description answers *"what do I type?"*. Tooltips don't reach the screen
reader; the description does.

3. **Keyboard-only test** — added to the existing "Tab focus" bullet.
Tab to the widget from a sibling, activate with `Space`/`Return`, no
mouse. Links to the existing QLabel anti-pattern section as the
structural escape.

4. **Colour contrast section** — between "Live value updates" and
"Custom-painted widgets." WCAG **4.5:1 normal text / 3:1 large text**
and non-text components. Cites aethersdr#3441 (Reboot Radio button) as the
canonical disabled-state-blends-into-background case so future readers
see *why* the section exists. WebAIM Contrast Checker + the OS-level
"Increase Contrast" mode as the practical verification workflow.

5. **Manual verification section** — between "Suppressing the lint" and
"CI enforcement." Names the three platform screen readers
([VoiceOver](https://www.apple.com/accessibility/),
[NVDA](https://www.nvaccess.org/) / Narrator,
[Orca](https://help.gnome.org/users/orca/stable/)) with the actual
keystrokes to launch them. Lint = static; this = real test. Explicit
that *"the lint catches what it can detect statically — this is the real
test."*

## Doc shape

| | Before | After |
|---|---|---|
| Lines | 166 | 254 |
| Sections | 7 | 9 |

Section order now flows: motivation → grounding → patterns → escape
valves → verification → mechanism.

## What I *didn't* import from the article

- **Legal framing** ("legally mandated… legal action or fines"). Out of
voice. AetherSDR's a11y motivation is "a blind ham operator filed aethersdr#3288
and we want AetherSDR usable for him" — that's a stronger frame than
fear of lawsuits.
- **POUR as the organizing structure of the whole doc**. The article is
structured around POUR; we'd lose clarity if we reshuffled our concrete
Qt sections under POUR headings. Mention WCAG/POUR once near the top,
keep our pragmatic structure.
- **Alt text on images / captions on video**. We have ~zero of either in
AetherSDR — no help text videos, the only "images" are panadapter
renders (covered by our custom-painted-widgets section).

## Test plan

- [x] `docs/a11y.md` parses as valid markdown (9 `##` sections, no
orphaned formatting).
- [x] Links checked: WCAG 2.1 quickref, WebAIM contrast checker +
articles/contrast, NVDA, Apple Accessibility, Orca docs — all resolve.
- [x] No code changes — `tools/check_a11y.py` and the workflow are
untouched, so no CI behaviour change.

Refs aethersdr#3288 aethersdr#3289
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
Release-prep for **v26.6.2**, a consolidated release rolling up the
26.6.1 feature wave, the v26.6.1.1 hotfixes, and new work through aethersdr#3443.

Bumps version 26.6.1.1 → 26.6.2 in `CMakeLists.txt` and the `README.md`
banner, and prepends the v26.6.2 `CHANGELOG.md` entry. No code changes.

Headline content: runtime theming system, three HID controller classes
(StreamDeck+, Ulanzi Dial, Icom RC-28), the packet-radio suite
(KISS-over-TCP TNC aethersdr#3279, connected-mode BBS terminal aethersdr#3381, PMS
mailbox), and the Microsoft Store MSIX packaging path
(aethersdr#3178/aethersdr#3205/aethersdr#3225/aethersdr#3281).

💻 Generated with Claude Code (Opus 4.8) with architecture by @jensenpat

Co-authored-by: Codex <noreply@openai.com>
G6PWY-Chris pushed a commit to G6PWY-Chris/AetherSDR that referenced this pull request Jun 22, 2026
Adds a "Scale text" dropdown (8-14 pt) to the per-pan Display overlay
menu, mirroring the aethersdr#1390 grid-spacing pattern: per-pan persisted
setting (DisplayFreqScaleFontPt), signal wiring, state sync on menu
open, and Reset-defaults support.

The fixed FREQ_SCALE_H strip height becomes freqScaleH(), derived from
the configured font at all 28 layout/hit-test sites, so larger labels
get a taller strip instead of clipped ascenders (never below the
existing 20 px at the 8 pt default).

drawFreqScale() also gains a right-column guard: ruler labels are
clamped/skipped clear of the dBm / waterfall-time scale column, fixing
a collision that existed marginally at 8 pt and became obvious at 14.

Reported by KN7K via Discord; first concrete item under the aethersdr#3443
accessibility initiative.

Squashed-from: aethersdr#3503

Co-authored-by: Nigel Fenton <2791007+nigelfenton@users.noreply.github.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: Pat Jensen <jensenpat@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant