<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.2.1">Jekyll</generator><link href="https://www.ckundo.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://www.ckundo.com/" rel="alternate" type="text/html" /><updated>2026-06-14T21:07:23+00:00</updated><id>https://www.ckundo.com/feed.xml</id><title type="html">Software, Accessibility, and Immersive Tech</title><subtitle>Cameron is a software development leader, immersive tech pioneer, and accessibity specialist.</subtitle><author><name>Cameron Cundiff</name></author><entry><title type="html">Accessibility Tooling for Agentic Coding Loops</title><link href="https://www.ckundo.com/2026/02/accessibility-tooling-for-agentic-coding-loops.html" rel="alternate" type="text/html" title="Accessibility Tooling for Agentic Coding Loops" /><published>2026-02-23T00:41:23+00:00</published><updated>2026-02-23T00:41:23+00:00</updated><id>https://www.ckundo.com/2026/02/accessibility-tooling-for-agentic-coding-loops</id><content type="html" xml:base="https://www.ckundo.com/2026/02/accessibility-tooling-for-agentic-coding-loops.html">&lt;p&gt;Coding agents are writing and modifying front-end code at scale. If your team maintains a design system or owns accessibility on a product, this changes the calculus: code that once went through a human review loop is now generated in seconds, often without any accessibility check at all.&lt;/p&gt;

&lt;p&gt;Existing tools weren’t designed for this. They assume a human in the loop; run a scan, read the report, interpret the findings, figure out the fix. That workflow requires domain expertise at every step.&lt;/p&gt;

&lt;p&gt;An agent operating in a coding loop needs something different. Not a report to interpret, but structured diagnostics it can act on directly: machine-executable fix instructions, DOM context for reasoning, a fixability classification for triage, and a verification mechanism to confirm fixes landed.&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://www.npmjs.com/package/@accesslint/mcp&quot;&gt;@accesslint/mcp&lt;/a&gt; is an MCP server built on &lt;a href=&quot;https://github.com/AccessLint/core&quot;&gt;@accesslint/core&lt;/a&gt;, a rule engine designed from the ground up for agent consumption. It exposes tools for auditing HTML (as a string, file, or URL), diffing before-and-after audits, and listing rules; for Claude Code, Cursor, Windsurf, or any MCP-compatible agent.&lt;/p&gt;

&lt;p&gt;This post walks through the design decisions behind the tool: how violations are structured, how fixability classification works, what context collection looks like per rule, and how the diff loop closes the audit-fix-verify cycle.&lt;/p&gt;

&lt;h2 id=&quot;does-it-actually-help&quot;&gt;Does it actually help?&lt;/h2&gt;

&lt;p&gt;Before getting into the design, here’s the evidence. Both approaches - agent with MCP tools vs. agent alone - were benchmarked across 25 HTML test cases covering 67 fixable WCAG violations (3 runs each, Claude Opus):&lt;/p&gt;

&lt;table&gt;
  &lt;thead&gt;
    &lt;tr&gt;
      &lt;th&gt; &lt;/th&gt;
      &lt;th&gt;With @accesslint/mcp&lt;/th&gt;
      &lt;th&gt;Agent alone&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Violations fixed&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;99.5% (200/201)&lt;/td&gt;
      &lt;td&gt;93.5% (188/201)&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Regressions&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;1.7 / run&lt;/td&gt;
      &lt;td&gt;2.0 / run&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Cost&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;$0.56 / run&lt;/td&gt;
      &lt;td&gt;$0.62 / run&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Duration&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;270s / run&lt;/td&gt;
      &lt;td&gt;377s / run&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;&lt;strong&gt;Timeouts&lt;/strong&gt;&lt;/td&gt;
      &lt;td&gt;0 / 63 tasks&lt;/td&gt;
      &lt;td&gt;2 / 63 tasks&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;The MCP-assisted path uses 23% fewer output tokens per run. Without tools, the agent has to recall WCAG rules from training data, reason about which rules apply to which elements, and then fix them. The MCP replaces that open-ended reasoning with structured output: specific rule IDs, CSS selectors pointing to exact elements, and concrete fix suggestions. The agent skips straight to applying fixes. Fewer reasoning steps, fewer tokens, less time, lower cost.&lt;/p&gt;

&lt;p&gt;The largest gains are on complex cases. A test case with 6 violations across nested landmark structures completed in 25-38 seconds with MCP tooling. The agent alone timed out at 90 seconds in 2 of 3 runs.&lt;/p&gt;

&lt;h2 id=&quot;anatomy-of-a-violation&quot;&gt;Anatomy of a violation&lt;/h2&gt;

&lt;p&gt;When an agent calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;audit_html&lt;/code&gt;, each violation includes:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;1. [CRITICAL] labels-and-names/button-name
   Button has no discernible text.
   Element: button.icon-search
   HTML: &amp;lt;button class=&quot;icon-search&quot; onclick=&quot;openSearch()&quot;&amp;gt;&amp;lt;svg aria-hidden=&quot;true&quot;&amp;gt;...&amp;lt;/svg&amp;gt;&amp;lt;/button&amp;gt;
   Fix: add-text-content
   Fixability: contextual
   Browser hint: Screenshot the button to identify its icon or visual label,
   then add a matching aria-label.
   Context: Classes: icon-search
   Guidance: Screen reader users need to know what a button does. Add visible
   text content, aria-label, or aria-labelledby. For icon buttons, use
   aria-label describing the action (e.g., aria-label=&apos;Close&apos;). If the button
   contains an image, ensure the image has alt text describing the button&apos;s
   action.
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Each field is deliberate:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fix&lt;/strong&gt; is a structured instruction from a closed set: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add-attribute&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;set-attribute&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove-attribute&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add-element&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;remove-element&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add-text-content&lt;/code&gt;, or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;suggest&lt;/code&gt;. The first six are mechanically executable. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;suggest&lt;/code&gt; is the escape hatch for violations where the fix depends on intent. About 75% of rules provide a mechanical fix; the remaining 25% use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;suggest&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Fixability&lt;/strong&gt; classifies the violation, not the fix. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;button-name&lt;/code&gt; rule provides a mechanical fix type (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add-text-content&lt;/code&gt;) that satisfies the rule. But its &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;contextual&lt;/code&gt; classification signals that the agent should use the collected context to determine &lt;em&gt;what&lt;/em&gt; text to add. More on this below.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt; is collected per-rule from the DOM. &lt;strong&gt;Guidance&lt;/strong&gt; provides the remediation principle behind the rule, written for direct LLM consumption. &lt;strong&gt;Browser hint&lt;/strong&gt;, present on 26 of 92 rules, tells agents with browser access how to verify or improve a fix using screenshots or DevTools.&lt;/p&gt;

&lt;h2 id=&quot;determining-fixability&quot;&gt;Determining “Fixability”&lt;/h2&gt;

&lt;p&gt;Every rule carries a fixability classification that the MCP server surfaces on each violation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Mechanical&lt;/strong&gt; (20 rules): Deterministic. A positive &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;tabindex&lt;/code&gt; gets set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;0&quot;&lt;/code&gt;. A non-valid ARIA role gets flagged with the correct value. No ambiguity.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Contextual&lt;/strong&gt; (65 rules): Requires surrounding context, but an LLM can reason about it. The violation’s context and guidance fields provide the inputs. The structured fix provides a safe floor.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Visual&lt;/strong&gt; (4 rules): Requires rendered output. Color contrast, primarily. Browser hints tell the agent how to inspect computed styles or screenshot the element.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The interaction between fixability and the structured fix is the core design decision. Take &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;button-name&lt;/code&gt;: the fix type is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;add-text-content&lt;/code&gt;, which is mechanically executable. But what text? The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;contextual&lt;/code&gt; classification is the signal to use the collected context. The violation reports &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Classes: icon-search&lt;/code&gt;. That’s developer intent that never made it into the accessible name. The agent reads the class, infers the action, and adds &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aria-label=&quot;Search&quot;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;This also maps to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;list_rules&lt;/code&gt; filtering. An agent or workflow can query rules by fixability to scope an audit pass: mechanical-only for automated batch remediation, contextual for agent-assisted passes, visual for flagging to human review.&lt;/p&gt;

&lt;h2 id=&quot;gathering-context&quot;&gt;Gathering context&lt;/h2&gt;

&lt;p&gt;Each rule gathers the specific context its violation type needs. This is where the design diverges most from existing tools, which tend to report the element and stop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;button-name&lt;/code&gt;&lt;/strong&gt; reports CSS class names (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;btn-close&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;icon-search&lt;/code&gt;), the enclosing form’s label, and the nearest heading. Class names are the key signal. They encode developer intent that never made it into the accessible name. A button with class &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;icon-search&lt;/code&gt; inside a form labeled “Site search” gives the agent two independent signals pointing to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aria-label=&quot;Search&quot;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;img-alt&lt;/code&gt;&lt;/strong&gt; checks whether the image is inside a link (and captures the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;href&lt;/code&gt;), looks for a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;figcaption&lt;/code&gt;, and captures adjacent text. If a figcaption already describes the image, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;alt=&quot;&quot;&lt;/code&gt; avoids redundant adjacent text. If the image is a standalone link, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;href&lt;/code&gt; helps the agent infer purpose.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;form-label&lt;/code&gt;&lt;/strong&gt; reports the input’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;type&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;placeholder&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;id&lt;/code&gt;, plus the full accessible name computation chain: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aria-labelledby&lt;/code&gt; resolution, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;aria-label&lt;/code&gt;, associated &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;label&amp;gt;&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;title&lt;/code&gt;, and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;placeholder&lt;/code&gt; fallback.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;link-name&lt;/code&gt;&lt;/strong&gt; captures the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;href&lt;/code&gt;, nearby heading text, and parent element context. For a link wrapping only an icon, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;href&lt;/code&gt; and surrounding headings are often enough to infer purpose.&lt;/p&gt;

&lt;p&gt;The goal is to front-load enough information that the agent can reason about the fix in a single pass, without a round-trip to read more of the document.&lt;/p&gt;

&lt;h2 id=&quot;looping-on-diffs&quot;&gt;Looping on diffs&lt;/h2&gt;

&lt;p&gt;The diff loop is the verification mechanism. The workflow:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;The agent calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;audit_html&lt;/code&gt; with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;name: &quot;before&quot;&lt;/code&gt; to audit and store the result.&lt;/li&gt;
  &lt;li&gt;The agent applies fixes.&lt;/li&gt;
  &lt;li&gt;The agent calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;diff_html&lt;/code&gt; with the updated markup and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;before: &quot;before&quot;&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The server audits the new HTML, diffs against the stored result, and returns:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;Summary: 2 fixed, 1 new, 3 remaining

FIXED:
  - [CRITICAL] text-alternatives/img-alt at img[src=&quot;photo.jpg&quot;]
  - [CRITICAL] labels-and-names/button-name at button.icon-search

NEW:
  - [SERIOUS] aria/aria-roles at div[role=&quot;buton&quot;]
    ARIA role &quot;buton&quot; is not a valid role value.
    Fix: set-attribute role=&quot;button&quot;

REMAINING:
  - [MODERATE] navigable/heading-order at h4
  - [MODERATE] distinguishable/link-in-text-block at a.subtle
  - [MINOR] text-alternatives/image-alt-words at img[alt=&quot;image of logo&quot;]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Violations are matched by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ruleId + selector&lt;/code&gt;. The agent gets a clear signal: what was fixed, what regressed (with the diagnosis and fix instruction for self-correction), and what remains.&lt;/p&gt;

&lt;p&gt;The NEW category is critical. An agent that adds &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;role=&quot;buton&quot;&lt;/code&gt; (a typo) gets the regression surfaced immediately, with a structured fix to correct it. The loop is: audit, fix, diff, self-correct. No human in the middle.&lt;/p&gt;

&lt;h2 id=&quot;opening-up-the-browser&quot;&gt;Opening up the browser&lt;/h2&gt;

&lt;p&gt;26 rules carry a browser hint: an instruction for agents with browser access (screenshots, DevTools MCP) on how to verify or improve a fix.&lt;/p&gt;

&lt;p&gt;For a visual rule like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;color-contrast&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Violation context includes computed colors and ratio. After changing colors, use JavaScript to read getComputedStyle() on the element and recalculate the contrast ratio. Screenshot the element to verify the fix looks correct in context.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For a contextual rule like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;button-name&lt;/code&gt;:&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Screenshot the button to identify its icon or visual label, then add a matching aria-label.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;These are opt-in. An agent without browser tools ignores them. An agent with browser MCP tools (Chrome DevTools, Playwright) can use them to bridge the gap between static analysis and rendered output, particularly for the 4 visual rules where static analysis alone can’t fully verify the fix.&lt;/p&gt;

&lt;h2 id=&quot;handling-page-fragments&quot;&gt;Handling page fragments&lt;/h2&gt;

&lt;p&gt;When the input HTML lacks &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;!DOCTYPE html&amp;gt;&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&amp;lt;html&amp;gt;&lt;/code&gt;, the server auto-enables component mode, suppressing 22 page-level rules (document title, landmarks, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;lang&lt;/code&gt; attribute, etc.) that would produce false positives on isolated markup. The agent can override this with the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;component_mode&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;This means an agent auditing a React component, a partial template, or a code snippet gets relevant results without noise.&lt;/p&gt;

&lt;h2 id=&quot;architecture&quot;&gt;Architecture&lt;/h2&gt;

&lt;p&gt;The MCP server is a thin integration layer. The rule engine is &lt;a href=&quot;https://github.com/AccessLint/core&quot;&gt;@accesslint/core&lt;/a&gt;: 92 rules, zero runtime dependencies, synchronous execution. The API is &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runAudit(doc: Document): AuditResult&lt;/code&gt;. The server handles HTML parsing (happy-dom), fragment detection, audit state for diffing, and violation enrichment (joining each violation with its rule’s fixability, browser hint, and guidance).&lt;/p&gt;

&lt;p&gt;The core library covers 23 WCAG 2.1 success criteria across Level A and AA, scoped to what static DOM analysis can meaningfully check. Rules that throw during execution are caught and skipped; the audit always completes.&lt;/p&gt;

&lt;p&gt;The library also exports lower-level primitives (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getAccessibleName&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;getComputedRole&lt;/code&gt;, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;isAriaHidden&lt;/code&gt;) and a declarative rule engine for authoring rules as JSON with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;validateDeclarativeRule&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compileDeclarativeRule&lt;/code&gt;. An agent can write, validate, and register new rules at runtime.&lt;/p&gt;

&lt;h2 id=&quot;a-note-on-the-core-engine&quot;&gt;A note on the core engine&lt;/h2&gt;

&lt;p&gt;Every MCP tool call is a round-trip through the protocol, and an agent running audit-fix-diff in a loop may make dozens of them. Latency per call matters. Engines like axe-core were designed at the outset for the browser; the execution model is async and demonstrably slow&lt;sup id=&quot;fnref:1&quot; role=&quot;doc-noteref&quot;&gt;&lt;a href=&quot;#fn:1&quot; class=&quot;footnote&quot; rel=&quot;footnote&quot;&gt;1&lt;/a&gt;&lt;/sup&gt;. The latency accrues.&lt;/p&gt;

&lt;p&gt;The rule engine is @accesslint/core: 92 rules, 23 WCAG 2.1 success criteria (Level A and AA), zero runtime dependencies, synchronous execution. The MCP server parses HTML with happy-dom and calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;runAudit(doc): AuditResult&lt;/code&gt;. A typical component audit completes in single-digit milliseconds. At that speed, the bottleneck is the LLM, not the tooling.&lt;/p&gt;

&lt;p&gt;The structured output described throughout this post (fix suggestions, fixability classifications, per-rule context, browser hints) are first-class fields on every violation, not bolted on after the fact. The rules are authored with agent consumption in mind.&lt;/p&gt;

&lt;hr /&gt;

&lt;p&gt;To add the MCP server to Claude Code:&lt;/p&gt;

&lt;div class=&quot;language-sh highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;claude mcp add accesslint &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; npx &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; @accesslint/mcp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Or add it to any MCP client configuration:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;mcpServers&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;accesslint&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;command&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;npx&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;args&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-y&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;@accesslint/mcp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s next&lt;/h2&gt;

&lt;p&gt;Testing in the DOM is important for many accessibility tests, and doesn’t &lt;em&gt;have&lt;/em&gt; to be slow. Keep an eye out for improvements to the &lt;a href=&quot;https://www.npmjs.com/package/@accesslint/storybook-addon&quot;&gt;AccessLint StoryBook Addon&lt;/a&gt; soon that will help.&lt;/p&gt;

&lt;p&gt;I’d love to hear from you! Please share questions and comments, or drop me a note, I’m always happy to nerd out on accessibility.&lt;/p&gt;
&lt;div class=&quot;footnotes&quot; role=&quot;doc-endnotes&quot;&gt;
  &lt;ol&gt;
    &lt;li id=&quot;fn:1&quot; role=&quot;doc-endnote&quot;&gt;
      &lt;p&gt;&lt;a href=&quot;https://observablehq.com/d/e26301f8709bf07a&quot;&gt;@accesslint/core vs axe-core benchmarks&lt;/a&gt; &lt;a href=&quot;#fnref:1&quot; class=&quot;reversefootnote&quot; role=&quot;doc-backlink&quot;&gt;&amp;#8617;&lt;/a&gt;&lt;/p&gt;
    &lt;/li&gt;
  &lt;/ol&gt;
&lt;/div&gt;</content><author><name>Cameron Cundiff</name></author><category term="accessibility" /><category term="javascript" /><category term="ai" /><category term="webdev" /><summary type="html">Coding agents are writing and modifying front-end code at scale. If your team maintains a design system or owns accessibility on a product, this changes the calculus: code that once went through a human review loop is now generated in seconds, often without any accessibility check at all.</summary></entry><entry><title type="html">Gesture Control in Adobe XD</title><link href="https://www.ckundo.com/2019/06/gesture-control-adobe-xd.html" rel="alternate" type="text/html" title="Gesture Control in Adobe XD" /><published>2019-06-25T00:00:00+00:00</published><updated>2019-06-25T00:00:00+00:00</updated><id>https://www.ckundo.com/2019/06/gesture-control-adobe-xd</id><content type="html" xml:base="https://www.ckundo.com/2019/06/gesture-control-adobe-xd.html">&lt;p&gt;Adobe XD launched key command support recently, that allows you to trigger actions based on gamepad or keyboard input.
You can use this feature to control your prototype with other inputs, by mapping values to keyboard events on the web.&lt;/p&gt;

&lt;video controls=&quot;&quot; width=&quot;500&quot;&gt;
  &lt;source src=&quot;/assets/images/xd-gestures.mp4&quot; /&gt;
&lt;/video&gt;

&lt;p&gt;I created a Chrome Extension (&lt;a href=&quot;https://github.com/ckundo/xd-gestures&quot;&gt;code on GitHub&lt;/a&gt;) that takes in a webcam feed and translates hand gestures to key commands.&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;It uses TensorFlow.js and the &lt;a href=&quot;https://github.com/tensorflow/tfjs-models/tree/master/posenet&quot;&gt;PoseNet model&lt;/a&gt; to identify your hand position relative to your body.&lt;/li&gt;
  &lt;li&gt;Then, the extension checks if your hands are in one of a few orientations to your shoulders and face.&lt;/li&gt;
  &lt;li&gt;If the gesture matches a command, the plugin creates a keyboard event that is sent to the canvas, like so:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;const&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;KeyboardEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;keydown&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;key&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;w&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// replace &quot;w&quot; with the keyboard trigger value&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;document&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;querySelector&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;#root-1&lt;/span&gt;&lt;span class=&quot;dl&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;dispatchEvent&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ev&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The XD web application handles the rest!&lt;/p&gt;

&lt;p&gt;Combine this with voice commands, that are built into XD, to prototype a multimodal experience.&lt;/p&gt;</content><author><name>Cameron Cundiff</name></author><category term="Adobe XD" /><category term="prototyping" /><category term="tensorflow" /><category term="gestures" /><summary type="html">Adobe XD launched key command support recently, that allows you to trigger actions based on gamepad or keyboard input. You can use this feature to control your prototype with other inputs, by mapping values to keyboard events on the web.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.ckundo.com/assets/images/gesture-xd.png" /><media:content medium="image" url="https://www.ckundo.com/assets/images/gesture-xd.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Auto Mute Video Calls using Head Rotation</title><link href="https://www.ckundo.com/2019/03/bose-ar-mute-conference-call.html" rel="alternate" type="text/html" title="Auto Mute Video Calls using Head Rotation" /><published>2019-03-30T00:00:00+00:00</published><updated>2019-03-30T00:00:00+00:00</updated><id>https://www.ckundo.com/2019/03/bose-ar-mute-conference-call</id><content type="html" xml:base="https://www.ckundo.com/2019/03/bose-ar-mute-conference-call.html">&lt;p&gt;When you look away from your screen during a conference call, it’s probably
because someone interrupted you, or you need to have a sidebar. It can be
anywhere from distracting to embarrassing to have these interactions get
broadcast to your meeting room. Microsoft recently released a background
blurring feature for these situations:&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/-wCyq9oll_o&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;This weekend I wrote some code with a similar goal. It disables your video in a
conference call when you turn away from the screen. It does that by connecting to
Bose AR wearable devices – in the demo my Bose Frames – to tell what direction
your head is turning. Then it disables the camera if you’re looking away.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/SZagtIsUx9Q&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;The bookmarklet connects using the Web Bluetooth API, via a &lt;a href=&quot;https://github.com/zakaton/Bose-Frames-Web-SDK&quot;&gt;JavaScript port of
the Bose AR SDK&lt;/a&gt;. I made it for fun and to learn more about spatial audio and
audio AR. If you want to try it out: in Google Chrome, drag this link to your
bookmarks bar:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;javascript:(function () { var script = document.createElement(&apos;SCRIPT&apos;); script.src = &apos;https://s3.amazonaws.com/bose-ar/content_script.js&apos;; document.body.appendChild(script); })()&quot;&gt;Enable Auto Mute&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then open a meeting in the Zoom.us web client, and click the bookmark to load the
code. Then, when you turn on the camera, you’ll get a prompt to connect your
Bose Frames or QC35 II headphones.&lt;/p&gt;</content><author><name>Cameron Cundiff</name></author><category term="AR" /><category term="hardware" /><category term="Bose Frames" /><category term="Bose AR" /><summary type="html">When you look away from your screen during a conference call, it’s probably because someone interrupted you, or you need to have a sidebar. It can be anywhere from distracting to embarrassing to have these interactions get broadcast to your meeting room. Microsoft recently released a background blurring feature for these situations:</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.ckundo.com/assets/images/pundit-kid-meme.png" /><media:content medium="image" url="https://www.ckundo.com/assets/images/pundit-kid-meme.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Create a GitHub PR via a link</title><link href="https://www.ckundo.com/2019/03/github-pr-from-link.html" rel="alternate" type="text/html" title="Create a GitHub PR via a link" /><published>2019-03-20T00:00:00+00:00</published><updated>2019-03-20T00:00:00+00:00</updated><id>https://www.ckundo.com/2019/03/github-pr-from-link</id><content type="html" xml:base="https://www.ckundo.com/2019/03/github-pr-from-link.html">&lt;p&gt;It’s possible to auto-populate a PR with parameters in a link, like you can with
a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;mailto:&lt;/code&gt; link.&lt;/p&gt;

&lt;h2 id=&quot;parameters&quot;&gt;Parameters&lt;/h2&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;https://github.com/[account]/[repo]/new/master \
  ?filename=filename \
  &amp;amp;value=file contents \
  &amp;amp;message=PR title \
  &amp;amp;description=PR description \
  &amp;amp;quick_pull=base-branch-name \
  &amp;amp;target_branch=new-branch-name
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;demo&quot;&gt;Demo&lt;/h2&gt;

&lt;p&gt;Try it out. &lt;a href=&quot;https://www.github.com/ckundo/demo-pr-link/new/master?filename=accesslint-test.html&amp;amp;value=&amp;lt;img src=&amp;quot;logo.png&amp;quot;&amp;gt;&amp;amp;message=[DO NOT MERGE] Testing AccessLint&amp;amp;description=Expect comments from AccessLint&amp;amp;quick_pull=master&amp;amp;target_branch=accesslint-test&quot;&gt;Open a test PR&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/github-pr.jpg&quot; alt=&quot;GitHub Pull Request UI, pre-populated&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;ruby-helper&quot;&gt;Ruby Helper&lt;/h2&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;    &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;link_to_test_pr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;options: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{})&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;hostname&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://www.github.com/&quot;&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;url_params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;project&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;*&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%w(new master)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;URI&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;hostname&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;url_params&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;query_params&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sx&quot;&gt;%w(
        filename=accesslint-test.html
        value=&amp;lt;img\ src=&quot;logo.png&quot;\ /&amp;gt;
        message=[DO\ NOT\ MERGE]\ Testing\ AccessLint
        description=Expect\ comments\ from\ AccessLint
        quick_pull=master
        target_branch=accesslint-test
      )&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&amp;amp;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;query_params&lt;/span&gt;

      &lt;span class=&quot;n&quot;&gt;link_to&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;.test&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;uri&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_s&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;ss&quot;&gt;class: &lt;/span&gt;&lt;span class=&quot;n&quot;&gt;options&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:class&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;])&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;background&quot;&gt;Background&lt;/h2&gt;

&lt;p&gt;I uncovered this while building &lt;a href=&quot;https://github.com/marketplace/accesslint&quot;&gt;AccessLint GitHub App&lt;/a&gt;, to open demo PR to test
the build integration.&lt;/p&gt;</content><author><name>Cameron Cundiff</name></author><category term="web" /><category term="git" /><category term="accesslint" /><category term="github" /><category term="pull request" /><summary type="html">It’s possible to auto-populate a PR with parameters in a link, like you can with a mailto: link.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.ckundo.com/assets/images/github-pr.jpg" /><media:content medium="image" url="https://www.ckundo.com/assets/images/github-pr.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Bose Frames Prescription Lenses</title><link href="https://www.ckundo.com/2019/03/bose-frames-prescription-lenses.html" rel="alternate" type="text/html" title="Bose Frames Prescription Lenses" /><published>2019-03-18T00:00:00+00:00</published><updated>2019-03-18T00:00:00+00:00</updated><id>https://www.ckundo.com/2019/03/bose-frames-prescription-lenses</id><content type="html" xml:base="https://www.ckundo.com/2019/03/bose-frames-prescription-lenses.html">&lt;p&gt;I’ve been wearing the new &lt;a href=&quot;https://www.bose.com/en_us/products/wearables/frames.html&quot;&gt;Bose Frames&lt;/a&gt; full time since January. They’re audio
AR glasses that are low profile, inexpensive, have solid sensor hardware, and an
application platform via iOS, Android, and Unity &lt;a href=&quot;https://bosedevs.bose.com/&quot;&gt;developer SDKs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/assets/images/bose-frames-rx.jpg&quot; alt=&quot;Selfie wearing Bose Frames&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The Bose Frames are sold as sunglasses, and Bose doesn’t offer a prescription
option. Despite that, I took them to my local optometrist, &lt;a href=&quot;https://jamesleonard.com/&quot;&gt;James Leonard&lt;/a&gt;, and
they agreed to try fitting them with my prescription. Turned out it was not an
issue, and mostly covered under my vision insurance plan.&lt;/p&gt;

&lt;p&gt;All in, Frames and lenses, I spent $280. That’s pretty good for some slick
prescription AR glasses.&lt;/p&gt;</content><author><name>Cameron Cundiff</name></author><category term="AR" /><category term="hardware" /><category term="Bose Frames" /><summary type="html">I’ve been wearing the new Bose Frames full time since January. They’re audio AR glasses that are low profile, inexpensive, have solid sensor hardware, and an application platform via iOS, Android, and Unity developer SDKs.</summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.ckundo.com/assets/images/bose-frames-rx.jpg" /><media:content medium="image" url="https://www.ckundo.com/assets/images/bose-frames-rx.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Talking Web - Voice Control using Semantic HTML and JavaScript</title><link href="https://www.ckundo.com/2018/09/talking-web-voice-ui-semantic-html.html" rel="alternate" type="text/html" title="Talking Web - Voice Control using Semantic HTML and JavaScript" /><published>2018-09-24T11:04:00+00:00</published><updated>2018-09-24T11:04:00+00:00</updated><id>https://www.ckundo.com/2018/09/talking-web-voice-ui-semantic-html</id><content type="html" xml:base="https://www.ckundo.com/2018/09/talking-web-voice-ui-semantic-html.html">&lt;p&gt;An HTML element and its attributes that carry intrinsic meaning. When you compose these elements into a page or web application, in a way that is semantic (and accessible), support for Voice control falls into place.&lt;/p&gt;

&lt;h2 id=&quot;searching-for-meaning&quot;&gt;Searching for Meaning&lt;/h2&gt;

&lt;p&gt;Let’s look an example using Search. Search is a pretty easy choice, because it’s such ubiquitous, consistent, and standardized feature of web pages. This is a Chrome extension I’ve dubbed “Say11y”, to demonstrate using website search with Voice, with no added HTML or back-end APIs.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/H736Y3U3cmE&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;The plugin uses the Speech Recognition API to access the microphone and capture utterances. Once the recognizer detects a discernible phrase, we look up the command. In this case, if the phrase starts with “Search”, we find a HTML element on the page that meets one of these selector criteria:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;input type of search&lt;/li&gt;
  &lt;li&gt;aria label that starts with “search”&lt;/li&gt;
  &lt;li&gt;value, name, or class attribute that starts with “search”&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we find a matching element, we set the form value for that field to the content of the utterance that following the command, and submit the form.&lt;/p&gt;

&lt;h2 id=&quot;leaning-on-standards&quot;&gt;Leaning on Standards&lt;/h2&gt;

&lt;p&gt;It took me a couple of hours to make this work fairly consistently across a dozen different sites that I tried. That’s because, when web content creators adhere to standards, it’s trivial to make Voice control work.&lt;/p&gt;

&lt;p&gt;This can apply to many different features. Here are a couple off the top of my head:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;
    &lt;blockquote&gt;
      &lt;p&gt;“Search for hard cheese.” (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;input[type=&apos;search&apos;]&lt;/code&gt;)&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;blockquote&gt;
      &lt;p&gt;&lt;em&gt;“Found Cheddar, Manchego, and Swiss”&lt;/em&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;[role=&apos;main&apos;] &amp;gt; ul&lt;/code&gt;)&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;blockquote&gt;
      &lt;p&gt;“Describe cheddar”&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;blockquote&gt;
      &lt;p&gt;&lt;em&gt;“Aged 2 years, from Vermont. $4.99”&lt;/em&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;dd&lt;/code&gt;)&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;blockquote&gt;
      &lt;p&gt;“Add cheddar”&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;blockquote&gt;
      &lt;p&gt;&lt;em&gt;“How much?”&lt;/em&gt;&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;blockquote&gt;
      &lt;p&gt;“3 ounces”&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;blockquote&gt;
      &lt;p&gt;&lt;em&gt;“Added.”&lt;/em&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;form input[type^=&apos;number&apos;]&lt;/code&gt;)&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;blockquote&gt;
      &lt;p&gt;“Where am I?”&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;blockquote&gt;
      &lt;p&gt;&lt;em&gt;“Shopping cart”&lt;/em&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;h1&lt;/code&gt;)&lt;/p&gt;
    &lt;/blockquote&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;accessibility-innovations&quot;&gt;Accessibility Innovations&lt;/h2&gt;

&lt;p&gt;Web standards advocates have long seen the benefits of semantic markup for
accessibility and screen reader support. Now, with the advent of Voice
Assistants, it should be clear that this is a universal principal. I’m excited
to see how we can take other lessons from accessible technology to set us up for
the future.&lt;/p&gt;</content><author><name>Cameron Cundiff</name></author><category term="accessibility" /><category term="voice" /><category term="javascript" /><category term="html" /><summary type="html">An HTML element and its attributes that carry intrinsic meaning. When you compose these elements into a page or web application, in a way that is semantic (and accessible), support for Voice control falls into place.</summary></entry><entry><title type="html">Assistive Tech with AR Way-finding</title><link href="https://www.ckundo.com/2018/09/ar-outloud-seat-finder-app.html" rel="alternate" type="text/html" title="Assistive Tech with AR Way-finding" /><published>2018-09-24T11:04:00+00:00</published><updated>2018-09-24T11:04:00+00:00</updated><id>https://www.ckundo.com/2018/09/ar-outloud-seat-finder-app</id><content type="html" xml:base="https://www.ckundo.com/2018/09/ar-outloud-seat-finder-app.html">&lt;p&gt;I joined a mini-batch at the Recurse Center last week, and spent my time
researching computer vision for people with visual impairments. I came up with
an app to find empty seats in a crowded space, like a classroom or conference
hall. Here’s a breakdown of what I did and why.&lt;/p&gt;

&lt;p&gt;This clip shows me panning the device around the room. Sighted users will
see a box outlining the chairs in the viewport.&lt;/p&gt;

&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube-nocookie.com/embed/ksPkmel11Y4?rel=0&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;h2 id=&quot;the-problem&quot;&gt;The Problem&lt;/h2&gt;

&lt;p&gt;A person with a visual impairment may find it annoying and uncomfortable to find
a seat at an event. From talking with friends of mine who are blind, this is
especially true because&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;Blind folks get unwanted attention when way-finding.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This can lead to disruptions, and sometimes unwelcome guidance.&lt;/p&gt;

&lt;h2 id=&quot;hypothesis&quot;&gt;Hypothesis&lt;/h2&gt;

&lt;p&gt;Mobile phones now have enough power to run real-time object recognition, using
their built in cameras and processing power. We can use these advancements to
augment traditional way-finding devices like canes and service animals.&lt;/p&gt;

&lt;h2 id=&quot;challenges&quot;&gt;Challenges&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Recording in public is still taboo and conspicuous.&lt;/li&gt;
  &lt;li&gt;People with disabilities don’t have a lot of cash to spend, due to high
unemployment rates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;design&quot;&gt;Design&lt;/h2&gt;

&lt;p&gt;I built the initial design using Adobe XD, using auto-animate and audio output
features. In the process I discovered that audio cues needed to be very
succinct and in some cases replaced with vibration, and that I’d need to signal
obstacles, distance, and placement, as well as just identifying the object.&lt;/p&gt;

&lt;h2 id=&quot;approach&quot;&gt;Approach&lt;/h2&gt;

&lt;p&gt;There are many computer vision models for neural networks. Some have existing
ports to mobile phones. iOS also has it’s own native object recognition in ARKit.&lt;/p&gt;

&lt;p&gt;iOS is more popular than Android with people using screen readers, so I
started there. I tried ARKit’s object classifier first, but it does not identify
more that one object at once. It’s also limited to newer devices.&lt;/p&gt;

&lt;p&gt;Instead of ARKit 2, I turned to Forge, an iOS toolkit for neural networks.
It includes the YOLO neural network. This implementation is limited to 20
object categories, but they include chairs and sofas. Recognition is super fast,
on the order of milliseconds, and works reliably in a handful of conditions.
It’s also not restricted to newer devices.&lt;/p&gt;

&lt;p&gt;There’s still work left to do, specifically adding placement and distance
indication.&lt;/p&gt;

&lt;h2 id=&quot;credit&quot;&gt;Credit&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://machinethink.net/&quot;&gt;Matthijs Hollemans&lt;/a&gt;’s work porting YOLO to iOS made
this possible. Also, thanks to Gus and Chancey for the usability feedback, and
to the Recurse Center for being a great place to learn and experiment.&lt;/p&gt;</content><author><name>Cameron Cundiff</name></author><category term="accessibility" /><category term="ar" /><category term="computer vision" /><summary type="html">I joined a mini-batch at the Recurse Center last week, and spent my time researching computer vision for people with visual impairments. I came up with an app to find empty seats in a crowded space, like a classroom or conference hall. Here’s a breakdown of what I did and why.</summary></entry></feed>