Files
zeekayandHanzo Dev f7785887de The 8.x rewrite compiled; this is what it was actually rendering
@hanzo/gui accepts a prop it does not recognise, and lets one prop silently
undo another. There is no error and no type failure, so the green `next build`
the last commit reported proved only that the imports resolved. Driving the
built export in a browser and reading computed styles off it — the only
evidence that tells "styled" apart from "the prop went nowhere" — found six
defects, every one of them visible on the page.

The card body was collapsed to zero. `flex={1}` on `CardContent` is React
Native's spelling and compiles to `flex: 1 1 0px` — a ZERO basis. The card is a
column whose height comes from its own content, so a zero-basis body
contributed nothing: the card sized itself from header plus footer alone, the
body measured 0px, every description vanished, and the tag row and byline
painted outside the box and under the buttons. All 202 cards. `grow={1}` leaves
the basis at `auto`, so the body is as tall as its text and then takes the
slack the grid's stretch hands the card, which is what pins every footer to the
same line. `height="100%"` on the Card went with it: `.cards` is a grid and a
grid stretches its items to the row already.

Every panel heading on the app page was body text. `size="$6"` on `CardTitle`
is a VARIANT, not a style prop: it expands to the whole typographic row for
that step — size, leading, tracking AND weight — and the weight column of the
Hanzo ladder is 400 at every step, so it compiled to
`font-weight: var(--f-weight-6)` and replaced the 600 CardTitle declares for
itself. Measured 400. The weight is restated after it; `size` stays, because it
is the only prop that pairs 17px with its 24px leading.

Every button that navigates was underlined and announced as a button. `asChild`
renders the control as the anchor it wraps — right markup — and picks up the
user-agent underline, and gui's Button frame declares `role="button"` and
stamps it on whatever it renders, so five `<a href>` told assistive tech they
were buttons. The underline is refused once through `hanzo-button`, the class
@hanzo/ui publishes for hosts hooking the control from CSS. The role is stated
once in `LinkButton`, which is now the store's one way to say "a button that
navigates" and picks its anchor from the destination: a store path routes on
the client, `hanzo://` hands off in place, GitHub opens a tab.

The mark disappeared on a phone. As a plain flex child it took its share of the
squeeze: 48px on a desktop, 17px at 390, and 0 at 320.

Every filter read "All Types ( 202 )". Written as JSX children, the source's own
newlines around the parentheses fold into text. The label is one string now —
and the type row and the category row, which were the same twenty lines twice,
are one `Facet` reading a field.

Every icon is a broken image. The urls in the data are presigned R2 links
inherited from the upstream Shinkai store, signed 2025-11-05 with a 24-hour
expiry, so all 202 have been dead since the day after. The grid had no fallback
and rendered the browser's glyph with the alt text spilling out of the box; the
app page had one, but only for a MISSING url, never a failing one. Both are the
same fact, so `AppIcon` states it once. `Screenshots` applies the same rule to
the gallery, and takes the panel with it when nothing survives.

The e2e suite tested the Tailwind DOM that no longer exists — `[class*="grid"]`,
`[class*="rounded-xl"]`, a title of "Hanzo Store", a hard-coded `#7c3aed` — and
nine of its twenty assertions were `expect(count).toBeGreaterThanOrEqual(0)`,
which cannot fail. It is replaced by one spec that reads computed styles and
measured boxes, and asserts these six defects specifically. Each was verified
to FAIL when its defect is put back: body height 0 against `> 60`, mark width 0
against 48, an extra `underline`, an h2 weight that is not 600.

typescript stays on 5.9.3 for the reason the previous commit gives; `tsgo` was
a leftover of the removed native-preview and the script runs `tsc`.

Verified: `next build` compiles and exports 209 static pages; `tsc --noEmit`
clean; `playwright test --project=chromium` 21 passed.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-04 17:55:29 -07:00

48 lines
1.6 KiB
TypeScript

'use client'
/**
* A button that navigates.
*
* Five controls in the store go somewhere: two into the store's own routes, two
* into the desktop app over `hanzo://`, one out to GitHub. Each was written as
* `<Button asChild>` wrapped around an anchor, which is the right markup — and
* @hanzo/gui's Button frame declares `role="button"`, which it stamps onto
* whatever it renders. So all five arrived in the page as
* `<a href="…" role="button">`: they navigate, and they told assistive tech they
* were buttons. An `<a href>` is a link. The role says so here, once, instead of
* at five call sites.
*
* Which anchor to render follows from the destination, so it is not a prop. A
* path inside the store is a `next/link` and routes on the client; anything else
* is a plain anchor. `newTab` is only for destinations that leave the web app —
* never for `hanzo://`, where a new tab would open, hand off, and stay behind
* empty.
*/
import type { ComponentProps, ReactNode } from 'react'
import Link from 'next/link'
import { Button } from '@hanzo/ui'
export function LinkButton({
href,
newTab = false,
children,
...props
}: Omit<ComponentProps<typeof Button>, 'asChild' | 'children'> & {
href: string
/** Open in a new tab — for destinations outside the store. */
newTab?: boolean
children?: ReactNode
}) {
return (
<Button asChild role="link" {...props}>
{href.startsWith('/') ? (
<Link href={href}>{children}</Link>
) : (
<a href={href} {...(newTab && { target: '_blank', rel: 'noopener noreferrer' })}>
{children}
</a>
)}
</Button>
)
}