Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ custom properties.
pnpm add sve-ui
```

➡️ **Usage, components and theming:** see the [package README](./packages/sve-ui/README.md).
➡️ **Usage, all 60 components and theming:** see the [package README](./packages/sve-ui/README.md).

## Upgrading from 0.1.x

Expand All @@ -39,6 +39,7 @@ packages/
typescript-config/ # shared @repo/typescript-config
apps/
docs/ # documentation site (SvelteKit 2 + Tailwind 4) → sveui.org
example/ # a real app built with the library — dogfooding with latency
```

Shared dependency versions live in the pnpm **catalog** in `pnpm-workspace.yaml`
Expand Down
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ exported symbol, which handles both the bare and the explicitly-annotated form.
### 3. No visual regression coverage — PARTLY DONE

- [x] `apps/docs/scripts/check-render.mjs` — a structural render guard over all
**65** prerendered pages, wired into CI after `build`. It compares a digest
**67** prerendered pages, wired into CI after `build`. It compares a digest
(ordered element skeleton plus attribute names, values and text dropped)
against a committed baseline, so copy edits are free while a changed tag,
lost attribute or reordered element fails. Normalises the things that churn
Expand Down
58 changes: 32 additions & 26 deletions apps/docs/README.md
Original file line number Diff line number Diff line change
@@ -1,41 +1,47 @@
# create-svelte
# docs

Everything you need to build a Svelte project, powered by [`create-svelte`](https://github.com/sveltejs/kit/tree/master/packages/create-svelte).
The documentation site for `sve-ui` — [sveui.org](https://sveui.org). SvelteKit 2
with Tailwind 4, fully prerendered, deployed on Vercel.

## Creating a project
Tailwind is used **here only**, for page chrome. It is deliberately absent from
the library and from consumer projects; that is the whole pitch.

If you're seeing this, you've probably already done this step. Congrats!

```bash
# create a new project in the current directory
npm create svelte@latest

# create a new project in my-app
npm create svelte@latest my-app
```sh
pnpm dev # localhost:5173
pnpm build # prerenders every route into .vercel/output/static
pnpm preview
```

## Developing
## What is generated, and from what

Once you've created a project and installed dependencies with `npm install` (or `pnpm install` or `yarn`), start a development server:
Nothing on this site states a fact twice. Anything that could drift is derived
from a single source, because it already drifted once: a hardcoded "13
components" shipped against a registry of 58.

```bash
npm run dev
| Output | Source | Command |
| ------------------------------ | --------------------------------------------------------------------------------------------------- | ---------------------------------- |
| Prop tables | the library's TypeScript types, via the compiler's checker (so inherited Bits UI props resolve too) | `pnpm gen:props` — runs on `build` |
| `/sitemap.xml` | `src/lib/docs/registry.ts` + `src/lib/docs/guides.ts` | prerendered on `build` |
| `/llms.txt` | the same registry and guide nav | prerendered on `build` |
| Component counts on every page | `readyComponents` from the registry | — |
| `static/og.png` (social card) | `scripts/gen-og.mjs`, rendered with Playwright's Chromium | `pnpm gen:og` |

# or start the server and open the app in a new browser tab
npm run dev -- --open
```
`gen:og` is **not** part of `build` — the Vercel builder has no browser — so the
PNG is committed. Re-run it after changing the tagline, the count or the
branding.

## Building
## Guards

To create a production version of your app:

```bash
npm run build
```sh
pnpm check:render # structural: element skeleton of all prerendered pages
pnpm test:visual # appearance: screenshots of every live preview
pnpm gen:props:check # fails if the committed prop data is stale
```

You can preview the production build with `npm run preview`.

> To deploy your app, you may need to install an [adapter](https://kit.svelte.dev/docs/adapters) for your target environment.
`check:render` compares an ordered element skeleton (attribute values and text
dropped) against `render-baseline.json`, so copy edits are free while a changed
tag, a lost attribute or a reordered element fails. Accept an intended
structural change with `pnpm check:render:update`.

## Visual regression

Expand Down
39 changes: 28 additions & 11 deletions packages/sve-ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,14 +68,26 @@ without it):

## Components

**Display & form** — `Button`, `Input`, `Card`, `Badge`, `Avatar`, `Spinner`,
`Text`, `Heading`, `Alert`.

**Form controls** (on Bits UI) — `Switch`, `Checkbox`, `RadioGroup`.

**Overlays** (on Bits UI) — `Dialog`, `DropdownMenu`, `Tooltip`, `Popover`.

Most components take `variant`, `color` and `size` props, e.g.:
**60 components**, every one styled and accessible out of the box. Names ending
in `.*` are namespace compositions — import the namespace and compose its parts
(`Dialog.Root`, `Dialog.Trigger`, `Dialog.Content`). The rest are default
imports.

| Group | Components |
| -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Display** | `Avatar.*`, `Badge`, `Card.*`, `Heading`, `Text`, `Skeleton` |
| **Forms** | `Button`, `Input`, `Field`, `Checkbox.*`, `RadioGroup.*`, `Switch.*`, `Select.*`, `Combobox.*`, `Slider`, `Textarea`, `Label`, `Toggle`, `ToggleGroup.*`, `Calendar.*`, `DateField.*`, `DatePicker.*`, `RangeCalendar.*`, `DateRangeField.*`, `DateRangePicker.*`, `TimeField.*`, `TimeRangeField.*`, `PinInput.*`, `RatingGroup.*` |
| **Feedback** | `Alert.*`, `Busy`, `Spinner`, `Toast.*`, `Progress`, `Meter` |
| **Navigation** | `Tabs.*`, `Accordion.*`, `Sidebar.*`, `Breadcrumb.*`, `NavigationMenu.*`, `Menubar.*`, `Collapsible.*`, `Toolbar.*` |
| **Overlays** | `Dialog.*`, `DropdownMenu.*`, `Popover.*`, `Tooltip.*`, `AlertDialog.*`, `Command.*`, `Sheet.*`, `ContextMenu.*`, `LinkPreview.*` |
| **Data** | `Table.*`, `Pagination.*` |
| **Layout** | `Stack`, `Flex`, `Separator`, `ScrollArea.*`, `AspectRatio` |
| **Utilities** | `Code` |

Every component has a live page with props and examples at
[sveui.org/components](https://sveui.org/components).

Most take `variant`, `color` and `size`:

```svelte
<Button variant="solid" color="primary">Primary</Button>
Expand All @@ -84,9 +96,14 @@ Most components take `variant`, `color` and `size` props, e.g.:
<Input variant="outline" size="md" placeholder="you@example.com" bind:value />
```

`Avatar`, `Card`, `Alert`, the overlays, and the form controls are **namespaced**
compositions — import the namespace and compose its parts (`Dialog.Root`,
`Dialog.Trigger`, `Dialog.Content`; `RadioGroup.Root`, `RadioGroup.Item`; …).
Three that are easy to miss:

- **`Field`** is the only thing here that wires `aria-describedby` — use it to
attach help text or a validation message to any control.
- **`Busy`** covers the gap between "nothing yet" and "loaded": a region that
announces itself while its content is in flight.
- **`Command.*`** is the command palette, and its `Command.Status` announces
result counts to a screen reader as you filter.

## Theming

Expand Down
2 changes: 1 addition & 1 deletion packages/sve-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@
},
"scripts": {
"build": "vite build && pnpm package",
"package": "svelte-kit sync && svelte-package && node scripts/check-dts.mjs && node scripts/check-treeshake.mjs && node scripts/check-package-files.mjs && node scripts/check-css-coverage.mjs && node scripts/check-docs-coverage.mjs && publint",
"package": "svelte-kit sync && svelte-package && node scripts/check-dts.mjs && node scripts/check-treeshake.mjs && node scripts/check-package-files.mjs && node scripts/check-css-coverage.mjs && node scripts/check-docs-coverage.mjs && node scripts/check-readme-coverage.mjs && publint",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"test": "vitest run",
"lint": "eslint .",
Expand Down
91 changes: 91 additions & 0 deletions packages/sve-ui/scripts/check-readme-coverage.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/**
* The README is the npm page. Every component must appear on it.
*
* This exists because it had already rotted all the way through: the README
* listed 18 of 61 exports and sold a 60-component library as sixteen. Forty-three
* components — `Table`, `Toast`, `Sidebar`, `Combobox`, `Command`, the whole date
* family — were not named anywhere in the file. Nobody noticed, because nothing
* was looking. `check-docs-coverage` guards the registry, the docs pages and the
* agent skill; the one surface a person actually lands on had no guard at all.
*
* That surface is also the only one a search engine indexes for the package, so
* an incomplete README is both a documentation bug and a discovery bug.
*
* Like its sibling, this checks that the prose EXISTS, not that it is good.
*/

import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';

const PKG = fileURLToPath(new URL('..', import.meta.url));
const LIB_INDEX = join(PKG, 'src/lib/index.ts');
const README = join(PKG, 'README.md');

/**
* Exports that legitimately need no entry in the component table. Each needs a
* reason, so the list cannot quietly absorb a component someone forgot.
*/
const NOT_IN_TABLE = new Map([
['ThemeProvider', 'infrastructure, not a component: documented in Quick start and Theming']
]);

function exportedSymbols() {
const src = readFileSync(LIB_INDEX, 'utf8');
return [
...[...src.matchAll(/export \* as (\w+) from/g)].map((m) => ({ name: m[1], namespace: true })),
...[...src.matchAll(/export \{ default as (\w+) \}/g)].map((m) => ({
name: m[1],
namespace: false
}))
];
}

const readme = readFileSync(README, 'utf8');
const exported = exportedSymbols();
const problems = [];

for (const { name, namespace } of exported) {
if (NOT_IN_TABLE.has(name)) continue;

// Namespaces are written `Dialog.*` in the table; singles as plain `Button`.
// Requiring the backticks is deliberate — a component named only in passing
// prose is not the same as one listed where a reader goes looking.
const listed = namespace
? readme.includes(`\`${name}.*\``)
: new RegExp('`' + name + '`').test(readme);

if (!listed) {
problems.push(
namespace
? `${name} is exported but never listed as \`${name}.*\` in the README`
: `${name} is exported but never listed as \`${name}\` in the README`
);
}
}

// The headline count is prose, and prose drifts. Check it against the exports.
const components = exported.length - NOT_IN_TABLE.size;
const claimed = readme.match(/\*\*(\d+) components\*\*/);
if (!claimed) {
problems.push('the README no longer states a component count in the form **N components**');
} else if (Number(claimed[1]) !== components) {
problems.push(`the README claims ${claimed[1]} components but the library exports ${components}`);
}

if (problems.length > 0) {
console.error('check-readme-coverage: FAILED\n');
for (const p of problems) console.error(` - ${p}`);
console.error(
'\nThe README is the npm page — a component missing from it is a component\n' +
'nobody installing this package will know exists. Add it to the component\n' +
'table, or, if the export genuinely is not a component, add it to\n' +
'NOT_IN_TABLE in this script with a reason.'
);
process.exit(1);
}

console.log(
`check-readme-coverage: ${components} components, all listed in the README, ` +
'and the stated count matches.'
);
Loading