mirror of
https://github.com/hanzo-apps/hanzo.ai.git
synced 2026-10-29 07:20:43 +00:00
The share card offers Hanzo, the products and the betas the member asked for, the rest under More by group. A topic picks the posts Shuffle offers, the link (/beta/for/<topic>, tagged utm_content) and the card it unfurls to: that page carries the topic's title, line and card, a canonical naming itself and the one noindex (lib/publish SHARES), and sends a person on to /beta with the query kept, so ref and the campaign land as before. lib/hanzo/topics is the list, its words the site's and BETAS's own. generate-og draws a card per topic from it, palette-quantized with sharp: 49 cards, 2.9 MB. The cards gate reads every card's rendered words. Claude-Session: https://claude.ai/code/session_017vrVus6PPBgP5YRzKV1iqW
106 lines
4.4 KiB
TypeScript
106 lines
4.4 KiB
TypeScript
import { test, expect } from '@playwright/test'
|
|
import { createHash } from 'node:crypto'
|
|
import { readFileSync } from 'node:fs'
|
|
import { getOgImageUrl, OG_IMAGE, OG_IMAGES_BY_PATH } from '../../lib/constants/og'
|
|
import { BACKING, scan } from '../gates/backing'
|
|
import { CARDS, MANIFEST, file, renderHtml } from '../../scripts/generate-og.mjs'
|
|
import { TOPICS, page } from '../../lib/hanzo/topics'
|
|
|
|
/**
|
|
* A SHARE CARD SAYS ONLY WHAT CAN BE CHECKED.
|
|
*
|
|
* Every card is drawn by scripts/generate-og.mjs, its words and its visuals
|
|
* alike, so this reads the whole generator. A card states what a page, the
|
|
* plan catalog (GET /v1/billing/plans), the model catalog (GET /v1/models) or
|
|
* /trust already states; it never names what backs Enso, Zen or Kai. A redrawn
|
|
* card takes a new address, because a share preview is cached by its address.
|
|
* Each image is held to the words it was drawn from by the hashes the
|
|
* generator records in scripts/og.json when it draws.
|
|
*/
|
|
|
|
const source = readFileSync(new URL('../../scripts/generate-og.mjs', import.meta.url), 'utf8')
|
|
|
|
/** Claims no page or catalog backs, and the made-up model names and scores the cards once drew. */
|
|
const UNBACKED = [
|
|
/zero retention/i,
|
|
/enterprise[- ]grade/i,
|
|
/SOC ?2/i,
|
|
/HIPAA/i,
|
|
/ISO ?27001/i,
|
|
/99\.9/,
|
|
/AES-256/i,
|
|
/in seconds/i,
|
|
/passkey/i,
|
|
/latency/i,
|
|
/\b\d+ ?ms\b/i,
|
|
/hidden fees/i,
|
|
/unlimited scale/i,
|
|
/peer[- ]reviewed/i,
|
|
/reproducible science/i,
|
|
/state[- ]of[- ]the[- ]art/i,
|
|
/most popular/i,
|
|
/\b\d+\+ (?:open |frontier )?models/i,
|
|
/zen[- ]?5[- ]ultra/i,
|
|
/enso[- ]?3/i,
|
|
/\b\d+\.\d+%/,
|
|
/claude|anthropic|deepseek|gemini|llama|mistral|openrouter/i,
|
|
/silicon/i,
|
|
/competitive equity/i,
|
|
/frontier hardware/i,
|
|
/decentralized/i,
|
|
/foundation for applied ai/i,
|
|
]
|
|
|
|
/** The words each card is drawn from: a share topic's come from lib/hanzo/topics and BETAS, not from the generator's text. */
|
|
const words = CARDS.map((card) => ({ name: card.name, html: renderHtml(card) }))
|
|
|
|
test('no card claims what nothing backs', () => {
|
|
for (const claim of UNBACKED) expect(source, String(claim)).not.toMatch(claim)
|
|
for (const { name, html } of words) for (const claim of UNBACKED) expect(html, `${name}: ${claim}`).not.toMatch(claim)
|
|
})
|
|
|
|
test('no card says what backs Enso, Zen or Kai, or names a lab that could', () => {
|
|
expect(scan(source, '.js')).toEqual([])
|
|
expect(source.match(BACKING)?.[0]).toBeUndefined()
|
|
for (const { name, html } of words) {
|
|
expect(scan(html, '.html'), name).toEqual([])
|
|
expect(html.match(BACKING)?.[0], name).toBeUndefined()
|
|
}
|
|
})
|
|
|
|
const sha = (bytes: string | Buffer) => createHash('sha256').update(bytes).digest('hex')
|
|
|
|
test('every image is the one its words drew', () => {
|
|
const drawn = JSON.parse(readFileSync(MANIFEST, 'utf8')) as Record<string, { html: string; png: string }>
|
|
expect(Object.keys(drawn).sort()).toEqual(CARDS.map((c) => c.name).sort())
|
|
for (const card of CARDS) {
|
|
const redraw = `node scripts/generate-og.mjs ${card.name}`
|
|
expect(sha(renderHtml(card)), `${card.name}'s words changed after it was drawn: ${redraw}`).toBe(drawn[card.name].html)
|
|
expect(sha(readFileSync(new URL(`../../${file(card.name)}`, import.meta.url))), `${file(card.name)} is not the image drawn: ${redraw}`).toBe(drawn[card.name].png)
|
|
}
|
|
})
|
|
|
|
test('every card takes the same new address', () => {
|
|
const version = OG_IMAGE.url.match(/\?v=(\d+)$/)?.[1]
|
|
expect(Number(version)).toBeGreaterThanOrEqual(3)
|
|
for (const [path, url] of Object.entries(OG_IMAGES_BY_PATH)) expect(url, path).toMatch(new RegExp(`^/og/[a-z0-9-]+\\.png\\?v=${version}$`))
|
|
// /contact has no card of its own, so it shares the default one.
|
|
expect(getOgImageUrl('/contact')).toBe(OG_IMAGE.url)
|
|
})
|
|
|
|
test('every card the pages name is one the generator draws', () => {
|
|
const drawn = new Set(CARDS.map((c) => `/${file(c.name).replace(/^public\//, '')}`))
|
|
for (const url of [OG_IMAGE.url, ...Object.values(OG_IMAGES_BY_PATH)]) expect(drawn.has(url.replace(/\?.*$/, '')), `${url} is drawn by scripts/generate-og.mjs`).toBe(true)
|
|
})
|
|
|
|
test('every share topic unfurls to a card of its own', () => {
|
|
const seen = new Map<string, string>()
|
|
for (const t of TOPICS) {
|
|
const url = getOgImageUrl(page(t.id))
|
|
expect(url, `${t.id} has no card of its own`).not.toBe(OG_IMAGE.url)
|
|
expect(seen.get(url), `${t.id} and ${seen.get(url)} share ${url}`).toBeUndefined()
|
|
seen.set(url, t.id)
|
|
}
|
|
expect(seen.size).toBe(TOPICS.length)
|
|
})
|