Give the tech radar its own page

/radar (/en/radar, /ca/radar) shows every technology the collective has
placed, by ring: logo, category, what it is, why it sits there, its
website and how many projects use it. The landing's radar summary links
to it.

It is the first listing — a page this site composes rather than one
record's — so resolveRoute learns about those (LISTINGS), and builds
their addresses and hreflang alternates from the same table.

Logos sit on a light tile in both themes. A brand's mark is not ours to
recolour, and Rust's, which is black, would vanish in dark mode.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
pigui
2026-09-29 15:07:19 +02:00
parent b5681a12ac
commit 06eeefc144
11 changed files with 558 additions and 13 deletions

View File

@@ -50,11 +50,20 @@ test.describe('landing', () => {
expect(response?.status()).toBe(404);
await expect(page.locator('h1')).toContainText('no existe');
await expect(page.locator('meta[name="robots"]')).toHaveAttribute('content', 'noindex');
await expect(page.locator('meta[name="robots"]')).toHaveAttribute(
'content',
'noindex',
);
});
test('answers 404 for a record the API does not have, in any section', async ({ page }) => {
for (const path of ['/proyectos/no-existe', '/en/team/nobody', '/ca/projectes/res']) {
test('answers 404 for a record the API does not have, in any section', async ({
page,
}) => {
for (const path of [
'/proyectos/no-existe',
'/en/team/nobody',
'/ca/projectes/res',
]) {
const response = await page.goto(path);
expect(response?.status(), path).toBe(404);
@@ -62,7 +71,9 @@ test.describe('landing', () => {
}
});
test('answers 404 for a section spelled in another language', async ({ page }) => {
test('answers 404 for a section spelled in another language', async ({
page,
}) => {
// `/projects/x` is the English section without the English prefix: not an
// address the API ever hands out.
const response = await page.goto('/projects/anything');
@@ -70,6 +81,23 @@ test.describe('landing', () => {
expect(response?.status()).toBe(404);
});
test('serves the radar in every language, and not under /es', async ({
page,
}) => {
for (const [path, heading] of [
['/radar', 'Radar técnico'],
['/en/radar', 'Tech radar'],
['/ca/radar', 'Radar tècnic'],
]) {
const response = await page.goto(path);
expect(response?.status(), path).toBe(200);
await expect(page.locator('h1')).toContainText(heading);
}
expect((await page.goto('/es/radar'))?.status()).toBe(404);
});
test('refuses an incomplete contact submission without calling the API', async ({
page,
}) => {