mirror of
https://github.com/jakejarvis/stanza.git
synced 2026-07-16 18:05:58 -04:00
fix: miscellaneous UI polish and doc link corrections
- Fix internal doc links to use absolute paths (`#anchor` → `/docs/page#anchor`) in `authoring.mdx` and `registry.mdx` so they resolve correctly from any page - Clarify `consumesPackages` description wording in the module field reference table - Switch category badge on module detail page from `outline` to `default` variant for better visual hierarchy - Nudge `IconCheck` margin, `Label` leading, and adapter-switcher category span font size for tighter alignment - Normalize `CardTitle` color from `text-foreground/85` → `text-foreground/80` and `text-muted-foreground` across stats cards for consistency; add `size="sm"` to per-category cards and tighten card spacing - Add `text-sm` to the "Learn more…" docs link on the home page so it inherits the correct size - Lighten `LastRefreshed` text from `/70` → `/85` opacity for legibility
This commit is contained in:
@@ -68,18 +68,18 @@ export default defineModule({
|
||||
|
||||
The whole shape is typed and validated at runtime — `defineModule` throws on the structural rules TypeScript can't express (e.g. `app` install-field overlays on `home: "repo"` modules), and `ModuleSchema` parses the JSON form the CLI fetches.
|
||||
|
||||
| Field | Required | What it does |
|
||||
| -------------------- | -------- | -------------------------------------------------------------------------------------------------------------------- |
|
||||
| `id` | ✓ | Stable identifier within a category. Used in `stanza add <category> <id>`. |
|
||||
| `category` | ✓ | One of `KNOWN_CATEGORIES` — see [Categories](/docs/concepts#categories). |
|
||||
| `label` | ✓ | Display name for the wizard, search results, and the web builder. |
|
||||
| `description` | ✓ | One-line summary. Surfaced in `stanza search` and module cards. |
|
||||
| `version` | ✓ | Semver string. Pinned into `stanza.json` at install time so future `update`/`swap` can read it. |
|
||||
| `peers` | | `Partial<Record<CategoryId, ModuleId[] \| "any">>` — categories this module needs filled. |
|
||||
| `consumesPackages` | | Dirs of other internal packages this one imports from (see [Cross-package consumption](#cross-package-consumption)). |
|
||||
| `adapters` | ✓ | At least one. Each carries its own install fields, templates, and codemods. |
|
||||
| `appKind` | | `"web"` or `"native"`. The runner refuses to install into an incompatible app. |
|
||||
| `homepage`, `author` | | Surfaced in the web builder; otherwise informational. |
|
||||
| Field | Required | What it does |
|
||||
| -------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `id` | ✓ | Stable identifier within a category. Used in `stanza add <category> <id>`. |
|
||||
| `category` | ✓ | One of `KNOWN_CATEGORIES` — see [Categories](/docs/concepts#categories). |
|
||||
| `label` | ✓ | Display name for the wizard, search results, and the web builder. |
|
||||
| `description` | ✓ | One-line summary. Surfaced in `stanza search` and module cards. |
|
||||
| `version` | ✓ | Semver string. Pinned into `stanza.json` at install time so future `update`/`swap` can read it. |
|
||||
| `peers` | | `Partial<Record<CategoryId, ModuleId[] \| "any">>` — categories this module needs filled. |
|
||||
| `consumesPackages` | | Dirs of other internal packages this one imports from (see [Cross-package consumption](/docs/authoring#cross-package-consumption)). |
|
||||
| `adapters` | ✓ | At least one. Each carries its own install fields, templates, and codemods. |
|
||||
| `appKind` | | `"web"` or `"native"`. The runner refuses to install into an incompatible app. |
|
||||
| `homepage`, `author` | | Surfaced in the web builder; otherwise informational. |
|
||||
|
||||
Module-level install fields (`dependencies`, `devDependencies`, `env`, `scripts`) are merged into every adapter — adapter-level values override per key, and `env` merges by `name`. Hoist anything that doesn't vary across adapters.
|
||||
|
||||
@@ -231,7 +231,7 @@ The runner routes overlay fields into every consuming app's `package.json` (vs t
|
||||
|
||||
## Cross-package consumption
|
||||
|
||||
If your module's source imports from another internal package (e.g. Better Auth's `auth.ts` reads `db` from the orm package), declare the dependency at the module level:
|
||||
If your module's source imports from another internal package (e.g. Better Auth's `auth.ts` reads `db` from the ORM package for its database schema), declare the dependency at the module level:
|
||||
|
||||
```ts
|
||||
defineModule({
|
||||
|
||||
@@ -3,7 +3,7 @@ title: Registry
|
||||
description: The roadmap of first-party modules Stanza ships — what's available today and what's planned.
|
||||
---
|
||||
|
||||
Every module fills exactly one **category**. This page is the canonical roadmap of the first-party modules Stanza ships: what's available today, and what's on the way. Third parties can publish their own modules under any `@scope` and have users pull them in alongside these — see the [Authoring manual](/docs/authoring) for the full surface, or jump to [Third-party modules](#third-party-modules) below for a pointer.
|
||||
Every module fills exactly one **category**. This page is the canonical roadmap of the first-party modules Stanza ships: what's available today, and what's on the way. Third parties can publish their own modules under any `@scope` and have users pull them in alongside these — see the [Authoring manual](/docs/authoring) for the full surface, or jump to [Third-party modules](/docs/registry#third-party-modules) below for a pointer.
|
||||
|
||||
## Categories
|
||||
|
||||
|
||||
@@ -202,7 +202,9 @@ const ModuleCard = memo(function ModuleCard({
|
||||
<TooltipContent sideOffset={8}>View details</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
{selected && <IconCheck className="size-4 shrink-0 text-foreground" aria-hidden />}
|
||||
{selected && (
|
||||
<IconCheck className="mr-0.5 size-4 shrink-0 text-foreground" aria-hidden />
|
||||
)}
|
||||
</div>
|
||||
<p className="mt-1 text-xs leading-relaxed text-muted-foreground">{m.description}</p>
|
||||
</div>
|
||||
|
||||
@@ -48,7 +48,10 @@ export function ProjectSetup({
|
||||
|
||||
return (
|
||||
<Card className="gap-0 px-3 py-3.5">
|
||||
<Label htmlFor={inputId} className="mb-1.5 text-[13px] font-medium text-muted-foreground">
|
||||
<Label
|
||||
htmlFor={inputId}
|
||||
className="mb-2 text-[13px] leading-none font-medium text-muted-foreground"
|
||||
>
|
||||
Project name
|
||||
</Label>
|
||||
<Field data-invalid={showError || undefined}>
|
||||
|
||||
@@ -49,7 +49,7 @@ export function AdapterSwitcher({
|
||||
const active = resolvedPeers[slot];
|
||||
return (
|
||||
<div key={slot} className="flex flex-wrap items-center gap-2">
|
||||
<span className="w-20 shrink-0 text-xs font-medium text-muted-foreground">
|
||||
<span className="w-20 shrink-0 text-[13px] font-medium tracking-tight text-muted-foreground">
|
||||
{categoryLabel(slot)}
|
||||
</span>
|
||||
<ToggleGroup
|
||||
|
||||
@@ -46,6 +46,7 @@ function listRegistryPaths(): string[] {
|
||||
{ cause: error },
|
||||
);
|
||||
}
|
||||
// oxlint-disable-next-line typescript/no-unsafe-type-assertion
|
||||
const index = JSON.parse(raw) as {
|
||||
categories: Array<{ id: string }>;
|
||||
modules: Array<{ category: string; id: string }>;
|
||||
@@ -55,10 +56,6 @@ function listRegistryPaths(): string[] {
|
||||
return [...categoryPaths, ...modulePaths].toSorted((a, b) => a.localeCompare(b));
|
||||
}
|
||||
|
||||
// OG image routes are deliberately not prerendered — they're rendered at
|
||||
// request time by takumi-rs (kept warm in the server bundle) and the binary
|
||||
// outputs collide with sibling subdirs at the filesystem level (e.g. `/og`
|
||||
// as a file vs `og/registry/...` as a directory).
|
||||
export function listPrerenderPages() {
|
||||
const docs = listDocsPaths();
|
||||
const registry = listRegistryPaths();
|
||||
|
||||
@@ -41,7 +41,7 @@ function Page() {
|
||||
<Link
|
||||
to="/docs/$"
|
||||
params={{ _splat: "" }}
|
||||
className="text-primary underline underline-offset-3 hover:text-primary/80"
|
||||
className="text-sm text-primary underline underline-offset-3 hover:text-primary/80"
|
||||
>
|
||||
Learn more…
|
||||
</Link>
|
||||
|
||||
@@ -115,7 +115,7 @@ function ModuleDetailPage() {
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<h1 className="text-2xl font-medium tracking-tight">{module.label}</h1>
|
||||
<Badge variant="outline">{categoryLabel(module.category)}</Badge>
|
||||
<Badge variant="default">{categoryLabel(module.category)}</Badge>
|
||||
</div>
|
||||
<p className="mt-1.5 text-sm text-muted-foreground">{module.description}</p>
|
||||
<div className="mt-3 flex flex-wrap gap-3 text-xs">
|
||||
|
||||
@@ -86,7 +86,7 @@ function CategoryLandingPage() {
|
||||
<Link
|
||||
to="/registry/$category/$id"
|
||||
params={{ category: m.category, id: m.id }}
|
||||
className="flex items-start gap-3 px-3 py-3 transition-colors hover:bg-muted/50"
|
||||
className="flex items-start gap-3 p-3 transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<ModuleLogo logo={m.logo} label={m.label} />
|
||||
<div className="min-w-0 flex-1">
|
||||
|
||||
@@ -6,7 +6,7 @@ import { lazy, Suspense, useEffect, useState } from "react";
|
||||
|
||||
import { ModuleLogo } from "@/components/module-logo";
|
||||
import { BarList } from "@/components/stats/bar-list";
|
||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Card, CardAction, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||
import { Skeleton } from "@/components/ui/skeleton";
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
|
||||
import { useTimeAgo } from "@/hooks/use-time-ago";
|
||||
@@ -44,7 +44,7 @@ function formatGeneratedAt(iso: string): string {
|
||||
function LastRefreshed({ iso }: { iso: string }) {
|
||||
const ago = useTimeAgo(iso);
|
||||
return (
|
||||
<p className="font-mono text-xs leading-4 text-muted-foreground/70">
|
||||
<p className="font-mono text-xs leading-4 text-muted-foreground/85">
|
||||
Last refreshed{" "}
|
||||
<Tooltip>
|
||||
<TooltipTrigger
|
||||
@@ -105,9 +105,9 @@ function StatsPage() {
|
||||
</header>
|
||||
|
||||
<section className="mb-4 grid gap-4 sm:grid-cols-2">
|
||||
<Card>
|
||||
<Card className="gap-2 pb-2">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-foreground/80 uppercase">
|
||||
Projects scaffolded
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -117,9 +117,9 @@ function StatsPage() {
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<Card className="gap-2 pb-2">
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-foreground/80 uppercase">
|
||||
Modules installed
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -132,12 +132,12 @@ function StatsPage() {
|
||||
</section>
|
||||
|
||||
<section className="mb-8">
|
||||
<Card>
|
||||
<Card className="gap-2 pb-2">
|
||||
<CardHeader>
|
||||
<div className="flex items-baseline justify-between gap-4">
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-muted-foreground uppercase">
|
||||
CLI runs · last 30 days
|
||||
</CardTitle>
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-foreground/80 uppercase">
|
||||
CLI runs <span className="text-muted-foreground">· last 30 days</span>
|
||||
</CardTitle>
|
||||
<CardAction className="font-mono text-xs text-muted-foreground tabular-nums">
|
||||
{stats ? (
|
||||
<span className="font-mono text-xs text-muted-foreground tabular-nums">
|
||||
{activitySum > 0
|
||||
@@ -145,9 +145,9 @@ function StatsPage() {
|
||||
: "No runs yet."}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent className="pb-1">
|
||||
<CardContent>
|
||||
<Suspense fallback={<div aria-hidden className="h-24 w-full" />}>
|
||||
<ActivityChart activity30d={stats?.activity30d ?? []} isLoading={stats === null} />
|
||||
</Suspense>
|
||||
@@ -162,18 +162,14 @@ function StatsPage() {
|
||||
const entries = stats?.perCategory[category] ?? [];
|
||||
const totalInCategory = entries.reduce((acc, entry) => acc + entry.count, 0);
|
||||
return (
|
||||
<Card key={category}>
|
||||
<CardHeader>
|
||||
<div className="flex items-baseline justify-between gap-3">
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-foreground/85 uppercase">
|
||||
{categoryLabel(category)}
|
||||
</CardTitle>
|
||||
{totalInCategory > 0 ? (
|
||||
<span className="font-mono text-xs text-muted-foreground tabular-nums">
|
||||
{numberFormatter.format(totalInCategory)}
|
||||
</span>
|
||||
) : null}
|
||||
</div>
|
||||
<Card size="sm" key={category}>
|
||||
<CardHeader className="pb-1">
|
||||
<CardTitle className="text-xs! font-medium tracking-wider text-foreground/80 uppercase">
|
||||
{categoryLabel(category)}
|
||||
</CardTitle>
|
||||
<CardAction className="font-mono text-xs text-muted-foreground tabular-nums">
|
||||
{totalInCategory > 0 ? numberFormatter.format(totalInCategory) : null}
|
||||
</CardAction>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<BarList
|
||||
@@ -201,9 +197,9 @@ function StatsPage() {
|
||||
<section id="telemetry" className="scroll-mt-20">
|
||||
<h2 className="mb-4 text-lg font-medium tracking-tight">Telemetry policy</h2>
|
||||
<div className="grid gap-4 sm:grid-cols-2">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-foreground/85 uppercase">
|
||||
<Card size="sm">
|
||||
<CardHeader className="pb-1">
|
||||
<CardTitle className="text-xs! font-medium tracking-wider text-foreground/80 uppercase">
|
||||
What we save
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -216,9 +212,9 @@ function StatsPage() {
|
||||
</ul>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="text-xs font-medium tracking-wider text-foreground/85 uppercase">
|
||||
<Card size="sm">
|
||||
<CardHeader className="pb-1">
|
||||
<CardTitle className="text-xs! font-medium tracking-wider text-foreground/80 uppercase">
|
||||
What we don’t
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import path from "node:path";
|
||||
|
||||
import { LRUCache } from "lru-cache";
|
||||
import {
|
||||
createHighlighter,
|
||||
@@ -27,6 +29,7 @@ const LANGS = [
|
||||
"shell",
|
||||
"prisma",
|
||||
"sql",
|
||||
"dotenv",
|
||||
] as const satisfies readonly BundledLanguage[];
|
||||
|
||||
const THEMES = ["github-light", "github-dark"] as const satisfies readonly BundledTheme[];
|
||||
@@ -67,12 +70,15 @@ const EXT_TO_LANG: Record<string, BundledLanguage> = {
|
||||
bash: "bash",
|
||||
prisma: "prisma",
|
||||
sql: "sql",
|
||||
env: "dotenv",
|
||||
// anything else returns undefined → highlighter renders as plaintext
|
||||
};
|
||||
|
||||
export function langForPath(path: string): BundledLanguage | undefined {
|
||||
const ext = path.split(".").pop()?.toLowerCase() ?? "";
|
||||
return EXT_TO_LANG[ext];
|
||||
export function langForPath(filePath: string): BundledLanguage | undefined {
|
||||
const base = path.basename(filePath);
|
||||
if (base.startsWith(".env")) return "dotenv";
|
||||
const ext = path.extname(filePath).toLowerCase();
|
||||
return EXT_TO_LANG[ext.slice(1)];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user