fix: polish copy, remove homepage icon from module cards, and tighten footer/link styles

- Drop `IconHome` anchor from `ModuleCard` header; only the `IconInfoCircle` details link remains (opacity toned down to `text-foreground/40`)
- Reword error messages (`describeError`), empty-state strings, route-boundary messages, and stats page copy for clarity and consistency
- Replace `rel="noreferrer"` with `rel="noopener noreferrer"` across header, footer, detail page, and stats
- Add external-link icon to footer GitHub anchor; add `hover:text-foreground` to footer nav links
- Fix `PreviewBlock` top spacing (`mt-4` moved to `pt-4` on the wrapper so the border-top sits flush)
- Add "audit the code" link to stats telemetry opt-out blurb pointing at the telemetry source file
This commit is contained in:
2026-05-26 18:03:03 -04:00
parent 2bde308229
commit 3028165da4
12 changed files with 77 additions and 91 deletions
+2 -2
View File
@@ -26,7 +26,7 @@
"@tanstack/react-pacer": "^0.22.1",
"@tanstack/react-router": "^1.170.8",
"@tanstack/react-router-devtools": "^1.167.0",
"@tanstack/react-start": "^1.168.13",
"@tanstack/react-start": "^1.168.14",
"@vercel/functions": "^3.6.0",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
@@ -42,7 +42,7 @@
"react-dom": "^19.2.6",
"react-resizable-panels": "^4.11.2",
"recharts": "^3.8.1",
"shadcn": "^4.8.0",
"shadcn": "^4.8.1",
"shiki": "^4.1.0",
"sonner": "^2.0.7",
"tailwind-merge": "^3.6.0",
@@ -236,7 +236,7 @@ export function FilePreview({
function EmptyState() {
return (
<div className="flex min-h-[280px] items-center justify-center px-6 py-10 text-center text-sm text-muted-foreground lg:min-h-0 lg:flex-1">
The file tree will populate as you pick modules.
Files appear here as you add modules.
</div>
);
}
@@ -257,7 +257,7 @@ function PreviewPane({
if (!preview || !path) {
return (
<div className="flex h-full items-center justify-center px-6 py-10 text-sm text-muted-foreground">
Select a file
Pick a file to preview
</div>
);
}
@@ -7,7 +7,7 @@ import {
PEER_CATEGORIES,
resolveAdapter,
} from "@stanza/registry";
import { IconCheck, IconHome, IconInfoCircle } from "@tabler/icons-react";
import { IconCheck, IconInfoCircle } from "@tabler/icons-react";
import { Link } from "@tanstack/react-router";
import { memo, useCallback, useMemo } from "react";
@@ -187,45 +187,23 @@ const ModuleCard = memo(function ModuleCard({
<div className="flex items-start justify-between gap-2">
<div className="flex min-w-0 items-center gap-2">
<h3 className="truncate text-sm leading-tight font-medium">{m.label}</h3>
<span className="flex shrink-0 items-center gap-1.5">
{m.homepage && (
<Tooltip>
<TooltipTrigger
nativeButton={false}
render={
<a
href={m.homepage}
target="_blank"
rel="noreferrer"
aria-label={`${m.label} website`}
onClick={(e) => e.stopPropagation()}
className="inline-flex shrink-0 translate-y-[-2px] items-center justify-center text-foreground/60 transition-colors hover:text-foreground"
/>
}
>
<IconHome className="size-3.5" aria-hidden />
</TooltipTrigger>
<TooltipContent sideOffset={8}>Open website</TooltipContent>
</Tooltip>
)}
<Tooltip>
<TooltipTrigger
nativeButton={false}
render={
<Link
to="/registry/$category/$id"
params={{ category: m.category, id: m.id }}
aria-label={`${m.label} details`}
onClick={(e) => e.stopPropagation()}
className="inline-flex shrink-0 translate-y-[-2px] items-center justify-center text-foreground/60 transition-colors hover:text-foreground"
/>
}
>
<IconInfoCircle className="size-3.5" aria-hidden />
</TooltipTrigger>
<TooltipContent sideOffset={8}>View details</TooltipContent>
</Tooltip>
</span>
<Tooltip>
<TooltipTrigger
nativeButton={false}
render={
<Link
to="/registry/$category/$id"
params={{ category: m.category, id: m.id }}
aria-label={`${m.label} details`}
onClick={(e) => e.stopPropagation()}
className="inline-flex shrink-0 translate-y-[-2px] items-center justify-center text-foreground/40 transition-colors hover:text-foreground"
/>
}
>
<IconInfoCircle className="size-3.5" aria-hidden />
</TooltipTrigger>
<TooltipContent sideOffset={8}>View details</TooltipContent>
</Tooltip>
</div>
{selected && <IconCheck className="size-4 shrink-0 text-foreground" aria-hidden />}
</div>
@@ -255,9 +233,9 @@ function describeError(error: ResolveError): string {
case "missing-peer":
return `Pick a ${categoryLabel(error.category)} module first.`;
case "incompatible-peer":
return `Doesn't pair with ${error.peer} (your ${categoryLabel(error.category)} pick).`;
return `Not compatible with ${error.peer} (${categoryLabel(error.category)}).`;
case "no-adapter":
return "No adapter matches your current stack.";
return "Not compatible with your current stack.";
default:
error satisfies never;
throw new Error(`Unknown resolve error: ${String(error)}`);
@@ -64,7 +64,7 @@ function PreviewBlock({ preview }: { preview: Preview }) {
);
return (
<div
className="mt-4 overflow-auto border-t border-border text-xs leading-relaxed [&_pre]:bg-transparent! [&_pre]:p-0!"
className="overflow-auto border-t border-border pt-4 text-xs leading-relaxed [&_pre]:bg-transparent! [&_pre]:p-0!"
dangerouslySetInnerHTML={inner}
/>
);
+10 -6
View File
@@ -1,3 +1,4 @@
import { IconExternalLink } from "@tabler/icons-react";
import { Link } from "@tanstack/react-router";
export function Footer() {
@@ -9,8 +10,8 @@ export function Footer() {
<a
href="https://github.com/jakejarvis"
target="_blank"
rel="noreferrer"
className="underline underline-offset-4 hover:text-foreground"
rel="noopener noreferrer"
className="underline underline-offset-1 hover:text-foreground"
>
@jakejarvis
</a>
@@ -18,17 +19,20 @@ export function Footer() {
</p>
<nav className="flex items-center gap-5">
<span>v{__APP_VERSION__ ?? "0.0.0"}</span>
<Link to="/docs/$" params={{ _splat: "" }}>
<Link to="/docs/$" params={{ _splat: "" }} className="hover:text-foreground">
Docs
</Link>
<Link to="/stats">Stats</Link>
<Link to="/stats" className="hover:text-foreground">
Stats
</Link>
<a
href="https://github.com/jakejarvis/stanza"
className="hover:text-foreground"
className="inline-flex items-center gap-1 hover:text-foreground"
target="_blank"
rel="noreferrer"
rel="noopener noreferrer"
>
GitHub
<IconExternalLink className="size-3" aria-hidden />
</a>
</nav>
</div>
+1 -1
View File
@@ -40,7 +40,7 @@ export function Header() {
<a
href="https://github.com/jakejarvis/stanza"
target="_blank"
rel="noreferrer"
rel="noopener noreferrer"
aria-label="GitHub"
/>
}
+2 -2
View File
@@ -42,7 +42,7 @@ function CenteredMessage({
}
const GENERIC_ERROR_MESSAGE =
"An unexpected error occurred while rendering this page. Try again, or head back to the builder.";
"Something broke while loading this page. Try again, or head back to the builder.";
/**
* Default error component for the router. Re-runs the loader on Try again via
@@ -97,7 +97,7 @@ export function RouteNotFoundBoundary() {
return (
<CenteredMessage
title="Page not found"
description="That page doesnt exist. It may have moved, or the URL might be wrong."
description="We couldnt find that page. It may have moved, or the URL might be wrong."
>
<Button render={<Link to="/" />} nativeButton={false} variant="outline" size="sm">
<IconHome data-icon="inline-start" />
+1 -1
View File
@@ -53,7 +53,7 @@ function Page() {
<Link
to="/docs/$"
params={{ _splat: "" }}
className="text-primary underline underline-offset-4 hover:text-primary/80"
className="text-primary underline underline-offset-3 hover:text-primary/80"
>
Learn more
</Link>
@@ -124,7 +124,7 @@ function ModuleDetailPage() {
<a
href={module.homepage}
target="_blank"
rel="noreferrer"
rel="noopener noreferrer"
className="inline-flex items-center gap-1 text-muted-foreground transition-colors hover:text-foreground"
>
Website
+23 -15
View File
@@ -80,8 +80,8 @@ function StatsPage() {
<header className="mb-8 max-w-2xl">
<h1 className="text-3xl font-semibold tracking-tight">Stats</h1>
<p className="mt-2 text-pretty text-muted-foreground">
What modules people actually pick, aggregated from anonymous CLI telemetry {" "}
<a href="#telemetry" className="text-primary underline underline-offset-4">
What modules developers actually pick, aggregated from anonymous CLI telemetry {" "}
<a href="#telemetry" className="text-primary underline underline-offset-3">
see exactly what&rsquo;s collected
</a>
.
@@ -129,7 +129,9 @@ function StatsPage() {
</CardTitle>
{!isLoading ? (
<span className="font-mono text-xs text-muted-foreground tabular-nums">
{activitySum > 0 ? `${numberFormatter.format(activitySum)} total` : "no runs yet"}
{activitySum > 0
? `${numberFormatter.format(activitySum)} total`
: "No runs yet."}
</span>
) : null}
</div>
@@ -196,13 +198,10 @@ function StatsPage() {
</CardHeader>
<CardContent>
<ul className="list-disc space-y-1 pl-4 text-muted-foreground">
<li>
Which command you ran (init, add, remove, list, search), how long it took, and
whether it succeeded.
</li>
<li>Which command you ran, how long it took, and whether it succeeded.</li>
<li>CLI version, Node version, OS, and architecture.</li>
<li>For installs/removes: the module id and its category.</li>
<li>An ephemeral UUID generated per process to deduplicate events.</li>
<li>For installs/removes, the module ID and its category.</li>
<li>An ephemeral UUID generated per-process to deduplicate events.</li>
</ul>
</CardContent>
</Card>
@@ -215,7 +214,7 @@ function StatsPage() {
<CardContent>
<ul className="list-disc space-y-1 pl-4 text-muted-foreground">
<li>File paths, project names, environment variables, or template contents.</li>
<li>Your IP address (PostHog ingest is server-proxied through this site).</li>
<li>Your IP address (PostHog ingest is server-proxied).</li>
<li>Any persistent identifier the process UUID is discarded on exit.</li>
<li>
Anything from CI runs (telemetry auto-skips when <code>CI</code> is set).
@@ -226,17 +225,26 @@ function StatsPage() {
</div>
<p className="mt-4 text-xs leading-5 text-muted-foreground">
<strong className="font-medium text-foreground">Opt out</strong> per-invocation with{" "}
<code>--no-telemetry</code>, persistently with <code>STANZA_TELEMETRY=0</code> or{" "}
<code>DO_NOT_TRACK=1</code>. More in the{" "}
<code>--no-telemetry</code>, or permanently with <code>STANZA_TELEMETRY=0</code> or{" "}
<code>DO_NOT_TRACK=1</code>. Learn more in the{" "}
<Link
to="/docs/$"
params={{ _splat: "cli" }}
hash="telemetry"
className="text-primary underline underline-offset-4"
className="text-primary underline underline-offset-1"
>
CLI docs
</Link>
.
</Link>{" "}
or{" "}
<a
href="https://github.com/jakejarvis/stanza/blob/main/apps/cli/src/lib/telemetry.ts"
target="_blank"
rel="noopener noreferrer"
className="text-primary underline underline-offset-1"
>
audit the code
</a>{" "}
yourself.
</p>
</section>
</div>
-1
View File
@@ -23,7 +23,6 @@
"devDependencies": {
"@changesets/changelog-github": "^0.7.0",
"@changesets/cli": "^2.31.0",
"@stanza/registry": "workspace:*",
"@types/node": "^25.9.1",
"jiti": "^2.7.0",
"typescript": "^6.0.3",
+14 -17
View File
@@ -25,9 +25,6 @@ importers:
'@changesets/cli':
specifier: ^2.31.0
version: 2.31.0(@types/node@25.9.1)
'@stanza/registry':
specifier: workspace:*
version: link:packages/registry
'@types/node':
specifier: ^25.9.1
version: 25.9.1
@@ -132,8 +129,8 @@ importers:
specifier: ^1.167.0
version: 1.167.0(@tanstack/react-router@1.170.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@tanstack/router-core@1.171.6)(csstype@3.2.3)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@tanstack/react-start':
specifier: ^1.168.13
version: 1.168.13(@vitejs/plugin-rsc@0.5.26(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(crossws@0.4.5(srvx@0.11.16))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
specifier: ^1.168.14
version: 1.168.14(@vitejs/plugin-rsc@0.5.26(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(crossws@0.4.5(srvx@0.11.16))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@vercel/functions':
specifier: ^3.6.0
version: 3.6.0
@@ -180,8 +177,8 @@ importers:
specifier: ^3.8.1
version: 3.8.1(@types/react@19.2.15)(react-dom@19.2.6(react@19.2.6))(react-is@17.0.2)(react@19.2.6)(redux@5.0.1)
shadcn:
specifier: ^4.8.0
version: 4.8.0(@types/node@25.9.1)(typescript@6.0.3)
specifier: ^4.8.1
version: 4.8.1(@types/node@25.9.1)(typescript@6.0.3)
shiki:
specifier: ^4.1.0
version: 4.1.0
@@ -2099,8 +2096,8 @@ packages:
react: '>=18.0.0 || >=19.0.0'
react-dom: '>=18.0.0 || >=19.0.0'
'@tanstack/react-start-client@1.168.4':
resolution: {integrity: sha512-PDJ7xEuUKrlBiQz2PrVN9pD2ErmWeFpckYW1WUE8JCAeVi8U7C6rQNTQe4hQxBhycRfRdD53M6UfdWdQODIxyg==}
'@tanstack/react-start-client@1.168.5':
resolution: {integrity: sha512-xRAGDhb81kvJa9lkr5CLt66HVMJWGh1iPZL/AYO8/wnPdbwFbRKSv5Yzx5SI+YQbB0qTgcYCyOPi4jrscAppag==}
engines: {node: '>=22.12.0'}
peerDependencies:
react: '>=18.0.0 || >=19.0.0'
@@ -2130,8 +2127,8 @@ packages:
react: '>=18.0.0 || >=19.0.0'
react-dom: '>=18.0.0 || >=19.0.0'
'@tanstack/react-start@1.168.13':
resolution: {integrity: sha512-E2pHQ92NiND1/HiD5Ax71xFXxiRZ2reOfU5W4BqxUL5plap3p8xSw1c6L8Np1E60vsxknuPCYRZESKkRy/LkOA==}
'@tanstack/react-start@1.168.14':
resolution: {integrity: sha512-Y/CXqtufmZkUL6OCcxxDoGxDDnz/hqrYqCJQeiBHx4MUbn9mx3sZviFVDkFrsyi/8J+IKjXdV5LlNATAxHQzAQ==}
engines: {node: '>=22.12.0'}
peerDependencies:
'@rsbuild/core': ^2.0.0
@@ -4764,8 +4761,8 @@ packages:
setprototypeof@1.2.0:
resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==}
shadcn@4.8.0:
resolution: {integrity: sha512-LAm3I/1FdoU/zu5GVG8Hbna4X9zlzEG5TeeCPXqsopkjvGk8QUF9OFhqeRN8oM6Oh/ynUI/yQHZxQAO3Ymcqsg==}
shadcn@4.8.1:
resolution: {integrity: sha512-B2J63uYwqfLJPNzZk2AAlonXG1aJhwv8xmXlUag8OZdV61CQB6570nkXLjU1u9XN1qXm0LpJhSFQ0ZhlOruA4Q==}
hasBin: true
shebang-command@2.0.0:
@@ -6914,7 +6911,7 @@ snapshots:
react: 19.2.6
react-dom: 19.2.6(react@19.2.6)
'@tanstack/react-start-client@1.168.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
'@tanstack/react-start-client@1.168.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
'@tanstack/react-router': 1.170.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@tanstack/router-core': 1.171.6
@@ -6958,10 +6955,10 @@ snapshots:
transitivePeerDependencies:
- crossws
'@tanstack/react-start@1.168.13(@vitejs/plugin-rsc@0.5.26(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(crossws@0.4.5(srvx@0.11.16))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
'@tanstack/react-start@1.168.14(@vitejs/plugin-rsc@0.5.26(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(crossws@0.4.5(srvx@0.11.16))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)':
dependencies:
'@tanstack/react-router': 1.170.8(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@tanstack/react-start-client': 1.168.4(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@tanstack/react-start-client': 1.168.5(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@tanstack/react-start-rsc': 0.1.13(@vitejs/plugin-rsc@0.5.26(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(react-dom@19.2.6(react@19.2.6))(react@19.2.6))(@voidzero-dev/vite-plus-core@0.1.22(@types/node@25.9.1)(esbuild@0.28.0)(jiti@2.7.0)(typescript@6.0.3))(crossws@0.4.5(srvx@0.11.16))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@tanstack/react-start-server': 1.167.9(crossws@0.4.5(srvx@0.11.16))(react-dom@19.2.6(react@19.2.6))(react@19.2.6)
'@tanstack/router-utils': 1.162.1
@@ -9928,7 +9925,7 @@ snapshots:
setprototypeof@1.2.0: {}
shadcn@4.8.0(@types/node@25.9.1)(typescript@6.0.3):
shadcn@4.8.1(@types/node@25.9.1)(typescript@6.0.3):
dependencies:
'@babel/core': 7.29.7
'@babel/parser': 7.29.7