Files
zeekay 0f5536b707 hanzo.ai reads in the reader's language, and Arabic right to left
Every word the landing, the header menus, the phone sheet, the footer,
the help corner and the way in (sign-in, sign-up, the reset, the
invitation, the callback, onboarding, the 404) write goes through
@hanzo/i18n's t(): one sentence is one message, its figures, names and
links arguments; copy written as data is phrase('…') where written and
t(label) where drawn. Plans, the chart, the API states and the lines
lib/kai and lib/enso build for the landing do the same.

Right to left: <html lang dir> is set before the first paint (the
package's bootScript first in the head); a figure, clock, range or
price outside a sentence is isolated; sides are logical gui props, the
layout's [data-join] corners logical, a bar grows from the start;
arrows along the line mirror (Onward for "words ->" links). The repo
dates read in the reader's language.

Gates select by slot, never by an English label (rtl, help). New gate
translated.spec.ts: in Arabic the landing at 1440 and 390, every menu
open, the phone sheet, sign-in, sign-up and the app signed in hold no
authored English, nothing scrolls sideways, every range is isolated;
French the same for English's commonest words. What another package
draws with no words prop is listed by name (@hanzo/ui's Footer label,
@hanzo/build's composer row and suggestions).

@hanzo/i18n 0.5.3 (the catalogs: 1,443 keys, every offered locale
complete), @hanzo/app 0.2.1 (the app's words). The link gate imports
the terms versions from @hanzo/app, the one SDK main now resolves.
2026-10-22 19:54:31 -07:00

204 lines
9.2 KiB
TypeScript

'use client'
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'
import { Check, RotateCcw } from 'lucide-react'
import { useI18n } from '@hanzo/i18n/react'
import { Button, Text, View, XStack, YStack, type GuiElement } from '@hanzo/ui'
import { More } from '@hanzo/ui/marketing'
import { Mark } from '@/components/models/Mark'
import snapshot from '@/lib/data/benchmarks-coding.json' with { type: 'json' }
import { useCoding } from '@/hooks/useCoding'
import { derive, range, times, LEVEL } from '@/lib/coding'
import type { Run } from '@/lib/kai'
import { race, type Lane } from '@/lib/home'
import { Reading, Section, Win, node, useSeen, useStill } from './chrome'
import { Onward } from './label'
/**
* Hanzo Dev beside Claude Code on the same held-out repairs, from the research
* API's coding.repair runs (lib/coding): the build's snapshot first, then the
* API's answer once it arrives (hooks/useCoding).
*
* A REPLAY OF THE RECORDED TIMINGS, NOT A TRANSCRIPT. The runs store each
* side's median seconds a task, its model calls a task and its solved count;
* no per-task transcript is filed. So each window runs a clock and a bar for
* exactly the median the run records, at the speed it ran, and names the run
* it replays. Under reduced motion both stand at their last frame. Under the
* clocks, the paired speed over the tasks both solved and each path Hanzo Dev
* settled a task by that is a resolved gain, with its own speed and interval,
* as /benchmarks prints them; both solved counts only while Hanzo Dev solved
* more (lib/home `race`).
*
* In the reader's language, every sentence is one message with its figures as
* arguments; a clock, a speed and an interval drawn on their own keep their own
* order on a right-to-left page (`data-i18n-ltr`), and each bar grows from the
* start of the line.
*/
const INITIAL = derive(snapshot.data as unknown as Run[])
/** Seconds as the clock prints them. */
const secs = (v: number) => `${v.toFixed(2)} s`
function Track({ lane, solved, take, play, still }: { lane: Lane; solved: boolean; take: number; play: boolean; still: boolean }) {
const { t, dir } = useI18n()
const bar = useRef<GuiElement>(null)
const clock = useRef<GuiElement>(null)
const [ended, setDone] = useState(false)
const done = still || ended
const run = useRef<Animation | null>(null)
const isClaude = lane.name.toLowerCase().includes('claude')
const icon = isClaude ? <Mark org="claude" size={15} ink /> : <Mark org="hanzo" size={15} ink />
// The bar is a paused animation from the first frame, so it waits at zero without a style of its own.
useLayoutEffect(() => {
const el = node(bar)
if (!el || typeof el.animate !== 'function') return
const a = el.animate([{ transform: 'scaleX(0)' }, { transform: 'scaleX(1)' }], { duration: lane.seconds * 1000, easing: 'linear', fill: 'both' })
run.current = a
if (still) a.finish()
else a.pause()
return () => a.cancel()
}, [lane.seconds, still])
useEffect(() => {
const a = run.current
const face = node(clock)
if (!a || !face) return
if (still) {
face.textContent = secs(lane.seconds)
return
}
a.currentTime = 0
face.textContent = secs(0)
if (!play) return
a.play()
const reset = requestAnimationFrame(() => setDone(false))
let raf = 0
const tick = () => {
const t = Math.min(lane.seconds, Number(a.currentTime ?? 0) / 1000)
face.textContent = secs(t)
if (t < lane.seconds) raf = requestAnimationFrame(tick)
}
raf = requestAnimationFrame(tick)
a.onfinish = () => {
face.textContent = secs(lane.seconds)
setDone(true)
}
return () => {
cancelAnimationFrame(reset)
cancelAnimationFrame(raf)
a.onfinish = null
}
}, [take, play, still, lane.seconds])
return (
<Win title={lane.name} icon={icon} aside={done ? t('done') : play ? t('working') : t('ready')} slot={`lane-${lane.name}`}>
<YStack p={14} gap={12} $md={{ p: 20, gap: 14 }}>
<Text fontFamily="$mono" fontSize="$1" lineHeight="$1" color="var(--muted-foreground)">
{t('median time a task')}
</Text>
<XStack items="center" justify="space-between" gap={12}>
<Text ref={clock} fontFamily="$mono" fontSize="1.75rem" lineHeight="2rem" $md={{ fontSize: '2.5rem', lineHeight: '2.75rem' }} fontWeight="600" color="var(--foreground)" $platform-web={{ fontVariantNumeric: 'tabular-nums' }} data-clock data-i18n-ltr>
{secs(lane.seconds)}
</Text>
<View
aria-hidden
opacity={done ? 1 : 0}
width={28}
height={28}
rounded={9999}
bg="var(--secondary)"
items="center"
justify="center"
$platform-web={{ borderRadius: '9999px', transition: 'opacity 0.2s ease-in-out' }}
>
<Check size={16} strokeWidth={2.25} color="var(--foreground)" />
</View>
</XStack>
<View height={10} rounded={9999} borderWidth={1} borderColor="var(--border-strong)" overflow="hidden" bg="var(--muted)" $platform-web={{ borderRadius: '9999px' }}>
<View ref={bar} height="100%" rounded={9999} bg="var(--foreground)" $platform-web={{ borderRadius: '9999px', transformOrigin: dir === 'rtl' ? 'right center' : 'left center' }} />
</View>
<YStack gap={2} pt={4}>
{lane.calls != null ? (
<Reading label={t('model calls a task')}>
<bdi dir="ltr">{lane.calls.toFixed(1)}</bdi>
</Reading>
) : null}
{solved ? <Reading label={t('solved')}>{t('{solved} of {of}', { solved: lane.solved, of: lane.of })}</Reading> : null}
</YStack>
<Text fontFamily="$mono" fontSize="$1" lineHeight="$1" color="var(--muted-foreground)" $platform-web={{ overflowWrap: 'anywhere' }}>
{t('replay of run {run}', { run: lane.run })}
</Text>
</YStack>
</Win>
)
}
export default function Race() {
const { t } = useI18n()
const c = useCoding(INITIAL)
const r = useMemo(() => race(c), [c])
const [ref, seen] = useSeen(0.35)
const still = useStill()
const [take, setTake] = useState(0)
if (!r) return null
return (
<Section
id="race"
label="Coding.Tasks"
title={t('{speed} faster on the same coding tasks.', { speed: times(r.speed) })}
lede={t("Hanzo Dev and Claude Code were handed the same {n} coding tasks (single-fault repairs), one try each. Each clock replays that side's median time a task, at the speed it ran.", { n: r.base.of })}
>
<YStack gap={16}>
<View ref={ref} display="grid" gridTemplateColumns="minmax(0, 1fr) minmax(0, 1fr)" gap={8} $md={{ gap: 16 }} data-slot="race">
<Track lane={r.base} solved={r.solved} take={take} play={seen} still={still} />
<Track lane={r.ours} solved={r.solved} take={take} play={seen} still={still} />
</View>
<XStack justify="center" pt={4}>
<Button
size="sm"
variant="outline"
rounded={9999}
$platform-web={{ borderRadius: '9999px', display: 'inline-flex', alignItems: 'center', gap: '6px' }}
disabled={!seen && !still}
onPress={() => setTake((t) => t + 1)}
>
<RotateCcw size={13} aria-hidden />
{t('Replay race')}
</Button>
</XStack>
</YStack>
<XStack flexWrap="wrap" items="center" justify="space-between" gap={24}>
<YStack gap={6} maxW={720} flex={1} minW={260}>
<Text fontSize="$5" lineHeight="$5" color="var(--foreground)" data-figure="speed">
{t('{speed} faster over the {n} tasks both solved, {level} interval {range}.', { speed: times(r.speed), n: r.speed.n ?? '', level: LEVEL, range: range(r.speed) })}
</Text>
{r.paths.length ? (
<Text fontSize="$3" lineHeight="$3" color="var(--muted-foreground)">
{r.solved ? `${t('Claude Code solved {base} of {of}; Hanzo Dev {ours} of {all}.', { base: r.base.solved, of: r.base.of, ours: r.ours.solved, all: r.ours.of })} ` : ''}
{t('By how Hanzo Dev settled a task:')}
</Text>
) : null}
<YStack render="ul" $platform-web={{ listStyle: 'none', margin: 0, padding: 0 }} data-figure="paths">
{r.paths.map((p) => (
<XStack key={p.label} render="li" justify="space-between" flexWrap="wrap" columnGap={16} py={4} borderTopWidth={1} borderColor="var(--border)" data-path={p.label}>
<Text fontSize="$2" lineHeight="$2" color="var(--foreground)">
{t(p.n === 1 ? '{label}, {n} task' : '{label}, {n} tasks', { label: t(p.label), n: p.n ?? '' })}
</Text>
<Text fontFamily="$mono" fontSize="$2" lineHeight="$2" color="var(--muted-foreground)" $platform-web={{ fontVariantNumeric: 'tabular-nums' }} data-i18n-ltr>
{times(p)} · {LEVEL} {range(p)}
</Text>
</XStack>
))}
</YStack>
</YStack>
<XStack gap={16} items="center" flexWrap="wrap">
<More href="/benchmarks#coding-agent">
<Onward>{t('Every coding task and both runs')}</Onward>
</More>
</XStack>
</XStack>
</Section>
)
}