openvibers/openvibe.work Commits
Sign-in links are nofollow (Meta's crawler followed each listing's sign-in link: 7,954 sign-in redirects today) (#55)
Changes
@@@ -254,7 +254,7 @@ ${signedIn(req)? (tracked? html`<p class="job-track"><span class="badge ok">Tracked: ${STATUS_LABEL[tracked.status] || tracked.status}</span> <a href="/applications/${tracked.id}">Open your application</a></p>`: html`<form class="inline" method="post" action="/jobs/${row.id}/track"><button class="sc-btn" type="submit">Track this job</button></form>`)-- : html`<p class="muted small"><a href="/auth/login?next=%2Fjobs%2F${row.id}">Sign in</a> to track this job and ask your agent for help applying.</p>`}++ : html`<p class="muted small"><a rel="nofollow" href="/auth/login?next=%2Fjobs%2F${row.id}">Sign in</a> to track this job and ask your agent for help applying.</p>`}<dl class="job-facts"><dt>Source</dt><dd>${sourceLine(row)} — <a href="/sources#${src.id}">its terms as applied</a></dd>${l.posted_at ? html`<dt>Posted</dt><dd>${time(l.posted_at)}</dd>` : ''}@@@ -295,7 +295,7 @@ ${l.posted_at ? html`<dt>Posted</dt><dd>${time(l.posted_at)}</dd>` : ''}title: 'Saved searches',body: html`<h1>Saved searches</h1><p>Sign in to keep a search and see what has appeared since you last looked. Searching itself needs no account.</p>--<p><a class="sc-btn sc-primary" href="/auth/login?next=%2Fsaved">Sign in with OpenVibe</a> <a class="sc-btn" href="/jobs">Search the listings</a></p>`,++<p><a class="sc-btn sc-primary" rel="nofollow" href="/auth/login?next=%2Fsaved">Sign in with OpenVibe</a> <a class="sc-btn" href="/jobs">Search the listings</a></p>`,});}const subject = subjectOf(req);@@@ -403,7 +403,7 @@ ${events.length ? html`<ul class="app-history">${events.map((e) => html`<li>${tititle: 'Applications', styles: [showcase.STYLESHEET],body: html`<h1>Applications</h1><p>Track the jobs you apply to, keep your notes and next steps in one place, and ask your OpenVibe.Actor agent to help you prepare each application. Sign in to begin.</p>--<p><a class="sc-btn sc-primary" href="/auth/login?next=%2Fapplications">Sign in with OpenVibe</a> <a class="sc-btn" href="/jobs">Search the listings</a></p>`,++<p><a class="sc-btn sc-primary" rel="nofollow" href="/auth/login?next=%2Fapplications">Sign in with OpenVibe</a> <a class="sc-btn" href="/jobs">Search the listings</a></p>`,});}const subject = subjectOf(req);@@@ -447,7 +447,7 @@ ${addForm()}`,if (!signedIn(req)) {return page(req, res, {title: 'Application', styles: [showcase.STYLESHEET],-- body: html`<h1>Application</h1><p>Sign in to see your applications.</p><p><a class="sc-btn sc-primary" href="/auth/login?next=%2Fapplications">Sign in with OpenVibe</a></p>`,++ body: html`<h1>Application</h1><p>Sign in to see your applications.</p><p><a class="sc-btn sc-primary" rel="nofollow" href="/auth/login?next=%2Fapplications">Sign in with OpenVibe</a></p>`,});}const subject = subjectOf(req);@@@ -223,10 +223,13 @@ function appToken(t, cap = []) {const anon = await t.get('/applications');assert.strictEqual(anon.status, 200);assert.match(anon.text, /Sign in with OpenVibe/);++ assert.match(anon.text, /rel="nofollow" href="\/auth\/login/);const listing = await firstListing();const before = await t.get(`/jobs/${listing.id}`);assert.match(before.text, /Sign in/, 'a signed-out listing page offers a sign-in for tracking');++ // Crawlers followed each listing's sign-in link (7,954 sign-in redirects from Meta's crawler on 2026-10-20).++ assert.match(before.text, /<a rel="nofollow" href="\/auth\/login\?next=%2Fjobs%2F/, 'the sign-in link is nofollow');assert.doesNotMatch(before.text, /Track this job/, 'a signed-out listing page has no tracking form');const trackedPage = await t.get(`/jobs/${listing.id}`, { as: lee });
@@ -254,7 +254,7 @@ ${signedIn(req)
@@ -254,7 +254,7 @@ ${signedIn(req)
? (tracked
? (tracked
? html`<p class="job-track"><span class="badge ok">Tracked: ${STATUS_LABEL[tracked.status] || tracked.status}</span> <a href="/applications/${tracked.id}">Open your application</a></p>`
? html`<p class="job-track"><span class="badge ok">Tracked: ${STATUS_LABEL[tracked.status] || tracked.status}</span> <a href="/applications/${tracked.id}">Open your application</a></p>`
: html`<form class="inline" method="post" action="/jobs/${row.id}/track"><button class="sc-btn" type="submit">Track this job</button></form>`)
: html`<form class="inline" method="post" action="/jobs/${row.id}/track"><button class="sc-btn" type="submit">Track this job</button></form>`)
- : html`<p class="muted small"><a href="/auth/login?next=%2Fjobs%2F${row.id}">Sign in</a> to track this job and ask your agent for help applying.</p>`}
+ : html`<p class="muted small"><a rel="nofollow" href="/auth/login?next=%2Fjobs%2F${row.id}">Sign in</a> to track this job and ask your agent for help applying.</p>`}
<dl class="job-facts">
<dl class="job-facts">
<dt>Source</dt><dd>${sourceLine(row)} — <a href="/sources#${src.id}">its terms as applied</a></dd>
<dt>Source</dt><dd>${sourceLine(row)} — <a href="/sources#${src.id}">its terms as applied</a></dd>
${l.posted_at ? html`<dt>Posted</dt><dd>${time(l.posted_at)}</dd>` : ''}
${l.posted_at ? html`<dt>Posted</dt><dd>${time(l.posted_at)}</dd>` : ''}
@@ -295,7 +295,7 @@ ${l.posted_at ? html`<dt>Posted</dt><dd>${time(l.posted_at)}</dd>` : ''}
@@ -295,7 +295,7 @@ ${l.posted_at ? html`<dt>Posted</dt><dd>${time(l.posted_at)}</dd>` : ''}
title: 'Saved searches',
title: 'Saved searches',
body: html`<h1>Saved searches</h1>
body: html`<h1>Saved searches</h1>
<p>Sign in to keep a search and see what has appeared since you last looked. Searching itself needs no account.</p>
<p>Sign in to keep a search and see what has appeared since you last looked. Searching itself needs no account.</p>
-<p><a class="sc-btn sc-primary" href="/auth/login?next=%2Fsaved">Sign in with OpenVibe</a> <a class="sc-btn" href="/jobs">Search the listings</a></p>`,
+<p><a class="sc-btn sc-primary" rel="nofollow" href="/auth/login?next=%2Fsaved">Sign in with OpenVibe</a> <a class="sc-btn" href="/jobs">Search the listings</a></p>`,
});
});
}
}
const subject = subjectOf(req);
const subject = subjectOf(req);
@@ -403,7 +403,7 @@ ${events.length ? html`<ul class="app-history">${events.map((e) => html`<li>${ti
@@ -403,7 +403,7 @@ ${events.length ? html`<ul class="app-history">${events.map((e) => html`<li>${ti
title: 'Applications', styles: [showcase.STYLESHEET],
title: 'Applications', styles: [showcase.STYLESHEET],
body: html`<h1>Applications</h1>
body: html`<h1>Applications</h1>
<p>Track the jobs you apply to, keep your notes and next steps in one place, and ask your OpenVibe.Actor agent to help you prepare each application. Sign in to begin.</p>
<p>Track the jobs you apply to, keep your notes and next steps in one place, and ask your OpenVibe.Actor agent to help you prepare each application. Sign in to begin.</p>
-<p><a class="sc-btn sc-primary" href="/auth/login?next=%2Fapplications">Sign in with OpenVibe</a> <a class="sc-btn" href="/jobs">Search the listings</a></p>`,
+<p><a class="sc-btn sc-primary" rel="nofollow" href="/auth/login?next=%2Fapplications">Sign in with OpenVibe</a> <a class="sc-btn" href="/jobs">Search the listings</a></p>`,
});
});
}
}
const subject = subjectOf(req);
const subject = subjectOf(req);
@@ -447,7 +447,7 @@ ${addForm()}`,
@@ -447,7 +447,7 @@ ${addForm()}`,
if (!signedIn(req)) {
if (!signedIn(req)) {
return page(req, res, {
return page(req, res, {
title: 'Application', styles: [showcase.STYLESHEET],
title: 'Application', styles: [showcase.STYLESHEET],
- body: html`<h1>Application</h1><p>Sign in to see your applications.</p><p><a class="sc-btn sc-primary" href="/auth/login?next=%2Fapplications">Sign in with OpenVibe</a></p>`,
+ body: html`<h1>Application</h1><p>Sign in to see your applications.</p><p><a class="sc-btn sc-primary" rel="nofollow" href="/auth/login?next=%2Fapplications">Sign in with OpenVibe</a></p>`,
});
});
}
}
const subject = subjectOf(req);
const subject = subjectOf(req);
@@ -223,10 +223,13 @@ function appToken(t, cap = []) {
@@ -223,10 +223,13 @@ function appToken(t, cap = []) {
const anon = await t.get('/applications');
const anon = await t.get('/applications');
assert.strictEqual(anon.status, 200);
assert.strictEqual(anon.status, 200);
assert.match(anon.text, /Sign in with OpenVibe/);
assert.match(anon.text, /Sign in with OpenVibe/);
+ assert.match(anon.text, /rel="nofollow" href="\/auth\/login/);
const listing = await firstListing();
const listing = await firstListing();
const before = await t.get(`/jobs/${listing.id}`);
const before = await t.get(`/jobs/${listing.id}`);
assert.match(before.text, /Sign in/, 'a signed-out listing page offers a sign-in for tracking');
assert.match(before.text, /Sign in/, 'a signed-out listing page offers a sign-in for tracking');
+ // Crawlers followed each listing's sign-in link (7,954 sign-in redirects from Meta's crawler on 2026-10-20).
+ assert.match(before.text, /<a rel="nofollow" href="\/auth\/login\?next=%2Fjobs%2F/, 'the sign-in link is nofollow');
assert.doesNotMatch(before.text, /Track this job/, 'a signed-out listing page has no tracking form');
assert.doesNotMatch(before.text, /Track this job/, 'a signed-out listing page has no tracking form');
const trackedPage = await t.get(`/jobs/${listing.id}`, { as: lee });
const trackedPage = await t.get(`/jobs/${listing.id}`, { as: lee });