You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The 10 JSX sources under src/ contain 21 <a> tags, 19 of them target="_blank", and 8 <img> tags. Nothing checks any of them.
This repository declares no ESLint configuration — package.json has no eslintConfig block and there is no .eslintrc* or eslint.config.* file, so eslint-plugin-jsx-a11y and react/jsx-no-target-blank, the two rules that
would normally cover this ground, never run. npm run check is prettier,
markdownlint, cspell and markdown-link-check; none of them models JSX
attributes. docusaurus build compiles JSX without auditing it.
Three invariants are consequently unguarded end to end:
target="_blank" without rel="noopener"/rel="noreferrer". The opened
page receives a live window.opener handle back to this site and can
navigate it (reverse tabnabbing). Current browsers imply noopener for target="_blank"; older ones and in-app webviews do not. Today 19 of 19
anchors are correct, but 10 of them spell it rel="noreferrer" and 9 spell
it rel="noopener noreferrer" — an inconsistency that makes a future
omission look like just another variant rather than a defect.
<img> with no alt attribute. A screen reader then announces the file
name. All 8 current images are correct, and 6 deliberately use alt="" to
mark themselves decorative — which is the right call and must keep being
distinguishable from forgetting the attribute.
A literal http:// href or src. Blocked as mixed content on the https
site. Zero today.
All three hold on main right now, which is exactly why the guard should land
now: a test that pins a currently-green invariant costs nothing and stops the
first regression, whereas these defects are all silent — they ship a clean
build and a green deploy.
Why existing gates miss it, and disjointness from open work
No ESLint, and no test in the repository reads a JSX attribute.
Add tests/jsx-link-hygiene.test.mjs — one new test-only file, no production
change, no new dependency, no package.json or lockfile edit. It scans src/
JSX source text (no transform needed) and asserts the three invariants above,
plus a non-vacuity assertion that the scan actually found the tags and that
every scanned <a> carries an href, which is what catches a scanner that
truncated tags at a > inside an attribute value.
tests/jsx-link-hygiene.test.mjs added and mutation-checked
A PR implementing exactly this is attached to this issue.
Evidence
Revision 00b44df, node v26.8.2, run locally 2026-09-20.
Counts: 10 JSX files under src/; 21 <a>; 19 target="_blank"; 8 <img>. rel spellings: 10 x noreferrer, 9 x noopener noreferrer.
ESLint absence: no .eslintrc*, no eslint.config.*, no eslintConfig key
in package.json.
Unit coverage: node --test --experimental-test-coverage -> 55/55 pass
before, 59/59 after. No existing test file reads any JSX attribute.
Finding
The 10 JSX sources under
src/contain 21<a>tags, 19 of themtarget="_blank", and 8<img>tags. Nothing checks any of them.This repository declares no ESLint configuration —
package.jsonhas noeslintConfigblock and there is no.eslintrc*oreslint.config.*file, soeslint-plugin-jsx-a11yandreact/jsx-no-target-blank, the two rules thatwould normally cover this ground, never run.
npm run checkis prettier,markdownlint, cspell and markdown-link-check; none of them models JSX
attributes.
docusaurus buildcompiles JSX without auditing it.Three invariants are consequently unguarded end to end:
target="_blank"withoutrel="noopener"/rel="noreferrer". The openedpage receives a live
window.openerhandle back to this site and cannavigate it (reverse tabnabbing). Current browsers imply
noopenerfortarget="_blank"; older ones and in-app webviews do not. Today 19 of 19anchors are correct, but 10 of them spell it
rel="noreferrer"and 9 spellit
rel="noopener noreferrer"— an inconsistency that makes a futureomission look like just another variant rather than a defect.
<img>with noaltattribute. A screen reader then announces the filename. All 8 current images are correct, and 6 deliberately use
alt=""tomark themselves decorative — which is the right call and must keep being
distinguishable from forgetting the attribute.
http://href or src. Blocked as mixed content on the httpssite. Zero today.
All three hold on
mainright now, which is exactly why the guard should landnow: a test that pins a currently-green invariant costs nothing and stops the
first regression, whereas these defects are all silent — they ship a clean
build and a green deploy.
Why existing gates miss it, and disjointness from open work
tests/static-assets.test.mjs) checks that/imgpaths resolve; itdoes not read
rel,alt, or URL schemes.src/, not link/imageattributes. [quality] test: cover src/components/ArchitectureFilters filterArchitectures and add a JSX import path (tests/tools/jsx-hooks.mjs + tests/helpers-jsx.mjs + tests/architecture-filters.test.mjs) #229 covers
filterArchitectures; test: cover useFocusTrap scroll lock, tab cycling and focus restore #268 coversuseFocusTrap—both single functions, neither reads markup.
docs//blog/, notsrc/JSX.CNCFProjectCardandimport-architectures.mjsproductionbehaviour around hrefs; neither adds a repository-wide markup assertion.
MemberDirectoryinto sub-modules. The check below walks thewhole
src/tree by file extension rather than assuming oneindex.jsperdirectory, so it keeps working across that split.
Recommendation
Add
tests/jsx-link-hygiene.test.mjs— one new test-only file, no productionchange, no new dependency, no
package.jsonor lockfile edit. It scanssrc/JSX source text (no transform needed) and asserts the three invariants above,
plus a non-vacuity assertion that the scan actually found the tags and that
every scanned
<a>carries anhref, which is what catches a scanner thattruncated tags at a
>inside an attribute value.tests/jsx-link-hygiene.test.mjsadded and mutation-checkedA PR implementing exactly this is attached to this issue.
Evidence
00b44df, node v26.8.2, run locally 2026-09-20.src/; 21<a>; 19target="_blank"; 8<img>.relspellings: 10 xnoreferrer, 9 xnoopener noreferrer..eslintrc*, noeslint.config.*, noeslintConfigkeyin
package.json.node --test --experimental-test-coverage-> 55/55 passbefore, 59/59 after. No existing test file reads any JSX attribute.
playwright/cypress/puppeteer, and CI publishes no coverage artifact fromany suite (tracked in [quality] CI publishes no coverage evidence, so coverage findings cannot be verified #186), so no claim is made that these paths lack
end-to-end coverage.
Priority
— hive: agent=quality backend=copilot model=claude-opus-5