Scope: the five-page public static site (index.html, check.html,
prepare.html, review.html, and evidence.html) and the separate applicant
flow in demo/app.py. Target: WCAG 2.2 Level AAA.
On check.html, a recognized jurisdiction selection also reveals the
Statewide Coverage Navigator. It is included in the static/code review below;
an automated browser profile flow covers its bounded evidence states.
Dedicated assistive-technology validation remains pending.
The static site loads a locally maintained California Design System version-0
preview compatibility layer on all five pages. It combines selected semantic
ca-* structures with published California token vocabulary, locally served
Public Sans 400/600/700, 18px body copy, 1.75 line height, and the 1,176px
shell / 876px reading widths. Permit Bearings maintains the accessibility
behavior and AAA-target extensions locally; no production-supported upstream
component release is being used. This alignment is not California Design
System or WCAG conformance, certification, State branding, affiliation, or
endorsement. See docs/DESIGN-SYSTEM.md.
The separate Python-rendered reference flow loads the same shared and product styles, uses the same bypass-link/header/footer patterns, and applies additive component hooks to its native controls and records. It is covered by static contracts, but the automated browser matrix below remains scoped to the five public task pages and named populated states.
This audit combines static and code-level checks, computed contrast, automated
axe scans of all five public pages, Lighthouse category budgets, automated
320px/390px reflow checks, populated-state checks, and limited local browser
inspection. Automation found and remediated invalid ARIA labeling on packet
status text and the ordinance results region. It is not a completed manual,
physical-device, or assistive-technology review.
Items under "Remaining" require a person using the named browsers, input
methods, or assistive technology. The executable task matrix and signed-result
requirements are defined in docs/MANUAL-VALIDATION.md; every row is
currently not_run. The corresponding machine-readable execution fields and
version locks are in data/validation/woodland-manual-evidence.json; creating
that ledger does not count as a human check.
Semantics & structure. Each page begins with the shared
#skip-to-content wrapper and link, then has one <main>, one <h1>, a
labeled viewport-appropriate primary navigation, and aria-current on the
active page. The phone navigation is a native disclosure with a 48px summary
target; the full navigation remains the desktop/tablet surface. Sections use
<h2>s. The local <ca-field>, <ca-shout>, <ca-box>, and <ca-mesh>
elements are semantically neutral grouping and layout wrappers: labels,
descriptions, headings, status regions, lists, and landmarks remain explicit
inside them. .ca-button styles native buttons and links without replacing
their native semantics. Table utilities style semantic tables that retain
captions and <th> headers.
The applicant result puts its decision boundary, count, and candidate route
before supporting records. Its group index is a labeled <nav>, and each
nonempty group has a focusable heading that serves as a jump-link target. The
answers-used receipt is a closed native <details> containing a <dl> and an
actual edit link. Each decision record is an <article> labeled by its unique
title. Candidate-route cards keep the same visible non-approval heading while
including the exact route-record identity in that heading's accessible name,
so a multi-route result does not produce duplicate article names. The
separately visible route-record line is hidden from assistive technology to
avoid repeating the identity. The always-visible decision boundary is an <aside role="note">
labeled by its heading; its four states are expressed as definition-list rows
for what is shown, what remains unconfirmed, and the next step rather than by
border color alone. Automated browser contracts exercise candidate,
unresolved-fact, no-route, and source-review-hold variants. Every longer rule
explanation and evidence body starts in a closed native <details>; the
candidate route is distinguished by structure and border treatment rather
than by starting open. Citations and source-status text remain outside those
disclosures. Multiple
deadlines or thresholds use a semantic heading and list rather than
visual-only layout. All interactive elements remain native controls, so
keyboard operability and focus order come from the platform.
Illustrations. The landing and project-check introduction images are
project-specific machine-generated, locally served WebP assets. They restate the nearby pathfinding
theme but add no instructions, status, evidence, or decision meaning, so each
uses alt="" inside an aria-hidden figure. Intrinsic width and height avoid
layout shift. The project-check image is hidden at the single-column
breakpoint to preserve the form's visual priority; removing either image does
not remove content or an interaction. Static tests verify the local paths,
WebP container integrity, actual encoded dimensions, bounded byte size, empty
alternatives, and intrinsic-size declarations. Browser coverage verifies image
decoding plus boundary-first ordering at the 768px and 928px breakpoints.
This is code-level evidence, not a completed screen-magnification or cognitive
accessibility review.
The separate ADU date tool is an optional closed native disclosure. A link in the configured current ADU route opens that same disclosure before moving to its fragment target; the date field, applicability checkboxes, action, status, and bounded deadline copy remain unchanged. Browser checks cover the closed state, keyboard expansion, and route-link opening.
The Statewide Coverage Navigator is a closed native <details> hidden until a
recognized jurisdiction value resolves. Its summary names the jurisdiction
and bounded record counts; its body has a visible location label and a
three-row definition list for the bounded
statewide candidate-rule set, local requirements status, and public HCD record
history. A limited local layer renders source links in a list; linked HCD
records are behind a native <details>/<summary> disclosure and retain
English source metadata with lang="en"; each HCD link's programmatic name
also identifies its jurisdiction, date, authority, and HAU number where
recorded. The outer disclosure has a 64px minimum target, and the nested HCD
disclosure has a 44px minimum target. The local-onboarding boundary is a
visible role="note". These labels make Not encoded, limited local records,
and no linked HCD record readable as different states rather than relying on
color. The profile is not a live region; the existing polite jurisdiction
status announces whether a selected value resolved. Screen-reader reading
order, the timing of the newly revealed section, and disclosure behavior
remain manual checks.
The evidence page exposes rule-review coverage as text, not color alone: all
19 current rules are labeled machine_linked, and the zero named-human and
zero jurisdiction-approval counts remain visible. This is a status disclosure,
not an accessibility or substantive-review approval.
The packet sample is explicitly labeled as a source-bound future-state simulation because Woodland's official page checked 2026-08-09 says “Preapproved ADU List: Coming soon!” It is not presented as a currently usable plan or applicant-ready workflow. The page has one labeled main region and one page heading. Its made-up packet cover, finding counts, program-status boundary, and source record use visible text and definition lists. Missing and unresolved findings are labeled articles with headings and visible text states. Reported-present, not-applicable, and not-evaluated groups use native disclosures. The generated manifest and official checklist use descriptive links. These observations come from markup, regression checks, and limited visual inspection; browser reading order and assistive-technology output remain pending.
The exact valid future-state simulation entry adds one labeled evidence-summary section only after entry, integrity, source-currency, and program-availability checks pass. Its public ID/version and facts use definition lists, preparation actions use an ordered list, direct staff questions use an unordered list, and source records retain descriptive links and visible status text. A native button opens the browser print dialog. The entire section, including that control, remains hidden on direct, invalid, or availability-blocked packet entry. A missing, malformed, or expired availability record produces a visible hold instead of revealing the handoff or summary. These structures are covered by static and browser checks; their screen-reader reading order, status-announcement quality, and printed-page usability have not been manually reviewed.
On 2026-07-29, the generated packet sample was inspected in the local in-app browser at 1280, 390, and 320 CSS-pixel viewport widths. The page had no horizontal document overflow at those widths, the generated output replaced its busy state without a visible load error, and the expected headings and visible trust labels rendered. Computed token contrast checks and spot checks of link and disclosure target heights were also recorded. This inspection did not establish keyboard operation, screen reader output, real-device zoom behavior, or conformance for the other four static pages.
On 2026-08-09, axe-core 4.12.1 reported no WCAG 2.0/2.1/2.2 A, AA, or tagged
AAA violations on index.html, check.html, prepare.html, review.html,
or evidence.html in a Playwright-managed Chromium build. Forty-five browser
checks also exercise each page at 320px and 390px, open the compact navigation,
check document-level overflow, scan a populated applicant result, verify that
mobile evidence tables render as labeled records, cover route-first order and
the collapsed/expanded jurisdiction, answers, statewide, and deadline
disclosures, and cover valid/invalid journey-summary disclosure. The
evidence-page checks now cover the adopted
all-unchanged source receipt, an internally consistent changed receipt with
its derived review queue, and an unverifiable receipt that warns without
staling a dependent. A print-media check isolates the summary from the
site and detailed packet surfaces and checks horizontal overflow at an
816-by-1056 CSS-pixel viewport. Lighthouse 13.4.1 audits all five initial pages
plus the populated applicant sample and exact valid journey entry using its
mobile profile. On 2026-08-09, all seven states met 1.00 accessibility and best
practices, at least 0.97 performance, and at least 0.90 SEO. CI repeats both
suites on pull requests, default-branch pushes, and weekly. A first performance
sample below the 0.90 budget triggers two confirmation samples and evaluates
their median; the budget itself is unchanged.
On 2026-08-21 the optional runtime AI panel (ADR 0004) was added to
check.html as a closed native details disclosure above the form. Its
controls are native buttons with 44px targets, its status lines are
role="status" live regions, the drafted answers render as a labeled
definition list inside a region with a focusable heading, and the
explanation and staff-question blocks are headed sections appended after the
result. A browser check confirms the panel starts closed, makes no request
until the applicant enables it, shows the "service not running" fallback
without altering the structured form, survives the language toggle, and
passes the axe scan in both states. The human/assistive-technology pass
below has not covered this panel either; the live-region timing of a 20-40
second model call is untested with a screen reader.
These automated results cover only rules the tools can evaluate. They do not establish WCAG conformance, substitute for the remaining keyboard and screen-reader work, or promote the Spanish machine drafts to reviewed copy.
Component-alignment contract. Static checks require the local compatibility
stylesheet to load before product styles on every public page, the shared
bypass-link structure to remain first, and applicable native controls and
semantic tables to retain the selected ca-* compatibility hooks. The
successor package contributes no runtime JavaScript or custom-element
behavior; the neutral ca-* wrappers must therefore remain understandable
from their contained HTML when CSS is absent. These checks guard this
repository's version-0 contract only. They are not upstream component tests or
evidence of California Design System approval.
Focus appearance (2.4.13, AAA-new). Global :focus-visible uses a 3px
accent2-300 ring with a 2px offset and a 5px primary-900 outer ring.
The inner gold is 5.87:1 against the primary surface and the outer blue is
10.68:1 against the page. Two colors keep the indicator visible across white,
gold, and blue components.
Target size (2.5.5 AAA / 2.5.8 AA). Buttons, selects, inputs, and
textareas have min-height: 44px; checkbox/radio rows get ≥44px hit areas
via label padding. Result evidence summaries also have a 44px minimum target.
Inline text links fall under the in-text exception.
Motion (2.3.3). The only animation (smooth scroll to results) is
disabled under prefers-reduced-motion: reduce.
Labels & names. Visible <legend> and <label> text for conditional
radio groups; the jurisdiction combobox is named by its visible legend and
described by help and status text. The ordinance textarea and date input have
explicit accessible names; the trust meter has role="img" with a
descriptive label. Required applicant facts use explicit Yes / No / “I'm not
sure” choices instead of favorable checkbox defaults.
Dynamic updates. A visually hidden status announces the grouped result
summary and boundary text without turning the full result collection into a
live region. This avoids announcing every source excerpt when several records
match. Scan findings, clock output, and jurisdiction status also use
aria-live="polite". The zero-result status includes the full staff-review
routing message instead of announcing only a count. The amendment rehearsal
announces how many matching guidance records were withheld or restored, then
moves focus to the newly available reset/rehearse control. After screening,
keyboard focus moves to the result heading; scanner and clock live regions
announce a concise summary instead of their full result tables or cards.
The packet page marks its generated output aria-live="polite" and
aria-busy="true" until rendering completes. Static checks confirm those
attributes and the visible loading and error containers. Actual announcement
timing, verbosity, and focus behavior have not been checked with a browser or
screen reader. The same visible status path communicates when strict
program-availability evidence is missing, malformed, or expired; it does not
silently expose the future-state simulation.
The shareable ADU sample is introduced by descriptive link text and followed by a visible disclosure that identifies the facts as hypothetical. It fills the same native form controls and invokes the same form submission path as manual answers, so the existing result-status announcement applies. For the automatically submitted sample, focus moves to the disclosure instead of skipping past it to the results. A sample-only link jumps to the generated result, and a plainly labeled link clears the sample. Editing a sample fact relabels the disclosure, removes the sample URL parameter, and clears the old results until the edited form is submitted. The same invalidation rule applies to ordinary applicant answers: changing the jurisdiction or a named project fact removes the rendered result, temporary answers-used cover sheet, and remembered disclosure state. This prevents a result from remaining visible beside facts that no longer produced it.
Selecting or clearing a recognized jurisdiction renders or hides the closed
coverage-profile disclosure before project submission. That render only reads bundled data and does
not move the selection into the packet URL, create a browser record, or add a
new asynchronous busy state. Static/code review covers the profile's generated
summary, definition list, nested native disclosure, links, and boundary note. The
automated browser test jurisdiction coverage profile separates statewide, local, and HCD evidence exercises Albany's zero-record state, Alameda's
linked-record disclosure, Davis's limited local layer, and Los Angeles
County's Not encoded state. It checks the 17-record statewide baseline,
profile data attributes, the HCD link name and target height, clear/unknown
selection cleanup, changed-statewide and changed-local source holds, empty
browser storage, no document overflow, and an automated axe scan. Separate
browser checks exercise keyboard expansion and axe/reflow at 320px and 390px
with the jurisdiction and optional-clock disclosures both closed and open.
That is not a screen-reader or physical-device test.
Every resolved jurisdiction result also includes a closed orientation-receipt disclosure built with native headings, definition lists, and lists. Its print action calls the browser's print dialog, and print media expands and isolates the receipt from navigation, intake, detailed result cards, and controls. Automated coverage checks the receipt's reflow and print overflow across representative city, county, incorporation-update, and bounded-local-layer profiles. Printed-output reading order and assistive-technology behavior remain manual checks.
Data-loading state. Data-dependent pathway, scanner, and trust-rehearsal
buttons use the native disabled state until their datasets are ready. A
load failure places a visible explanation in the relevant results area and a
short message in the result status region instead of leaving empty controls
that appear operable. The packet page also clears its busy state and presents
a visible load error. Strict program-availability validation is part of this
fail-closed boundary: the handoff and printable simulation stay unavailable
when its canonical record is missing, malformed, or past its recheck date.
These behaviors are covered by code inspection and static tests, not a manual
assistive-technology failure rehearsal.
Link purpose (2.4.9 AAA). Every link's text alone states its target ("view scan findings (JSON)", full source citations, named statutes).
Not color-alone. Statuses are icon + word badges; decorative badge glyphs are hidden from assistive technology, and hue never carries meaning by itself. The current-source badge is deliberately neutral rather than approval-green.
Contrast: 1.4.6 Enhanced (7:1 normal text), computed from the adopted tokens:
| Pair | Ratio |
|---|---|
| Black body ink / gray-50 page | 20.12:1 |
| Gray-800 secondary ink / gray-50 page | 8.44:1 |
| Primary-900 link / gray-50 page | 10.68:1 |
| White button text / primary-900 | 11.15:1 |
| Success-900 / success-100 badge | 9.58:1 |
| Warning-900 / accent2-100 badge | 9.57:1 |
| Danger-900 / danger-100 badge | 9.56:1 |
Status meter fills are graphical objects with adjacent text labels; meaning does not depend on color. The gray-700 form-control boundary is 6.39:1 against its white surface.
Visual presentation (1.4.8, partial). Base line height is 1.75; reading content is capped at the published 876px width; no text is justified. At phone widths, wide evidence tables become labeled records and the automated 320px and 390px checks find no document-level horizontal scroll. User-selectable colors/spacing beyond browser and OS mechanisms are not provided.
Print presentation (automated emulation only). Readiness-page print CSS withholds the site header/footer, task hero, detailed packet surfaces, and print control while keeping the evidence summary. It uses print-sized type, visible black borders/status text, overflow wrapping for IDs and URLs, and break avoidance on bounded records. The printed surface remains a labeled future-state synthetic record, not a currently available City plan or applicant-ready packet. The automated check uses Chromium print media; it does not inspect a generated PDF, paper output, pagination across printer drivers, or assistive reading of a saved file.
Language switching. The applicant page preserves the current intake and
rendered result set, temporary answers-used receipt, and rule-disclosure state when
English/Spanish interface copy is toggled. Stable rule anchors do not change
with the language. Source material that remains English carries lang="en".
The Python reference demo's language link is labeled as starting over because
that server-rendered surface does not preserve form state. Both surfaces state
that language selection covers the applicant form and results, while the
staff-facing trust, ordinance, and clock tools remain English.
- Execute and sign every row in
docs/MANUAL-VALIDATION.md. Preparing that record did not complete any human or assistive-technology test. - Screen-reader walkthrough (VoiceOver/NVDA): reading order, datalist combobox behavior, Statewide Coverage Navigator reveal/disclosure behavior, packet-result reading order, and live-region verbosity.
- 200% and 400% zoom checks on real devices. Automated Chromium reflow passes at 320px and 390px, but that is not a physical-device, browser-zoom, or virtual-keyboard test.
- Physical iPhone/Safari and Android/Chrome task runs, including the compact section menu, form editing with the virtual keyboard, disclosures, and labeled evidence records.
- Browser Print and Save-as-PDF review in Safari, Chrome, and Firefox, including pagination, link destinations, long-value wrapping, grayscale, and reading order in the resulting artifact.
forced-colors/ high-contrast mode spot check.- Spanish screen-reader pronunciation check.
- Mixed-language audit: translated intake/result labels and Spanish
plain-language drafts carry explicit
langmetadata. Pathway titles, source-derived notes/excerpts, and document hints are marked English. The Spanish drafts have not had human or semantic-parity review, and the source-linked letter metadata, scanner, clocks, dashboard, and sources remain partly or wholly English. - Keyboard-only end-to-end run of all five static flows.
- AAA content-level judgments (3.1.5 reading level): the new plain-language layer is structurally simpler, but no formal readability or comprehension assessment has been done).