AccessibilityConformance Report (VPAT)

Accessibility Conformance Report

VPAT 2.5 International edition for de:volt (simulator and de:volt Learn).

This report is authored by the de:volt team (self-assessment). It is not an independent audit. It records what has actually been verified, by which method, and what has not been verified yet. Rows that report a partial or failing result name a tracked issue, and every tracked issue is listed in Known gaps and tracked issues.

Read it alongside the public accessibility statement, which states the same target (WCAG 2.2 AA) and the same headline limitations for the breadboard editor, oscilloscope, drag wiring and live classroom views.

Product information

FieldValue
Product namede:volt (browser-based breadboard simulator) and de:volt Learn
Product version2026-09
Product descriptionA browser circuit simulator with a breadboard canvas, oscilloscope and analysis tools, plus de:volt Learn: guided lessons whose objectives are checked against the learner’s real circuit, a teacher classroom console, and a class progress tracker.
Report date16 September 2026
Report version1.1
Contacthello@devoltapp.com
NotesScope is the simulator and Learn (student and teacher surfaces). The marketing site, documentation site, account, and billing surfaces are covered by the accessibility statement but are not itemised here.

Evaluation methods

MethodStatus
Manual keyboard review of the canvas read and build path, the lesson player, the teacher console and the progress trackerPerformed
Source code review of the canvas assistive-technology layer, live regions, table semantics and design tokensPerformed
Screen-reader spot checks, VoiceOver + Safari (macOS)Planned - owner pass
Screen-reader spot checks, NVDA + Chrome (Windows)Planned - owner pass
Windows forced-colors mode and 200% browser zoomPlanned - owner pass
Automated axe-core scansPerformed - 16 September 2026, apps/learn/e2e/a11y-smoke.spec.ts (pnpm --filter @devolt/learn e2e): @axe-core/playwright against the WCAG 2.2 A and AA tag set on the three session-free Learn routes (landing, and the two dev module previews that mount the real lesson player), each route is given a settle beat before the scan so contrast is measured on settled colours rather than mid-animation blends. A serious or critical violation fails the run; lesser ones print. Two tracked landing-page contrast findings are exempted by node (S20-A11Y-11, S20-A11Y-12). Authenticated routes are out of scope for the scan and stay with the owner pass.

The owner pass is scripted in memory/audits/2026-09-16-learn-at-pass-template.md. Rows below marked Not Evaluated are the rows that pass depends on.

Applicable standards and guidelines

StandardIncluded in this report
WCAG 2.2 Level AYes, criterion by criterion
WCAG 2.2 Level AAYes, criterion by criterion
WCAG 2.2 Level AAANo
Revised Section 508 (2017 refresh, 36 CFR 1194 Appendices A-C)Yes, by chapter, mapped to the WCAG tables
EN 301 549 v3.2.1, clause 9 (Web)Yes, by clause, mapped to the WCAG tables

WCAG 2.2 removed SC 4.1.1 Parsing, so it does not appear below.

Conformance terms

  • Supports: the functionality meets the criterion without known defects.
  • Partially Supports: some functionality meets the criterion; a named gap does not.
  • Does Not Support: the majority of the functionality does not meet the criterion.
  • Not Applicable: the criterion is not relevant to this product.
  • Not Evaluated: not yet assessed. Every such row here is waiting on the owner assistive-technology pass.

Table 1: WCAG 2.2 Level A

CriterionConformanceRemarks
1.1.1 Non-text ContentPartially SupportsThe canvas is not left opaque: packages/simcore/src/canvas/a11y/mirror.ts maintains an off-screen DOM mirror of the circuit with aria-roledescription “circuit editor”, a summary (“Circuit: Blink. 3 components, 2 wires, 4 nets.”), a flat components listbox where each component row is followed by its pin rows, per-pin net connection names (“anode on Node 0”, “not connected”), a wires list, a nets list, breadboard tie-point addresses, and aria-selected tracking store selection. Decorative SVG is aria-hidden and icon-only controls carry aria-label. Gaps: the oscilloscope trace, cursors and measurements have no text alternative (S20-A11Y-2), and the mirror’s structure tree carries no live node values (voltages are read on request only, S20-A11Y-9).
1.2.1 Audio-only and Video-only (Prerecorded)Not ApplicableLesson content is text plus an interactive circuit. Neither the simulator nor Learn ships prerecorded audio or video.
1.2.2 Captions (Prerecorded)Not ApplicableNo prerecorded media.
1.2.3 Audio Description or Media Alternative (Prerecorded)Not ApplicableNo prerecorded media.
1.3.1 Info and RelationshipsPartially SupportsThe class progress matrix (apps/learn/components/tracker/ProgressMatrix.tsx) is a real table with thead, th scope="col" module headers and th scope="row" student names; per-cell status chips expose one accessible name on the wrapper so the chip reads as a unit. Lesson bodies are Markdown, so headings, lists and emphasis carry through. The canvas mirror uses a listbox plus lists rather than a tree, by design. The matrix now carries a visually-hidden caption naming the class and its student and module counts, so it no longer announces as an unnamed table (S20-A11Y-7 caption fixed 2026-09-16). Gap: the below-md card view has still not been checked for information parity with the table (S20-A11Y-7).
1.3.2 Meaningful SequenceSupportsSingle-column DOM flow on the lesson, library and teach surfaces; the canvas mirror is a sibling of the canvas element, so it reads in position. Verified by code review; reading-order spot check is part of the owner pass.
1.3.3 Sensory CharacteristicsNot EvaluatedObjective failure reasons are engine-generated and name components, but lesson prose has not been reviewed for instructions that depend on shape, colour or on-screen position. Not Evaluated (owner AT pass pending; see the template).
1.4.1 Use of ColorPartially SupportsStatus is paired with text almost everywhere: the teacher console uses labelled badges (“All caught up”, “Ended”, “Manual advance”), objective state is announced as words, and the scope status line carries an aria-label. Gap: on-canvas simulation state (current-flow arrows, LED and probe colour) is conveyed by colour alone with no equivalent in the mirror (S20-A11Y-9).
1.4.2 Audio ControlNot ApplicableNothing plays audio automatically.
2.1.1 KeyboardPartially SupportsA keyboard read and build path v1 exists for the canvas and is shared by all three hosts (simulator workspace, Learn lesson player, Learn module creator). It covers: Up/Down to step every row and Left/Right to jump component to component; select-on-focus; Enter or Space on a component to toggle a single-pole switch (interact.ts) or to read that component’s live pin voltages aloud on request; Shift+arrow to nudge the cursored component one tie-pitch, announcing the new breadboard address or “Can’t move there”; two-pick wiring, Enter on a source pin then Enter on a target pin, with Escape or a same-pin re-press cancelling and refusals (“already connected”, self-wire) announced assertively; Escape with no pending wire returning focus to the canvas; global chords (undo, redo, delete, duplicate, rotate, select all) acting on the keyboard selection; and the camera panning a cursored off-screen component into view, using an instant pan under prefers-reduced-motion. Parts can now also be ADDED from the keyboard: Enter or Space on a palette tile arms it through the same placement store touch tap-to-place uses (the armed state is exposed as aria-pressed, not just an accent ring), and Enter inside the mirror drops it at the cursored pin’s tie point, or at the first free hole in the board’s top bank when no pin is cursored, announcing “Placed Resistor at column 12, row B”; Escape disarms with an announcement from either end; a refusal is assertive (“No room for …”, “Can’t place …: add a breadboard first”) and leaves the part armed so another hole can be tried. Candidate holes are validated by the same computeDrop the pointer and drag-and-drop paths use, so keyboard placement obeys identical collision and alignment rules. Breadboards and free-standing parts, which have no hole to target, keep the existing centre-place path (S20-A11Y-1 fixed 2026-09-16). Gaps: multi-rocker DIP switches are pointer-only and say so when activated (S20-A11Y-3); oscilloscope cursors cannot be driven from the keyboard (S20-A11Y-2).
2.1.2 No Keyboard TrapSupportsThe mirror exposes an onExit path (Escape with no pending wire moves focus back to the canvas), and globalHandlerShouldYield stops the global canvas key handler from consuming keys while focus is inside the mirror or a text field. Verified by code review; a full trap sweep is part of the owner pass.
2.1.4 Character Key ShortcutsSupportsSingle-character defaults exist (R rotate, A current arrows, W wire-only drag, Space wire bend) but every entry in KEYBIND_ACTIONS is rebindable: true, and per-user overrides are resolved through resolveKeybinds, so a remapping mechanism is available. The handler also yields while focus is in a field or in the accessible mirror.
2.2.1 Timing AdjustableSupportsNo time limits. Lessons are self-paced; in a live class the gate is the teacher’s advance, not a timer. The objective live region clears its text after two seconds, which resets the region rather than expiring content.
2.2.2 Pause, Stop, HideSupportsSimulation is user-started and can be paused, and the Learn oscilloscope has an explicit pause control (“Pause oscilloscope” / “Resume oscilloscope”). prefers-reduced-motion collapses the shared duration tokens to 1ms in packages/ui/tailwind-preset.ts, so token-driven motion goes near-instant.
2.3.1 Three Flashes or Below ThresholdSupportsNo interface element flashes. Learner-built blink circuits are driven by the learner’s own timing and the shipped modules blink around 1Hz, below the threshold.
2.4.1 Bypass BlocksSupportsBoth app shells now open with a “Skip to main content” link as the first focusable element, visually hidden until it takes focus: the Learn shell (apps/learn/components/shared/AppShell.tsx, covering the library, classes, assignments and teach routes) targets its own main#main, and the simulator shell (apps/sim/components/shell/SimHeader.tsx) targets the main#main its dashboard and settings pages render. Both targets carry tabindex="-1" so focus, not just scroll position, moves. The full-screen lesson player and the simulator editor render no repeated navigation block to bypass (S20-A11Y-8 fixed 2026-09-16).
2.4.2 Page TitledSupportsPer-route titles come from Next metadata in both apps.
2.4.3 Focus OrderSupportsThe mirror patches its DOM in place rather than replacing it, so a focused row is never silently recreated and focus does not jump on circuit updates. Verified by code review; tab-order spot checks are part of the owner pass.
2.4.4 Link Purpose (In Context)SupportsProgress-matrix cells set the accessible name on the link or chip wrapper so a status cell reads as one purposeful target rather than a bare icon.
2.5.1 Pointer GesturesPartially SupportsThe canvas supports two-finger pan and pinch zoom. Single-pointer alternatives exist for placement (tap-to-place arming) but an equivalent for every multipoint or path gesture was not confirmed from code (S20-A11Y-4).
2.5.2 Pointer CancellationSupportsCanvas gestures complete on pointer-up, a pending wire can be abandoned (including from the touch action bar’s “Cancel wire”), and the cancel action is bound to Escape.
2.5.3 Label in NameSupportsIcon controls take the same text for their tooltip and their aria-label (for example the objective panel’s show/hide handle), so speech input matches the visible label.
2.5.4 Motion ActuationNot ApplicableNo device-motion or orientation input.
3.1.1 Language of PageSupportsBoth app shells set lang="en" on the root element.
3.2.1 On FocusSupportsFocusing a mirror row selects that component and, only when it is off-screen, pans it into view. This changes content within the page, not context; no focus change opens a dialog or navigates.
3.2.2 On InputSupportsNo form control submits or navigates on change; the class join form submits explicitly.
3.2.6 Consistent HelpNot EvaluatedThe accessibility and support contact is consistent in the marketing and docs footers, but the in-app help entry points in Learn have not been checked for consistent placement. Not Evaluated (owner AT pass pending; see the template).
3.3.1 Error IdentificationSupportsThe class join form pairs a real label with aria-describedby and announces each rejection through role="alert" (the fix for the June 2026 join-code-no-label finding). The lesson player surfaces save failures in a polite region (“Progress not saved. Retrying…“).
3.3.2 Labels or InstructionsSupportsForm fields carry visible labels plus descriptive help text wired up with aria-describedby; the join code field states its format before submission.
3.3.7 Redundant EntrySupportsSign-in is a magic link or an OAuth provider, so nothing is re-typed; a class join code is entered once and the membership persists.
4.1.2 Name, Role, ValuePartially SupportsThe mirror gives canvas objects names, roles and selection state; the oscilloscope channel toggles expose aria-pressed with a descriptive name. Gaps: DIP rocker state has no exposed value (S20-A11Y-3) and scope cursors have no exposed role or value (S20-A11Y-2).

Table 2: WCAG 2.2 Level AA

CriterionConformanceRemarks
1.2.4 Captions (Live)Not ApplicableThe live classroom is a realtime data channel, not an audio or video stream.
1.2.5 Audio Description (Prerecorded)Not ApplicableNo prerecorded media.
1.3.4 OrientationSupportsNo orientation lock; layouts reflow in both orientations.
1.3.5 Identify Input PurposeNot EvaluatedAutocomplete tokens on the sign-in and profile fields have not been audited. Not Evaluated (owner AT pass pending; see the template).
1.4.3 Contrast (Minimum)Partially SupportsLearn renders the AA-corrected light theme defined in the html.light block of packages/ui/tailwind-preset.ts: body text #111318 on --paper #f8fafc, accent #4338ca with --on-accent #ffffff (white on indigo, because dark-on-indigo failed AA), hover #3730a3, and status tokens re-chroma’d because the dark-theme neons fail AA on white (live 5 150 105 about 4.5:1, danger 220 38 38 about 5.9:1, volt 180 83 9 about 4.6:1 on white). That block is what closed the June 2026 white-on-lilac-contrast-fail, dark-shell-contrast and status-token-contrast-on-dark findings. Gaps: text painted into the canvas (silkscreen part labels, scope graticule and readouts) is outside the token system and has not been measured (S20-A11Y-10); and the automated scan found two token-level failures on the public Learn landing page - 12px text-mist body text at 4.24:1 (S20-A11Y-11) and the text-live status chip on its own tint at 3.35:1 (S20-A11Y-12) - both of which need a light-palette change rather than a page fix.
1.4.4 Resize TextNot EvaluatedType and spacing are rem-based through the shared preset, but 200% zoom has not been walked. Not Evaluated (owner AT pass pending; see the template).
1.4.5 Images of TextSupportsNo text-in-image assets; the wordmark is SVG with an accessible name.
1.4.10 ReflowPartially SupportsThe progress matrix swaps its data table for a card list below the md breakpoint instead of forcing a horizontal scroll, and lesson text reflows. The breadboard canvas needs a two-dimensional layout and relies on the criterion’s exception. Gap: card-view and table parity for assistive technology is unverified (S20-A11Y-7).
1.4.11 Non-text ContrastPartially SupportsControl borders use the light-theme --line #cbd5e1 and focus rings come from the shared primitives. Forced colors is now handled in the shared preset (packages/ui/tailwind-preset.ts): a @media (forced-colors: active) block pins every border to ButtonText, re-draws Tailwind ring-* shadows (which forced colors discards) as ButtonText outlines, gives :focus-visible a 2px Highlight outline, and paints the canvas mirror’s selected row in Highlight/HighlightText (S20-A11Y-10 forced-colors CSS fixed 2026-09-16). Gap: colours painted INTO the canvas - component outlines, wires, the scope graticule - are still literals outside the token system and unmeasured, so the board art itself is unchanged in forced colors (S20-A11Y-10).
1.4.12 Text SpacingNot EvaluatedNo user-stylesheet override test yet. Not Evaluated (owner AT pass pending; see the template).
1.4.13 Content on Hover or FocusNot EvaluatedTooltips are used for icon controls; their dismissible, hoverable and persistent behaviour was not confirmed from code. Not Evaluated (owner AT pass pending; see the template).
2.4.5 Multiple WaysSupportsLearn content is reachable from the library, from a course index and from direct lesson links; documentation adds sidebar navigation and search.
2.4.6 Headings and LabelsSupportsLesson, library and teach surfaces use descriptive headings; controls are labelled rather than positional.
2.4.7 Focus VisibleSupportsThe shared primitives ship focus-visible rings and the simulator keeps visible focus outlines. Verified by code review; visual confirmation is part of the owner pass.
2.4.11 Focus Not Obscured (Minimum)Not EvaluatedThe objective panel is a fixed overlay on the canvas and the classroom reconnect banner is fixed to the bottom centre, so a focused control could in principle be covered. Needs a visual check, including at 200% zoom. Not Evaluated (owner AT pass pending; see the template).
2.5.7 Dragging MovementsPartially SupportsPart placement has a non-dragging pointer path (palette arming plus a tap on the board), and wiring can be completed and cancelled from the touch action bar. Gaps: repositioning a placed part freely and moving the oscilloscope cursors still require a drag with no single-pointer alternative (S20-A11Y-4).
2.5.8 Target Size (Minimum)Not EvaluatedThe canvas scales its hit testing for touch pointers, and the shared controls are built to a 44px-class touch target, but nothing has been measured against the 24px minimum. Not Evaluated (owner AT pass pending; see the template).
3.1.2 Language of PartsNot ApplicableThe product is English-only in this version, including every assistive-technology string, which is centralised for a future locale.
3.2.3 Consistent NavigationSupportsOne shell per role (student, teacher) with navigation in the same order on every route.
3.2.4 Consistent IdentificationSupportsShared primitives mean the same action carries the same label and icon across Learn and the simulator.
3.3.3 Error SuggestionSupportsSign-in failures are mapped to specific recoverable messages (for example an expired one-time link tells the user to request a new one) rather than raw error codes, and join-code rejections say what to do next.
3.3.4 Error Prevention (Legal, Financial, Data)Not ApplicableNo legal or financial commitment, and no irreversible bulk data deletion, in the surfaces in scope. Billing is out of this report’s scope.
3.3.8 Accessible Authentication (Minimum)SupportsSign-in is an emailed magic link or Google or Microsoft OAuth. There is no password to recall, transcribe or re-type and no puzzle or cognitive function test.
4.1.3 Status MessagesSupportsVerified live regions: the lesson player keeps a visually-hidden aria-live="polite" aria-atomic="true" region outside the collapsible panel, so an objective pass announces even when the panel is closed (“Objective 2 passed: … . 2 of 3 complete.”, or “All objectives complete. 3 of 3 passed. Next is enabled.”), clearing after two seconds; the canvas announcer is a single store with polite and assertive lanes and one mounted announcer per app, with a nonce so a repeated identical reading still speaks; the classroom provider announces “Reconnecting to your class…” via role="status"; the oscilloscope status line is a role="status"; the teacher console announces its state badge (“All caught up”, “Ended”, “Manual advance”), channel degradation and session end politely. The same region now also speaks the active objective’s failure reason, but only on a transition: the engine re-emits it at roughly 8Hz, so it is announced once the text has held still for 1.2s and only when it differs from what was last spoken, and arriving on a lesson seeds the guard so opening the player is silent (S20-A11Y-5 fixed 2026-09-16). A teacher advance is announced assertively to the student - “Your teacher moved the class to the next module. Loading it now.” as the session’s module changes, then “Your teacher moved the class to <module title>” once the destination lesson has loaded and can name it - and the student’s live chip text now changes with the state instead of holding one constant string, so the chip itself announces too (S20-A11Y-6 fixed 2026-09-16). Screen-reader confirmation of both remains part of the owner pass.

Section 508 mapping

Revised Section 508 adopts WCAG 2.0 A and AA by reference for electronic content and software user interfaces. The WCAG 2.2 tables above are a superset, so each chapter below points into them rather than repeating rows.

Section 508 chapterApplicabilityConformanceWhere to read it
Chapter 3, 302 Functional Performance CriteriaAppliesPartially Supports302.1 without vision and 302.2 with limited vision are the constrained cases: the canvas read path and Learn announcements are usable, but part placement, DIP switches and the oscilloscope are not (S20-A11Y-1, S20-A11Y-2, S20-A11Y-3). 302.4 without hearing and 302.5 with limited hearing: Supports, no audio output. 302.7 with limited manipulation: Partially Supports, see 2.5.7. 302.9 with limited language or cognition: Supports, plain-language objectives and per-step guidance.
Chapter 4, HardwareNot ApplicableNot ApplicableWeb-delivered software; no hardware is supplied.
Chapter 5, 501-503 SoftwareAppliesPartially SupportsTables 1 and 2 above. Platform accessibility services are used through standard DOM and ARIA; no custom accessibility API.
Chapter 5, 504 Authoring ToolsAppliesPartially SupportsThe Learn module creator is an authoring tool. It shares the canvas accessibility layer and therefore inherits its gaps (S20-A11Y-1). Authored lesson content is Markdown, so headings, lists and links are preserved in the output.
Chapter 6, 601-603 Support Documentation and ServicesAppliesSupportsDocumentation is this site (HTML, WCAG-targeted), this report is published here, and accessibility support is reachable at hello@devoltapp.com.
E205 Electronic ContentAppliesPartially SupportsTables 1 and 2 above.

EN 301 549 v3.2.1 mapping

ClauseApplicabilityConformanceWhere to read it
9.1 PerceivableAppliesPartially SupportsWCAG rows 1.1.1 to 1.4.13 in Tables 1 and 2.
9.2 OperableAppliesPartially SupportsWCAG rows 2.1.1 to 2.5.8 in Tables 1 and 2.
9.3 UnderstandableAppliesPartially SupportsWCAG rows 3.1.1 to 3.3.8 in Tables 1 and 2.
9.4 RobustAppliesPartially SupportsWCAG rows 4.1.2 and 4.1.3 in Tables 1 and 2.
9.5 Non-interferenceAppliesSupportsThe four non-interference criteria are 1.4.2 (Not Applicable), 2.1.2, 2.2.2 and 2.3.1, all reported as Supports above.
9.6 Conformance requirementsAppliesPartially SupportsFull-page and full-process conformance is not claimed while the gaps below are open.
10 Non-web documentsNot ApplicableNot ApplicableNo non-web documents are produced as part of the product experience in scope.
11 Non-web softwareNot ApplicableNot Applicablede:volt is delivered in the browser.
12 Documentation and support servicesAppliesSupportsSee Section 508 Chapter 6 above.
13 ICT with relay or emergency service accessNot ApplicableNot ApplicableNo real-time text, relay or emergency features.

Known gaps and tracked issues

Severity uses the June 2026 production audit scale: H blocks a task for an affected user, M degrades it, L is a polish defect. Under the wave rule, every H-or-above finding from the owner assistive-technology pass is fixed in the same wave and then appears here as a row.

IdSurfaceSeverityWCAG criterionGap and planned fix
S20-A11Y-2Oscilloscope (simulator scope and Learn scope)H1.1.1, 2.1.1, 4.1.2The trace, the cursors and the measurements are painted into a canvas. Only the status line and the per-channel show/hide toggles are exposed, so there is no text alternative and no keyboard cursor control. Fix: render a keyboard-reachable readout in the DOM (per-channel value, cursor positions and delta) and bind cursor stepping to the arrow keys.
S20-A11Y-3Simulator canvasM2.1.1, 4.1.2Multi-rocker DIP switches have no single-toggle model, so they stay pointer-only and the canvas announces “use the pointer to set individual DIP switches”. Fix: expose one row per rocker in the mirror with its own state and an Enter toggle.
S20-A11Y-4Simulator canvas, oscilloscopeM2.5.1, 2.5.7Repositioning an already-placed part freely and moving the scope cursors are drag-only, and a single-pointer equivalent for every pan and pinch gesture is unconfirmed. Fix: add a “move to hole” entry path (tie-point address or stepper) for parts, tap-and-step cursors for the scope, and on-screen zoom controls to stand in for pinch.
S20-A11Y-7Learn class progress matrixL1.4.10The below-md card view has not been checked for information parity with the table. Fix: verify the card view exposes student, module and status with the same wording the table cells use. (The missing table caption, the other half of this finding, is fixed - see below.)
S20-A11Y-9Simulator canvas, Learn lesson canvasM1.1.1, 1.4.1Simulation state reaches the user through colour and motion (current-flow arrows, LED and probe colour) with no equivalent in the accessible mirror, and the mirror’s structure tree carries no node values; voltages are available only as an on-request reading of the cursored component. Fix: add per-net state text to the mirror and a text summary of conducting branches, keeping it pull-based so the polite queue is not flooded.
S20-A11Y-10Canvas paintingM1.4.3, 1.4.11Text and strokes painted into the canvas (silkscreen labels, wires, component outlines, scope graticule and readouts) sit outside the token system and have never been contrast-measured, and the painters keep their literals in forced-colors mode. Fix: measure the canvas palette against AA for text and non-text contrast, and have the painters read a forced-colors palette. (The shared UI package’s forced-colors CSS, the other half of this finding, is fixed - see below.)
S20-A11Y-11Learn landing pageM1.4.312px text-mist body text measures 4.24:1 against the light --surface (#64748b on #f0f2f5), just under the 4.5:1 small-text threshold. Found by the automated axe scan, exempted there by node so it stays visible without failing the build. Fix: darken the light-theme --mist token (it is shared by both apps, so the change needs a palette pass, not a page edit).
S20-A11Y-12Learn landing page, status chipsM1.4.3The text-live status chip measures 3.35:1 on its own bg-live/10 tint (#059669 on #e6f5f0) at 11px. The token was tuned against white, not against its tint. Fix: re-tune the light-theme live token, or give the chips a darker text token on tinted fills; audit danger and volt chips the same way.

Fixed in S20 W6

Closed by the accessibility pass; kept here so the row history stays readable.

IdFixedWhat shipped
S20-A11Y-1fixed 2026-09-16Keyboard add-part path: palette tiles arm on Enter or Space (with aria-pressed), the canvas mirror places the armed part at the cursored tie point or the first free hole in the top bank, and announces “Placed <part> at <breadboard address>”; Escape disarms; rejections are assertive and keep the part armed. Placement runs through the existing computeDrop pipeline.
S20-A11Y-5fixed 2026-09-16The objective failure reason is announced through the lesson player’s polite region on transition only, after a 1.2s settle and with a last-spoken guard, clearing after two seconds.
S20-A11Y-6fixed 2026-09-16A teacher advance is announced assertively to the student on both sides of the navigation, and the student’s live chip text changes with the state instead of holding one string.
S20-A11Y-7 (caption)fixed 2026-09-16The progress matrix has a visually-hidden caption naming the class and its student and module counts. The card-view parity check stays open above.
S20-A11Y-8fixed 2026-09-16”Skip to main content” as the first focusable element of both app shells, targeting a main#main with tabindex="-1".
S20-A11Y-10 (forced colors)fixed 2026-09-16A @media (forced-colors: active) block in the shared Tailwind preset: ButtonText borders, ring-* shadows re-drawn as outlines, a Highlight focus outline, and Highlight/HighlightText for the canvas mirror’s selected row. The canvas painters’ own literals stay open above.

Alongside these, the automated scan’s one non-contrast finding was fixed in the same pass: the lesson player’s objective badge carried an aria-label on a roleless span (prohibited, and dropped by some screen readers), so the passed and current states are real visually-hidden text now.

There is no PDF build pipeline for this report. To produce a PDF, open this page and use the browser’s own print dialog (“Save as PDF” as the destination): Ctrl+P on Windows and Linux, Cmd+P on macOS. Background graphics can be left off; the tables print as plain rows. If you need a countersigned or dated copy for a procurement file, email hello@devoltapp.com with the report version (1.0, 16 September 2026).

Feedback

Accessibility problems, accommodation requests and questions about this report go to hello@devoltapp.com. Please include the page URL, the browser and assistive technology you used, what you were trying to do and what blocked you. Issues that block account access, student learning or teacher workflows are prioritised.