ReviewOS

also looking at this

stacks/lastsoulultra

LSU's own coaching surface, and what auditing the site turned up

#12
Merged glennmichael123 wants to merge claude/last-soul-ultra-web-6e0718 into main
86 files +3943 -1190

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

resources/data/share.tsadded+92-0
Changes to resources/data/share.ts
@@ -0,0 +1,92 @@
1/**
2 * The social share cards, one per page.
3 *
4 * Nine 1200x630 cards ship under `public/assets/lsu/images/`, produced by the
5 * organiser in the brand's own black-and-red: the LSU wordmark top left, a red
6 * kicker, a white headline, and `lastsoulultra.com` bottom right over a
7 * darkened race photograph. They were captured with the rest of the production
8 * assets and then never referenced, so every page in the site shared one
9 * portrait WebP of the hero instead - the wrong shape for a card slot that is
10 * 1.91:1, and a format several scrapers still decline to render.
11 *
12 * Mapped here rather than in `production-pages.ts`, which records the captured
13 * page metadata as it was found.
14 */
15
16export interface ShareCard {
17 /** Site-relative path. Absolute URLs are built at render time. */
18 src: string
19 /**
20 * What the card shows AND what it reads. A share card is mostly type, so an
21 * alt that describes only the photograph behind it drops the actual message.
22 */
23 alt: string
24}
25
26/** Every card on disk is this size, which is also what the card slot wants. */
27export const CARD_WIDTH = 1200
28export const CARD_HEIGHT = 630
29
30const DIR = '/assets/lsu/images'
31
32export const cards = {
33 default: {
34 src: `${DIR}/og-default.jpg`,
35 alt: 'Silhouetted runners against floodlights, captioned "World Series · Season 3 · 2027" and "Three qualifiers. One final."',
36 },
37 format: {
38 src: `${DIR}/og-format.jpg`,
39 alt: 'A rain-beaded pair of rappid. sunglasses in close-up, captioned "What a backyard ultra is" and "One loop. Every hour."',
40 },
41 soulRaces: {
42 src: `${DIR}/og-soul-races.jpg`,
43 alt: 'Runners and crew waiting in a corral at the start, captioned "Independent backyard ultras" and "Soul Races around the world".',
44 },
45 sponsorship: {
46 src: `${DIR}/og-sponsorship.jpg`,
47 alt: 'A runner in a cap and race vest lit from the side in the dark, captioned "Partner with the World Series" and "A race built to be watched".',
48 },
49 contact: {
50 src: `${DIR}/og-contact.jpg`,
51 alt: 'A lone runner silhouetted in fog, captioned "We reply within 24 hours" and "Contact & FAQ".',
52 },
53 lottery: {
54 src: `${DIR}/og-lottery.jpg`,
55 alt: 'The lap board reading "souls remaining", captioned "Four events · One World Series" and "Enter the lottery".',
56 },
57 results: {
58 src: `${DIR}/og-results.jpg`,
59 alt: 'Runners with hands raised under floodlights, captioned "Every edition · Every yard" and "Results".',
60 },
61 season2025: {
62 src: `${DIR}/og-2025.jpg`,
63 alt: 'The Last Soul lettering on a dark wall, captioned "Season 1 · 3 October 2025" and "Last Soul 2025".',
64 },
65 season2026: {
66 src: `${DIR}/og-2026.jpg`,
67 alt: 'A runner in a headtorch looking straight at the camera at night, captioned "Season 2 · 14 August 2026" and "Last Soul 2026".',
68 },
69} satisfies Record<string, ShareCard>
70
71/**
72 * Path to card. A page with no card of its own gets the default one, which
73 * carries the World Series message rather than a page-specific headline.
74 */
75const byPath: Record<string, ShareCard> = {
76 '/': cards.default,
77 '/world-series': cards.default,
78 '/format': cards.format,
79 '/soul-races': cards.soulRaces,
80 '/sponsorship': cards.sponsorship,
81 '/contact': cards.contact,
82 '/faq': cards.contact,
83 '/lottery': cards.lottery,
84 '/results': cards.results,
85 '/results/last-soul-ultra-2025': cards.season2025,
86 '/results/last-soul-ultra-2026': cards.season2026,
87}
88
89/** The card for a page, falling back to the default for anything unlisted. */
90export function shareCard(path: string): ShareCard {
91 return byPath[path] ?? cards.default
92}
resources/partials/head.stxmodified+9-1
Changes to resources/partials/head.stx
@@ -1,7 +1,9 @@
11<script server>
2import { CARD_HEIGHT, CARD_WIDTH, shareCard } from '../data/share'
23import { absolute } from '../data/site'
34const metaPage = typeof currentPage !== 'undefined' && currentPage ? currentPage : { title: 'Last Soul Ultra', description: 'The backyard ultra World Series. There is no finish line.', styles: ['/assets/styles/production/base.css'] }
45const pagePath = typeof canonicalPath !== 'undefined' ? canonicalPath : '/'
6const card = shareCard(pagePath)
57</script>
68<meta charset="utf-8">
79<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -17,8 +19,14 @@ const pagePath = typeof canonicalPath !== 'undefined' ? canonicalPath : '/'
1719<meta property="og:title" content="">
1820<meta property="og:description" content="">
1921<meta property="og:url" content="">
20<meta property="og:image" content="">
22<meta property="og:image" content="">
23<meta property="og:image:type" content="image/jpeg">
24<meta property="og:image:width" content="1200">
25<meta property="og:image:height" content="630">
26<meta property="og:image:alt" content="">
2127<meta name="twitter:card" content="summary_large_image">
28<meta name="twitter:image" content="">
29<meta name="twitter:image:alt" content="">
2230<link rel="icon" href="/assets/lsu/images/favicon.png" type="image/png">
2331<link rel="apple-touch-icon" href="/assets/lsu/images/apple-touch-icon.png">
2432<link rel="preload" href="/assets/lsu/fonts/anton-400-latin.woff2" as="font" type="font/woff2" crossorigin>
tests/share.test.tsadded+97-0
Changes to tests/share.test.ts
@@ -0,0 +1,97 @@
1import { describe, expect, it } from 'bun:test'
2import { resolve } from 'node:path'
3import { processDirectives } from '@stacksjs/stx'
4import { CARD_HEIGHT, CARD_WIDTH, cards, shareCard } from '../resources/data/share'
5import { indexablePaths } from '../resources/data/site'
6
7/**
8 * Share cards are the one part of a page nobody on the team ever sees: they
9 * render in someone else's app, days later, and a wrong one looks deliberate.
10 * The site shipped nine cards it never referenced for exactly that reason, so
11 * these tests check the file on disk rather than the mapping alone.
12 */
13
14/** Width and height off the JPEG's own SOF marker, not off a filename. */
15function jpegSize(bytes: Uint8Array): { width: number, height: number } {
16 const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength)
17 let offset = 2 // skip SOI
18 while (offset < bytes.length) {
19 if (view.getUint8(offset) !== 0xFF)
20 throw new Error('not a JPEG segment boundary')
21 const marker = view.getUint8(offset + 1)
22 const length = view.getUint16(offset + 2)
23 // SOF0..SOF15, minus the four markers in that range that are not frames.
24 if (marker >= 0xC0 && marker <= 0xCF && ![0xC4, 0xC8, 0xCC].includes(marker))
25 return { height: view.getUint16(offset + 5), width: view.getUint16(offset + 7) }
26 offset += 2 + length
27 }
28 throw new Error('no frame header')
29}
30
31describe('share cards', () => {
32 it('serves every card from a file that is actually on disk', async () => {
33 for (const card of Object.values(cards)) {
34 const file = Bun.file(`public${card.src}`)
35 expect(await file.exists()).toBe(true)
36 }
37 })
38
39 it('ships each card at the size the card slot crops to', async () => {
40 for (const card of Object.values(cards)) {
41 const bytes = new Uint8Array(await Bun.file(`public${card.src}`).arrayBuffer())
42 expect(jpegSize(bytes)).toEqual({ width: CARD_WIDTH, height: CARD_HEIGHT })
43 }
44 })
45
46 it('gives every indexable page a card', () => {
47 for (const path of indexablePaths)
48 expect(shareCard(path).src.endsWith('.jpg')).toBe(true)
49 })
50
51 it('falls back to the default card rather than to nothing', () => {
52 expect(shareCard('/404')).toEqual(cards.default)
53 expect(shareCard('/a-page-that-does-not-exist')).toEqual(cards.default)
54 })
55
56 it('describes what each card reads, not just the photograph behind it', () => {
57 for (const card of Object.values(cards)) {
58 expect(card.alt.length).toBeGreaterThan(40)
59 expect(card.alt).toContain('captioned')
60 }
61 })
62})
63
64describe('the head partial', () => {
65 async function head(): Promise<string> {
66 return processDirectives('@include(\'head\')', {}, resolve('resources/views/index.stx'), {
67 enabled: true,
68 cache: false,
69 partialsDir: resolve('resources/partials'),
70 componentsDir: resolve('resources/components'),
71 }, new Set())
72 }
73
74 it('points both card slots at the same absolute JPEG', async () => {
75 const html = await head()
76 const og = html.match(/property="og:image" content="([^"]+)"/)?.[1]
77 const twitter = html.match(/name="twitter:image" content="([^"]+)"/)?.[1]
78
79 expect(og).toBe(twitter)
80 expect(og?.startsWith('http')).toBe(true)
81 expect(og?.endsWith('.jpg')).toBe(true)
82 })
83
84 it('declares the card size, so a scraper need not fetch it to lay it out', async () => {
85 const html = await head()
86 expect(html).toContain(`content="${CARD_WIDTH}"`)
87 expect(html).toContain(`content="${CARD_HEIGHT}"`)
88 })
89
90 it('escapes the quotes inside an alt rather than ending the attribute early', async () => {
91 const html = await head()
92 const alt = html.match(/property="og:image:alt" content="([^"]+)"/)?.[1] ?? ''
93
94 expect(alt).toContain('&quot;')
95 expect(alt).not.toContain('"')
96 })
97})