Skip to content

feat: add template-anchor-has-content#35

Open
johanrd wants to merge 13 commits intomasterfrom
feat/template-anchor-has-content
Open

feat: add template-anchor-has-content#35
johanrd wants to merge 13 commits intomasterfrom
feat/template-anchor-has-content

Conversation

@johanrd
Copy link
Copy Markdown
Owner

@johanrd johanrd commented Apr 21, 2026

Note

This is part of a series where Claude has audited eslint-plugin-ember against jsx-a11y, vuejs-accessibility, angular-eslint, lit-a11y and html-validate, ember-template-lint, and the HTML and WCAG specs.

Summary

  • Premise 1: Every <a href> rendered to the DOM is exposed as a link to assistive tech. Per ACCNAME 1.2 §4.3.2 Computation steps, the accessible name is computed in order from aria-labelledby (step 2B), then aria-label (step 2D), then host-language label sources / descendant text content (steps 2E–2F), then a tooltip attribute such as title (step 2I). For an <a> element with none of these, the result is the empty string. An anchor with none of these has no accessible name; a screen reader announces "link" with nothing else. Authoring guidance: WCAG 2.1 SC 2.4.4 Link Purpose.
  • Premise 2: jsx-a11y/anchor-has-content and vuejs-accessibility/anchor-has-content both flag anchors with no accessible-name source; our plugin doesn't have an equivalent today.
  • Conclusion: Add template-anchor-has-content. Flags <a href> with no text, no accessible-name attribute (aria-label / aria-labelledby / title), and no child that contributes an accessible name. Dynamic cases (mustache-only content) stay accepted — we can't know at lint time whether they resolve to a non-empty name.

Four ecosystem positions on valueless aria-hidden

The question "what does <span aria-hidden> (bare), aria-hidden="" (empty), or aria-hidden={{false}} mean?" has no single authoritative answer:

# Source Interpretation Evidence
1 jsx-a11y Valueless → hidden Side effect of jsx-ast-utils coercing valueless JSX → boolean true. Quirk: string aria-hidden="true" is NOT recognized. Not a deliberate ARIA interpretation.
2 vue-a11y Anything not literal "false" → hidden isHiddenFromScreenReader.ts: non-spec shortcut.
3 axe-core / W3C ACT Rules Valueless/empty → INCOMPLETE axe PR #3635; ACT Rule 6a7281 scopes out empty values as inapplicable.
4 WAI-ARIA 1.2 spec Valueless/empty → default undefined → not hidden §aria-hidden value table.

Design choice for this rule

We lean toward fewer false positives. For this rule, that means treating a child with valueless / empty aria-hidden as NOT hidden — if someone writes <a href="/x"><span aria-hidden>X</span></a>, the child's content likely still contributes a name, so we don't flag the anchor as having no accessible content. Only explicit aria-hidden="true" / {{true}} hides the child subtree from the name computation.

Prior art

Verified each peer in source:

Plugin Rule Behavior
jsx-a11y anchor-has-content Flags <a> without text content or a recognized accessible-name attribute.
vuejs-accessibility anchor-has-content Flags <a> lacking content and aria-label; configurable accessibleChildren / accessibleDirectives let callers widen what counts as a name source.
lit-a11y No anchor-has-content equivalent. Its anchor-is-valid covers href validity (noHref/invalidHref/preferButton aspects), not accessible-name content.
@angular-eslint/template No equivalent rule.

Flags

<a href="/x" />
<a href="/x"><span aria-hidden="true">X</span></a>
<a href="/x"><img aria-hidden="true" alt="Nope" /></a>
<a href="/x"><img /></a>
<a href="/x" aria-label="" />
<a href="/x">   </a>

Allows

<a href="/x">link text</a>
<a href="/x"><span aria-hidden>X</span></a>        {{! valueless — child content counts }}
<a href="/x"><img aria-hidden alt="Nope" /></a>    {{! same }}
<a href="/x" aria-label="Close" />
<a href="/x">{{@label}}</a>
<a href="/x"><img alt="Search" /></a>
<Link href="/x" />

Ports jsx-a11y/anchor-has-content and vuejs-accessibility/anchor-has-content
to Ember templates. Requires every <a href> to expose a non-empty accessible
name so screen readers announce something meaningful.

An anchor is flagged when:
- It is empty, whitespace-only, or has aria-label="" / aria-labelledby=""
  / title="" on itself.
- Every child contributes nothing to the accessible name: aria-hidden
  subtrees, <img> with no alt or empty alt, <img aria-hidden> even with
  alt (hidden subtrees don't surface alt).

The rule is permissive about opacity:
- Dynamic content ({{@foo}}, {{this.x}}, {{#if ...}}) is trusted — we
  cannot statically tell what it renders.
- Component invocations as children (PascalCase, @arg, this.x, foo.bar,
  foo::bar) are treated as opaque.
- Component invocations at the <a> position are ignored (only plain <a>
  is in scope).
- Anchors without href are left alone — covered by
  template-link-href-attributes.
- A dynamic aria-label / aria-labelledby / title value is accepted.

Scope decision matches existing rule precedent:
- href-gating mirrors the "only interactive anchors" treatment in
  template-no-invalid-interactive.
- Component detection inlines the pattern from template-no-invalid-
  interactive.js:184 (lib/utils/is-component-invocation.js is not yet
  on master).
- Dynamic-value tolerance mirrors template-no-invalid-link-text.

Not added to template-lint-migration — opt-in.

Closes the M1 finding in the Phase 3 audit for anchor-has-content
(audit/phase3/anchor-has-content).
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Apr 21, 2026

🏎️ Benchmark Comparison

Benchmark Control (p50) Experiment (p50) Δ
js small 10.35 ms 10.42 ms +0.6%
js medium 4.94 ms 5.02 ms +1.6%
js large 1.96 ms 1.92 ms -1.8%
gjs small 846.29 µs 844.82 µs -0.2%
gjs medium 430.16 µs 431.31 µs +0.3%
gjs large 170.59 µs 170.73 µs +0.1%
gts small 857.27 µs 857.38 µs +0.0%
gts medium 431.90 µs 431.16 µs -0.2%
gts large 170.70 µs 170.60 µs -0.1%

🟢 faster · 🔴 slower · 🟠 slightly slower · ⚪ within 2%

Full mitata output
clk: ~3.51 GHz
cpu: AMD EPYC 9V74 80-Core Processor
runtime: node 24.14.1 (x64-linux)

benchmark                   avg (min … max) p75 / p99    (min … top 1%)
------------------------------------------- -------------------------------
js small (control)            12.06 ms/iter  13.30 ms ██                   
                       (8.65 ms … 24.92 ms)  22.24 ms ██▇▂▅  ▂             
                    (  6.02 mb …  10.13 mb)   7.11 mb █████▆▃█▆▃▁▁▃▃▁▁▁▁▃▆▆

js small (experiment)         10.97 ms/iter  11.82 ms  █▂ █ ▂              
                       (9.24 ms … 15.62 ms)  14.65 ms ▇██ █▂█   ▂▅         
                    (  6.26 mb …   7.85 mb)   6.80 mb ███▇███▄▇▄██▁▄▁▇▁▄▄▄▇

                             ┌                                            ┐
                             ╷┌─────────┬───┐                             ╷
          js small (control) ├┤         │   ├─────────────────────────────┤
                             ╵└─────────┴───┘                             ╵
                               ╷ ┌───┬──┐        ╷
       js small (experiment)   ├─┤   │  ├────────┤
                               ╵ └───┴──┘        ╵
                             └                                            ┘
                             8.65 ms           15.44 ms            22.24 ms

summary
  js small (experiment)
   1.1x faster than js small (control)

------------------------------------------- -------------------------------
js medium (control)            5.38 ms/iter   5.48 ms █▆                   
                       (4.62 ms … 12.12 ms)   9.73 ms ██▅                  
                    (  2.77 mb …   3.97 mb)   3.51 mb ███▆▄▅▄▄▃▄▁▂▁▁▂▁▁▁▁▁▂

js medium (experiment)         5.52 ms/iter   5.67 ms █ ▅                  
                       (4.23 ms … 14.06 ms)  11.52 ms █▇██                 
                    (  2.15 mb …   4.92 mb)   3.48 mb ████▆▄▄▆▃▂▂▃▂▁▁▂▂▁▂▁▂

                             ┌                                            ┐
                               ╷┌───┬┐                         ╷
         js medium (control)   ├┤   │├─────────────────────────┤
                               ╵└───┴┘                         ╵
                             ╷┌──────┬┐                                   ╷
      js medium (experiment) ├┤      │├───────────────────────────────────┤
                             ╵└──────┴┘                                   ╵
                             └                                            ┘
                             4.23 ms            7.87 ms            11.52 ms

summary
  js medium (control)
   1.03x faster than js medium (experiment)

------------------------------------------- -------------------------------
js large (control)             2.19 ms/iter   2.08 ms █▂                   
                        (1.87 ms … 5.31 ms)   4.82 ms ██                   
                    (781.81 kb …   2.13 mb)   1.42 mb ██▅▂▂▃▂▁▂▂▁▂▁▁▁▁▁▂▁▁▁

js large (experiment)          2.17 ms/iter   2.01 ms █▇                   
                        (1.82 ms … 7.23 ms)   5.13 ms ██                   
                    (131.67 kb …   2.95 mb)   1.42 mb ██▃▃▂▂▂▁▂▂▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                              ┌───┬                                   ╷
          js large (control)  │   │───────────────────────────────────┤
                              └───┴                                   ╵
                             ╷┌───┬                                       ╷
       js large (experiment) ├┤   │───────────────────────────────────────┤
                             ╵└───┴                                       ╵
                             └                                            ┘
                             1.82 ms            3.47 ms             5.13 ms

summary
  js large (experiment)
   1.01x faster than js large (control)

------------------------------------------- -------------------------------
gjs small (control)          923.29 µs/iter 859.74 µs █                    
                      (824.96 µs … 4.60 ms)   4.38 ms █                    
                    ( 15.12 kb …   2.11 mb)   1.06 mb █▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs small (experiment)       920.67 µs/iter 856.72 µs █                    
                      (823.27 µs … 4.65 ms)   4.37 ms █                    
                    (517.75 kb …   1.62 mb)   1.05 mb █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌┬                                           ╷
         gjs small (control) ││───────────────────────────────────────────┤
                             └┴                                           ╵
                             ┌┬                                           ╷
      gjs small (experiment) ││───────────────────────────────────────────┤
                             └┴                                           ╵
                             └                                            ┘
                             823.27 µs           2.60 ms            4.38 ms

summary
  gjs small (experiment)
   1x faster than gjs small (control)

------------------------------------------- -------------------------------
gjs medium (control)         470.09 µs/iter 434.82 µs █                    
                      (416.87 µs … 4.98 ms)   1.52 ms █                    
                    (501.12 kb …   1.20 mb) 540.61 kb █▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs medium (experiment)      468.20 µs/iter 437.75 µs █                    
                      (417.03 µs … 4.36 ms)   1.51 ms █                    
                    (248.29 kb …   1.16 mb) 540.26 kb █▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌─┬                                          ╷
        gjs medium (control) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             ┌─┬                                          ╷
     gjs medium (experiment) │ │──────────────────────────────────────────┤
                             └─┴                                          ╵
                             └                                            ┘
                             416.87 µs          967.02 µs           1.52 ms

summary
  gjs medium (experiment)
   1x faster than gjs medium (control)

------------------------------------------- -------------------------------
gjs large (control)          187.59 µs/iter 173.66 µs  █                   
                      (165.93 µs … 4.50 ms) 223.99 µs  ██                  
                    ( 16.09 kb … 808.52 kb) 216.12 kb ▂██▄▄▅▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gjs large (experiment)       189.70 µs/iter 173.88 µs  █                   
                      (165.78 µs … 4.44 ms) 285.95 µs  █                   
                    (215.70 kb …   1.23 mb) 216.27 kb ██▆▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷┌──────┬             ╷
         gjs large (control) ├┤      │─────────────┤
                             ╵└──────┴             ╵
                             ╷┌───────┬                                   ╷
      gjs large (experiment) ├┤       │───────────────────────────────────┤
                             ╵└───────┴                                   ╵
                             └                                            ┘
                             165.78 µs         225.86 µs          285.95 µs

summary
  gjs large (control)
   1.01x faster than gjs large (experiment)

------------------------------------------- -------------------------------
gts small (control)          940.72 µs/iter 872.65 µs █                    
                      (835.04 µs … 5.29 ms)   4.50 ms █                    
                    (573.98 kb …   1.57 mb)   1.05 mb █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts small (experiment)       921.62 µs/iter 865.87 µs █                    
                      (834.12 µs … 5.04 ms)   4.36 ms █                    
                    (511.52 kb …   1.63 mb)   1.05 mb █▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌┬                                           ╷
         gts small (control) ││───────────────────────────────────────────┤
                             └┴                                           ╵
                             ┌┬                                         ╷
      gts small (experiment) ││─────────────────────────────────────────┤
                             └┴                                         ╵
                             └                                            ┘
                             834.12 µs           2.67 ms            4.50 ms

summary
  gts small (experiment)
   1.02x faster than gts small (control)

------------------------------------------- -------------------------------
gts medium (control)         467.20 µs/iter 437.38 µs  █                   
                      (418.41 µs … 4.62 ms) 828.88 µs ██                   
                    (272.67 kb …   1.64 mb) 540.65 kb ██▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

gts medium (experiment)      471.74 µs/iter 437.02 µs █                    
                      (417.83 µs … 5.09 ms)   1.10 ms █▃                   
                    (151.92 kb …   1.68 mb) 540.61 kb ██▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ┌──┬                       ╷
        gts medium (control) │  │───────────────────────┤
                             └──┴                       ╵
                             ┌───┬                                        ╷
     gts medium (experiment) │   │────────────────────────────────────────┤
                             └───┴                                        ╵
                             └                                            ┘
                             417.83 µs          758.61 µs           1.10 ms

summary
  gts medium (control)
   1.01x faster than gts medium (experiment)

------------------------------------------- -------------------------------
gts large (control)          188.96 µs/iter 174.01 µs  █▃                  
                      (165.69 µs … 4.62 ms) 221.65 µs  ██                  
                    (128.70 kb …   1.39 mb) 216.59 kb ▂██▇▄▆▄▂▁▁▁▁▁▁▁▁▁▁▁▁▁

gts large (experiment)       187.86 µs/iter 173.74 µs  ██                  
                      (165.42 µs … 4.31 ms) 217.66 µs  ██▂                 
                    (128.70 kb …   1.10 mb) 216.25 kb ▂███▃▆▅▃▂▁▁▁▁▁▁▁▁▁▁▁▁

                             ┌                                            ┐
                             ╷  ┌───────────────┬                         ╷
         gts large (control) ├──┤               │─────────────────────────┤
                             ╵  └───────────────┴                         ╵
                             ╷  ┌──────────────┬                       ╷
      gts large (experiment) ├──┤              │───────────────────────┤
                             ╵  └──────────────┴                       ╵
                             └                                            ┘
                             165.42 µs         193.54 µs          221.65 µs

summary
  gts large (experiment)
   1.01x faster than gts large (control)

johanrd added 2 commits April 21, 2026 20:09
…I-ARIA spec

Per WAI-ARIA 1.2 §6.6 + aria-hidden value table, a missing or empty-string
aria-hidden resolves to the default `undefined` — NOT `true`. So
<span aria-hidden>X</span> as a child of <a href="/x"> does NOT hide the
span; its content still contributes to the anchor's accessible name.

The prior behavior inherited jsx-a11y's JSX-coercion convention and
vue-a11y's "anything-not-literal-false" shortcut. Both are peer-plugin
conventions that diverge from normative ARIA. Matches the spec-first
resolution of ember-cli#2717, #19, and #33.

Moved valueless / empty aria-hidden cases from invalid → valid. Kept the
explicit aria-hidden="true" and {{true}} cases as invalid.
…+ scope)

Remove the inline PascalCase-regex isComponentInvocation heuristic in
favor of the lists + scope pattern from ember-cli#2689 / ember-cli#2724. The new
lib/utils/is-native-element.js mirrors ember-cli#2724's util byte-for-byte so
the two PRs can land in either order without conflict.

evaluateChild / evaluateChildren now thread sourceCode through the
recursion so the scope check has access to the enclosing template's
bindings.

Heuristic approaches were explicitly rejected in ember-cli#2689 because lowercase
tags CAN be components when shadowed by scope bindings. Treating custom
elements as opaque (the same as components) is a behavior improvement
— matches ember-cli#2724's convention.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new accessibility-focused template rule to the plugin to ensure <a href> elements expose a non-empty accessible name, aligning behavior with similar ecosystem rules while avoiding false positives for dynamic content.

Changes:

  • Introduces template-anchor-has-content rule with recursive accessible-name source detection (text, aria-*, title, <img alt>, aria-hidden handling).
  • Adds isNativeElement utility (HTML/SVG/MathML allowlist + scope-shadowing check) and unit tests.
  • Adds rule documentation and lists the new rule in the README.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
lib/rules/template-anchor-has-content.js Implements the new rule’s logic for detecting unlabeled <a href> anchors.
lib/utils/is-native-element.js Adds shared helper for native-element vs component discrimination (including scope shadowing).
tests/lib/rules/template-anchor-has-content.js Adds rule tests for both .gjs and .hbs parsing modes.
tests/lib/utils/is-native-element-test.js Adds unit tests for list-based native-element detection and tag set sanity checks.
docs/rules/template-anchor-has-content.md Documents rule behavior and examples.
README.md Adds the rule to the accessibility rules table.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread docs/rules/template-anchor-has-content.md
Comment thread docs/rules/template-anchor-has-content.md Outdated
Comment thread lib/rules/template-anchor-has-content.js
Comment thread lib/rules/template-anchor-has-content.js
johanrd added 2 commits April 22, 2026 12:49
'Native' is overloaded in the web platform context. The util name
remains isNativeElement (common convention in React/Vue/Angular
ecosystems for 'platform-provided, not a component'), but the JSDoc
now explicitly names three alternative meanings of 'native' that
this util does NOT answer:

  - 'native accessibility' / 'widget-ness' — interactive-roles.js
    (aria-query widget taxonomy)
  - 'native interactive content' — html-interactive-content.js
    (HTML §3.2.5.2.7 content-model question)
  - 'natively focusable' — HTML §6.6.3 sequential focus navigation

This util answers only: is this tag a first-class built-in element
of HTML/SVG/MathML, rather than a component invocation or a
scope-shadowed local binding? Callers compose it with the more
specific utils when they need a narrower question.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/rules/template-anchor-has-content.js
Comment thread lib/rules/template-anchor-has-content.js
Comment thread tests/lib/rules/template-anchor-has-content.js
@johanrd johanrd force-pushed the feat/template-anchor-has-content branch from a071bfc to 6030630 Compare April 22, 2026 17:13
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread lib/utils/is-native-element.js
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/lib/rules/template-anchor-has-content.js Outdated
Comment thread lib/utils/is-native-element.js Outdated
Comment thread lib/rules/template-anchor-has-content.js
Comment thread lib/rules/template-anchor-has-content.js
johanrd added 2 commits April 24, 2026 18:46
…ment, is-native-element JSDoc sync (Copilot review)

- Add meta.fixable: null — the rule is not autofixable; be explicit so
  eslint-plugin/consistent-output / downstream tooling doesn't misclassify
  it.
- Clarify test comment: 'detects the shadowing via scope bindings in the
  scope chain' (the implementation walks scope.variables up scope.upper,
  it does not use scope.references).
- Byte-identical sync of lib/utils/is-native-element.js (+ test) to PR #50
  canonical; drops the stale template-no-noninteractive-tabindex JSDoc
  reference (non-existent rule on master).
… helper (Copilot review)

Extract a new `getStaticAttrValue` util that resolves literal-valued
mustaches (`{{"foo"}}`, `{{true}}`, `{{-1}}`) and single-part concat
statements (`"{{true}}"`) to their static string value. `isAriaHiddenTrue`
now delegates to the helper, so quoted-mustache forms of aria-hidden
(e.g. `<a aria-hidden="{{true}}">link</a>`) are recognised the same as
their text-node counterparts when walking descendants for accessible
content.

Byte-identical carrier of lib/utils/static-attr-value.js across all PRs
that land it.
johanrd added a commit that referenced this pull request Apr 24, 2026
… via shared helper (Copilot review)

Extract a new `getStaticAttrValue` util that resolves literal-valued
mustaches (`{{"foo"}}`, `{{true}}`, `{{-1}}`) and single-part concat
statements (`"{{true}}"`) to their static string value. `isAriaHiddenTruthy`
now delegates to the helper and compares the resolved string to `'true'`
(case-insensitive, whitespace-trimmed).

Behavior change: valueless `<h1 aria-hidden>`, `aria-hidden=""`, and the
mustache-empty-string equivalents (`aria-hidden={{""}}`, `aria-hidden="{{""}}"`,
`aria-hidden={{" "}}`) are no longer treated as hidden. Per WAI-ARIA 1.2
§6.6 value table, those shapes resolve to the default `undefined` — NOT
`true` — so the empty-content check still applies. Drops the previous
"fewer false positives" deviation rationale in favour of spec-literal
consistency with sibling rules (#19, #35, #41) that share the same
aria-hidden resolution.

Byte-identical carrier of lib/utils/static-attr-value.js across all PRs
that land it.
@johanrd johanrd requested a review from Copilot April 24, 2026 17:48
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants