Files
hanzo.ai/components/legal/document.tsx
zeekay 6c65e3e941 @hanzo/app 0.2.0: the app SDK, renamed from @hanzo/rooms
Every import, the catalog pin and the docs name @hanzo/app and
github.com/hanzoai/app. lib/rooms.ts is lib/app.ts and
components/rooms.tsx is components/app.tsx.
2026-10-22 19:07:20 -07:00

102 lines
4.2 KiB
TypeScript

import { notFound } from 'next/navigation'
import { Page, PageHero, Prose, Section } from '@/components/marketing/page-kit'
import { legalDoc } from '@/lib/legal'
import { pageMeta } from '@/lib/page-meta'
import { AUP_VERSION, TERMS_VERSION, versionDate } from '@hanzo/app/lib/terms'
/** The two documents a person agrees to at sign-up take their version from `@hanzo/app/lib/terms`, not their own front matter. */
const AGREED: Record<string, string> = { terms: TERMS_VERSION, 'acceptable-use': AUP_VERSION }
/**
* A legal document, rendered — the ONE place any of them is drawn.
*
* `content/legal/*.md` is the one authoritative version of each document and
* this is the one thing that renders it. Four routes reach it: `/legal/<slug>`
* for all ten, and `/privacy`, `/terms` and `/cookies` for the three whose
* short addresses were minted before the markdown stack existed.
*
* Those three used to be HAND-WRITTEN TSX, and they were not stale renderings
* of the same text — they were DIFFERENT DOCUMENTS. The TSX privacy policy ran
* thirteen generic sections against the markdown's nineteen, and the two
* disagreed about the minimum age and about whether research participation is
* on by default. A site cannot serve two answers to "what are your terms", so
* the bodies are gone and both addresses now read one file.
*
* The status line is not decoration. Every one of these is a DRAFT pending
* counsel, and the page says so above the text, from the document's own front
* matter. A reviewer relying on unexecuted terms because a page implied
* otherwise is the exact failure /trust exists to avoid.
*/
export function LegalDocument({ slug }: { slug: string }) {
const doc = legalDoc(slug)
if (!doc) notFound()
const effective = doc.status.toLowerCase() === 'effective'
const version = AGREED[slug] ?? doc.version
const date = AGREED[slug] ? versionDate(version) : doc.updated
return (
<Page>
<PageHero eyebrow="Legal" title={doc.title} lede={doc.summary} />
<Section>
<Prose>
{effective ? null : (
<p>
<strong>Draft.</strong> This document is not yet in force.
</p>
)}
<p>
Effective {date} · Version {version} ·{' '}
{doc.previous ? <a href={doc.previous}>Previous version</a> : 'Previous version: none, this is the first.'}
</p>
{doc.toc.length > 1 ? (
<nav aria-label="Contents">
<h2>Contents</h2>
<ul>
{doc.toc.map((row) => (
<li key={row.id}>
<a href={`#${row.id}`}>{row.text}</a>
</li>
))}
</ul>
</nav>
) : null}
</Prose>
</Section>
<Section>
<Prose html={doc.html} />
</Section>
</Page>
)
}
/**
* The metadata for a document served at its own short address, `/<slug>`.
*
* `/legal/<slug>` is the address, and this is what says so: the canonical
* points there, so search consolidates two URLs onto one document rather than
* ranking them against each other. `pageMeta` already carries that option and
* already states the reason — "left to point at itself it is a second, empty
* URL competing with the page that holds the words".
*
* A redirect stub was the alternative and it is not available here. This is a
* static export, so Next's `redirects()` emits nothing; the repo's substitute
* is a shell listed in `lib/publish`'s `EMPTY`, which makes the route
* `noindex` — and `shown()` in `components/home/shell.tsx` links a page in the
* chrome only while `policy()` calls it public. `@hanzogui/shell`'s footer
* registry points at `https://hanzo.ai/privacy` and `https://hanzo.ai/terms`,
* so a stub would delete both links from the footer of every page on the site,
* in a package this repo cannot edit.
*
* Title and description come from the document's own front matter, so nothing
* about it is typed twice.
*/
export function legalMeta(slug: string, path = `/${slug}`) {
const doc = legalDoc(slug)
if (!doc) throw new Error(`no legal document '${slug}' in content/legal`)
return pageMeta({
title: doc.title,
description: doc.summary,
path,
canonical: `/legal/${slug}`,
})
}