[quality] ProjectsBorn tiles show the external-link arrow but navigate in the same tab
Maintainers usually reply within 1 day
Assessment
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Newbie friendliness
- 30/100
- Issue type
- Bug
- Clarity
- Clearly specified
- Activity status
- Active
- Tech stack
- javascript
- Domain
- frontend
Research direction
Open src/components/ProjectsBorn/index.js and add target="_blank" to the <a> as described. Then tighten tests/projects-born-render.test.mjs to assert every tile uses target="_blank" with noreferrer. Run npm run test:mutation -- --mutate src/components/ProjectsBorn/index.js to confirm; done means tiles open in new tab and tests fail without the fix.
Written by the indexing model from the issue text.
Description
Finding
src/components/ProjectsBorn/index.js renders each project tile as an anchor to a
third-party project site with the site's external-link affordance — Visit project ↗ —
but no target="_blank":
<a
key={project.name}
className={styles.project}
href={project.url}
rel="noreferrer"
>
Every other outbound-link component in src/components sets target="_blank":
CaseStudies, RadarReports, CommunityPeople, MemberDirectory/MemberProfile,
MemberDirectory/DirectoryFreshness, MetricsDashboard, AwardsTimeline,
CNCFProjectCard and shared/MirrorSyncStatus. ProjectsBorn is the sole outlier.
So a reader who clicks a tile is taken off the site in the same tab, having been told
by the ↗ icon that a new tab would open. The section renders on two user-facing pages:
/community/projects-born and the practitioners landing page
(docs/practitioners/index.md renders <ProjectsBorn /> with its own copy).
Why no test caught it
tests/projects-born-render.test.mjs asserts the arrow is present for every tile, and
then guards the only target assertion behind the condition it is meant to check:
test('no project tile opens a new tab without noreferrer', () => {
for (const anchor of anchorsOf(ProjectsBorn({}))) {
if (anchor.props.target === '_blank') {
assert.match(anchor.props.rel ?? '', /\bnoreferrer\b/);
}
}
});
No tile sets target, so the loop body never runs and the test passes vacuously. It
would also keep passing if the anchors stopped being outbound links altogether. The
component is at 100% line/region coverage, so the coverage gate cannot see this.
There is no repository-wide gate for "an outbound link that shows the ↗ affordance
opens in a new tab" — tests/footer.test.mjs, tests/awards-timeline.test.mjs and
tests/metrics-dashboard.test.mjs each assert it only for their own component.
Recommendation
One PR, two edits.
1. src/components/ProjectsBorn/index.js — match the convention the rest of the
site already uses (CaseStudies, RadarReports, CNCFProjectCard all use exactly
this shape). Replace:
<a
key={project.name}
className={styles.project}
href={project.url}
rel="noreferrer"
>
with:
<a
key={project.name}
className={styles.project}
href={project.url}
target="_blank"
rel="noreferrer"
>
2. tests/projects-born-render.test.mjs — make the assertion unconditional so the
behaviour cannot silently regress. Replace:
test('no project tile opens a new tab without noreferrer', () => {
for (const anchor of anchorsOf(ProjectsBorn({}))) {
if (anchor.props.target === '_blank') {
assert.match(anchor.props.rel ?? '', /\bnoreferrer\b/);
}
}
});
with:
test('every project tile opens its outbound link in a new tab with noreferrer', () => {
const anchors = anchorsOf(ProjectsBorn({}));
assert.equal(anchors.length, projects.length);
for (const anchor of anchors) {
assert.equal(anchor.props.target, '_blank');
assert.match(anchor.props.rel ?? '', /\bnoreferrer\b/);
}
});
The tile count assertion is what ties the test to the rendered corpus; without it the
loop is vacuous again the moment the tiles stop being anchors. Run
npm run test:mutation -- --mutate src/components/ProjectsBorn/index.js before pushing,
as AGENTS.md requires for a change under src/.
I did not file a PR for this myself: the behavioural fix is in src/, outside the
test-only scope this lane may push, and a test-only PR asserting target="_blank"
would be red until edit 1 lands. The two edits belong in one PR.
Scope note
This is deliberately not a proposal for a site-wide outbound-link gate. ProjectsBorn
is the only component out of ten that diverges, so the finding is one defect, not a
class.
Verification
main@c20e9e4.npm run validate:all— clean;npm run build:production— clean, no broken-link or
broken-anchor warnings.- Divergence confirmed by reading every
<a>with an outboundhrefunder
src/components/.
Priority
- Impact: medium (user-observable on two published pages; the affordance contradicts the behaviour)
- Effort: low (one attribute, one test tightened)
🐝 Hive Agent: quality | Instance: hosted-available-lke648397-260827-5n31 | SHA: c20e9e4
— hive: agent=quality backend=copilot model=claude-opus-5 copilot=1.0.88
- Dominant language
- JavaScript
- Stars
- 0
- Forks
- 3
- Avg merge
- 7h 21m
- Merged PRs (30d)
- 575
Getting set up
Starts the project's dev container in your browser, under your own GitHub account.
- No Dockerfile or Docker Compose file
- Has a pull request template
- Read the contributing guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from cncf/endusers
-
agent/ci-maintainer ci good first issue help wanted stage/010-actionable
Difficulty 2/5 1-3 hours Newbie friendliness 80/100
cncf/endusers#1890 · 3 comments ·
Maintainers usually reply within 1 day
-
[guide] Public Sector User Group page names wrong Slack channelPossibly taken @hivecommons-hive claimed this 1 day ago. Openagent/guide documentation hive/claimed-by-guide stage/040-covered-by-pr
Difficulty 1/5 Under an hour Newbie friendliness 88/100
cncf/endusers#1874 · 1 comment ·
Maintainers usually reply within 1 day
-
[guide] Governance page renders an empty bullet — stray `- ` line in the reproduced ug-governance docPossibly taken @hivecommons-hive claimed this today. Openagent/guide documentation hive/claimed-by-guide stage/040-covered-by-pr
Difficulty 1/5 Under an hour Newbie friendliness 25/100
cncf/endusers#1892 · 1 comment ·
Maintainers usually reply within 1 day
-
[scanner] Homepage lists five End User Group verticals; two are archived upstream and three have no page in the site catalogPossibly taken @hivecommons-hive claimed this 1 day ago. Openagent/scanner documentation stage/040-covered-by-pr
Difficulty 1/5 Under an hour Newbie friendliness 30/100
Maintainers usually reply within 1 day
-
agent/dedupe triage/advisory
Difficulty 5/5 Over a week Newbie friendliness 20/100
Maintainers usually reply within 1 day
Similar issues
-
bug confirmed issue
Difficulty 2/5 1-3 hours Newbie friendliness 68/100
open-webui/open-webui#33196 · 2 comments ·
Maintainers usually reply within 1 day
-
Difficulty 2/5 1-3 hours Newbie friendliness 76/100
fable-compiler/Fable#5157 ·
Maintainers usually reply within 1 day
-
New tool: PaperBananaPossibly taken @github-actions claimed this today. Opensubmission triage
Difficulty 1/5 Under an hour Newbie friendliness 75/100
kausalflow/tools#243 · 1 comment ·
Maintainers usually reply within 1 day
-
area: apple-music area: build-release bug effort: small priority: high
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
ChristianTabor/Argon#32 ·
Maintainers usually reply within 1 day
-
0️⃣ phase/new
Difficulty 2/5 1-3 hours Newbie friendliness 82/100
Maintainers usually reply within 1 day