Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@profullstack/agenticjobs",
"version": "0.22.5",
"version": "0.22.6",
"description": "An agent-friendly job board you self-host. It posts its own jobs, never scrapes anyone else's, and answers on every surface: web, API, MCP, CLI, TUI, desktop and PWA. Instances find each other through an open directory.",
"license": "MIT",
"type": "module",
Expand Down
2 changes: 1 addition & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ export function loadConfig(env: NodeJS.ProcessEnv = process.env): Config {
}

/** Kept in step with package.json by the release script. */
export const VERSION = '0.22.5';
export const VERSION = '0.22.6';
export const SOFTWARE_NAME = 'agenticjobs';

/**
Expand Down
42 changes: 19 additions & 23 deletions src/views/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { renderMarkdown } from '../markup/markdown.ts';
import type { SkillCount } from '../core/skills.ts';
import { pathForQuery } from '../core/landing.ts';
import { EMPTY_QUERY } from '../schema/query.ts';
import { pfsFooter } from './pfs-footer.ts';
import { pitFooter } from './pit-footer.ts';
import { AdUnit, showsAd } from './ad-unit.tsx';

export interface PageProps {
Expand Down Expand Up @@ -267,11 +267,12 @@ export const Layout: FC<PropsWithChildren<PageProps>> = (props) => {
</div>
</header>
<main id="main">
{/* The one ad, at the top of the content: never pressed against the footer. */}
{showsAd(path, noindex) && <AdUnit />}
<div class="container">{children}</div>
</main>
{showsAd(path, noindex) && <AdUnit />}
{skills.length > 0 && (
<footer class="site-footer">
<footer class="site-footer">
{skills.length > 0 && (
<div class="container footer-skills">
<span>Popular skills:</span>
{skills.map((skill) => (
Expand All @@ -281,25 +282,18 @@ export const Layout: FC<PropsWithChildren<PageProps>> = (props) => {
))}
<a href="/skills">all skills</a>
</div>
</footer>
)}
{raw(
pfsFooter({
site: `${publicUrl.replace(/\/+$/, '')}/`,
links: [
{ label: 'Popular', href: '/popular' },
{ label: 'Most profitable', href: '/most-profitable' },
{ label: 'Agents', href: '/agents' },
{ label: 'API', href: '/docs' },
{ label: 'OpenAPI', href: '/api/v1/openapi.json' },
{ label: 'Instance', href: '/.well-known/agenticjobs' },
{ label: 'Feed', href: '/jobs.json' },
{ label: 'llms.txt', href: '/llms.txt' },
{ label: 'Source', href: 'https://github.com/profullstack/agenticjobs' },
],
tagline: `${boardName} runs on agenticjobs, MIT licensed.`,
}),
)}
)}
<div class="container footer-links">
<span>
&copy; {new Date().getFullYear()} {boardName}
</span>
<a href="/popular">Popular</a>
<a href="/agents">Agents</a>
<a href="/docs">API</a>
<a href="/jobs.json">Feed</a>
<a href="https://github.com/profullstack/agenticjobs">Source</a>
</div>
</footer>
<script src="/assets/app.js" defer></script>
{/*
* CrawlProof, as a plain tag. `defer` is what `strategy` meant on the
Expand All @@ -312,6 +306,8 @@ export const Layout: FC<PropsWithChildren<PageProps>> = (props) => {
defer
></script>
)}
{/* The pit.world global footer, last in <body>. */}
{raw(pitFooter(publicUrl))}
</body>
</html>
);
Expand Down
23 changes: 0 additions & 23 deletions src/views/pfs-footer.ts

This file was deleted.

57 changes: 57 additions & 0 deletions src/views/pit-footer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* The pit.world global footer (site name, search, ring, publish), fetched on
* the server from https://pit.world/snippets/footer.html?site=<board host> and
* rendered as the last thing in <body>.
*
* Layout renders synchronously (pages and tests stringify it directly), so this
* returns the last good copy for the host and refreshes it in the background
* once it is an hour old (3s timeout). A failed refresh keeps the old copy; with
* no copy yet it returns '' and the page simply has no global footer.
*/
const MAX_AGE_MS = 60 * 60 * 1000;

interface Entry {
html: string;
fetchedAt: number;
inflight: Promise<void> | null;
}

const cache = new Map<string, Entry>();

function refresh(site: string, entry: Entry): Promise<void> {
entry.inflight ??= fetch(
`https://pit.world/snippets/footer.html?site=${encodeURIComponent(site)}`,
{ signal: AbortSignal.timeout(3000) },
)
.then(async (res) => {
const text = res.ok ? await res.text() : '';
if (!text.includes('data-pit-footer')) throw new Error(`pit footer: HTTP ${res.status}`);
entry.html = text;
entry.fetchedAt = Date.now();
})
.catch(() => {
entry.fetchedAt = Date.now() - MAX_AGE_MS + 60_000; // try again in a minute
})
.finally(() => {
entry.inflight = null;
});
return entry.inflight;
}

/** The footer fragment for a board's public URL ('' until the first fetch lands). */
export function pitFooter(publicUrl: string): string {
let site: string;
try {
site = new URL(publicUrl).hostname;
} catch {
return '';
}
if (!site || site === 'localhost' || site.endsWith('.test')) return '';
let entry = cache.get(site);
if (!entry) {
entry = { html: '', fetchedAt: 0, inflight: null };
cache.set(site, entry);
}
if (Date.now() - entry.fetchedAt > MAX_AGE_MS) void refresh(site, entry);
return entry.html;
}
18 changes: 7 additions & 11 deletions web/public/app.css
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ main {

.site-footer {
border-top: 1px solid var(--border);
padding: 2rem 0;
padding: 1.25rem 0;
color: var(--muted-foreground);
font-size: 0.85rem;
}
Expand All @@ -159,17 +159,13 @@ main {
color: var(--muted-foreground);
}

/* The shared Profullstack footer (src/views/pfs-footer.ts) under the skills row. */
footer.pfs-footer {
--pfs-footer-muted: var(--muted-foreground);
border-top-color: var(--border);
}

/* The skill links above the footer proper: one row that wraps. */
/* The shared footer's own top rule follows, so the row needs none of its own. */
/* Two rows: the popular skills, then the copyright and the links people use. */
.footer-skills {
gap: 0.5rem 0.9rem;
}
.footer-skills + .footer-links {
margin-top: 0.75rem;
}

.thread.unread {
font-weight: 600;
Expand Down Expand Up @@ -1100,9 +1096,9 @@ a.badge-plain:hover .badge {
.tracker-missing { color: var(--warning); font-size: 0.9rem; margin: 0.25rem 0; }
.tracker-improve ul { margin: 0; padding-left: 1.25rem; display: grid; gap: 0.5rem; }

/* CrawlProof ad unit (src/views/ad-unit.tsx): one fluid text_link frame above the footer. */
/* CrawlProof ad unit (src/views/ad-unit.tsx): one fluid text_link frame at the top of the content. */
.ad-unit {
margin: 1.5rem 0 0;
margin: 0 0 1.5rem;
}
.ad-unit iframe {
display: block;
Expand Down
2 changes: 1 addition & 1 deletion web/public/sw.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
* drifted, which is the only version of this rule that survives contact.
*/

const VERSION = '4a4fd178';
const VERSION = '4442cc3d';
const SHELL = `shell-${VERSION}`;
const ASSETS = ['/assets/app.css', '/assets/tokens.css', '/assets/icon.svg'];

Expand Down
Loading