diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index 040fb7c1..73c01011 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -1,8 +1,9 @@ # Architecture `@corbits/react-ui` is an installed npm component library: the code lives in the -consumer's `node_modules` and is picked up by a version bump. Three properties shape -the layout. +consumer's `node_modules` and is picked up by a version bump. Why it exists is +in [PRODUCT.md](./PRODUCT.md); toolchain, peers and publish layout are in +[IMPLEMENTATION.md](./IMPLEMENTATION.md). Three properties shape the layout. **The public surface is a semver commitment.** Every subpath in `exports` is something we have promised not to break casually, so the map is *generated* from a triage list in @@ -52,7 +53,10 @@ Components in this package **never fetch**, and never import a server-side packa component that fetches has chosen the consumer's data layer for them; a component that imports a server package drags a database tree into a browser bundle. -Data arrives one of two ways: as props, or through a `DataPort`. +Data arrives one of two ways: as props, or through a `DataPort`. That is the +README contract — components take data through a `DataPort` (or none). "None" +is a props-only surface; `DataPortProvider` is required only for components +that call `useDataPort`. ```ts type DataPort = { @@ -153,9 +157,13 @@ Tailwind resolves `@source` relative to the file containing it, so it means `src build the standalone sheet and `dist/` when a consumer imports it out of `node_modules`. Since there is only one file, the tokens and keyframes cannot drift between the two. -Dark mode is opt-in through a `dark` class on an ancestor. `ThemeProvider` -owns applying that class (and `data-theme` presets + `color-scheme`) and -persisting the choice; the host mounts the provider and keys storage per user. +Dark mode is a `dark` class on an ancestor. The stylesheet reads it +(`@custom-variant dark (&:is(.dark *))`); it does not manage it. A host can +set the class itself (``). `ThemeProvider` is an +optional helper that applies that class (and `data-theme` presets plus +`color-scheme`) and persists the choice; the host mounts it and keys storage +per user. `useTheme` / `ThemeToggle` require the provider; every other +component does not. ### Contrast is gated diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md new file mode 100644 index 00000000..5b4dad1c --- /dev/null +++ b/IMPLEMENTATION.md @@ -0,0 +1,86 @@ +# Implementation + +Concrete toolchain, peers, formats and publish layout. Design of the +`DataPort` seam and the theme layer lives in [ARCHITECTURE.md](./ARCHITECTURE.md). +Why the library exists lives in [PRODUCT.md](./PRODUCT.md). + +## Runtime + +- **Node.js** `>=24` (`engines` in `package.json`). +- Package name `@corbits/react-ui`, ESM (`"type": "module"`), license + `LGPL-2.1-only`. +- Required peers: `react` and `react-dom` (`^18.2.0 || ^19.0.0`), + `lucide-react` (`^0.545.0 || ^1.0.0`), `sonner` (`^2.0.7`), + `@radix-ui/react-dialog` (`^1.1.15`), `@radix-ui/react-dropdown-menu` + (`^2.1.16`), `@radix-ui/react-slot` (`^1.2.3`), + `@radix-ui/react-tooltip` (`^1.2.8`). +- Optional peer: `@tanstack/react-query` (`^5.90.2`), needed only for + `createTanstackDataPort()`. Declared in `peerDependenciesMeta`. +- Direct dependencies: `class-variance-authority`, `clsx`, `tailwind-merge`. +- `sideEffects` is CSS-only (`**/*.css`). `files` publishes `dist/` only. + +## Toolchain + +Development uses **bun**. `bun run build` is four steps and a gate, joined +by `&&`: + +| Step | Tool | Output | +| --- | --- | --- | +| `generate` | `scripts/generate-exports.mjs` | `src/index.ts` and the `exports` map in `package.json` | +| `build:js` | SWC (`@swc/cli`, `@swc/core`) | `dist/**/*.js`, one per source file | +| `build:types` | `tsc -p tsconfig.build.json` | `dist/**/*.d.ts` | +| `build:css` | Tailwind v4 CLI | `dist/styles.css`, plus `dist/theme.css` copied from source | +| `contrast-test` | `scripts/contrast-test.mjs` | the gate — reads `dist/styles.css` | + +Source imports are relative and carry `.js` extensions. There is no path +alias. `prepack` runs the whole build so a failing gate cannot be packed. +`prepare` (`scripts/prepare.mjs`) builds `dist/` on a git install; it is a +no-op when `dist/` is already present. + +Other scripts: `bun run typecheck`, `bun run lint` (eslint), +`bun run dep-guard` (`scripts/dep-guard.mjs`), `bun test`, `bun run stories` +/ `stories:build` ([Ladle](https://ladle.dev) over `stories/`). + +## CSS artifacts + +Two published sheets, both generated from `src/theme.css`: + +- `@corbits/react-ui/styles.css` → `dist/styles.css`. Tailwind compiles + `src/styles.css` (`@import "tailwindcss"` then `./theme.css`). The host + needs no Tailwind. +- `@corbits/react-ui/theme.css` → `dist/theme.css`, a byte copy of + `src/theme.css`. For a host already on Tailwind v4. + +Dark mode is the CSS custom variant `@custom-variant dark (&:is(.dark *));`. +The sheet reads a `dark` class on an ancestor; it does not set it. Named +presets are `data-theme` values (`default` / `warm` / `cool`). Brand faces +are `--font-sans: "Red Hat Display", …` and `--font-mono: "Space Mono", …`; +no font files ship. + +`ThemeProvider` (`src/ui/theme-provider.tsx`) is optional host wiring. It +writes `.dark`, `data-theme`, and `color-scheme` on a root (default +`document.documentElement`) and persists `{ mode, preset }` JSON under +`localStorage` key `corbits-theme` (override with `storageKey`). Default +mode is `"system"`. `ThemeToggle` / `useTheme` require the provider; the +rest of the library does not. + +## DataPort adapter + +`src/lib/data-port.ts` is the seam type plus `DataPortProvider` / +`useDataPort`. `src/lib/tanstack-data-port.ts` is the one implementation: +`useInfiniteQuery` from `@tanstack/react-query`, `initialPageParam: 0`, +`getNextPageParam` from `page.nextOffset`. That file is the only public +module on `BARREL_EXCLUDED` in `scripts/generate-exports.mjs` — import it by +subpath `@corbits/react-ui/lib/tanstack-data-port`. The host must already +have a TanStack `QueryClientProvider`. + +`dep-guard` fails if anything reachable from `src/index.ts` imports an +optional peer, if any file other than the adapter imports +`@tanstack/react-query`, or if any file imports `@workbench/*`. + +## Publish + +Registry install is the documented path (`npm` / `pnpm` / `yarn` / +`bun add @corbits/react-ui`). `publishConfig.access` is `public`. A git +install still works via `prepare`, and under bun that requires +`trustedDependencies: ["@corbits/react-ui"]`. diff --git a/PRODUCT.md b/PRODUCT.md new file mode 100644 index 00000000..151a2f7b --- /dev/null +++ b/PRODUCT.md @@ -0,0 +1,55 @@ +# Product + +`@corbits/react-ui` is the Corbits component library for React. It exists so a +host can ship agent and workflow surfaces that already look finished — chat, +runs, schedules, artifacts, analytics, collections — without taking a data +layer, a backend, or a docs site from this package. + +The GitHub description is the product claim: **ship agentic UIs that feel +finished.** The code lives in the consumer's `node_modules` and is picked up +by a version bump. + +## Who it is for + +Teams building an agent or workflow product in React. The host already has an +app, a router, and a source of data. This package is the visual and +interaction layer, not the product. + +It is not a design-system starter, not a CLI, and not a template app. + +## What a host can do + +- Import one module at a time (`@corbits/react-ui/ui/button`) or from the + root barrel, and bundle only what they used. +- Render collection surfaces against *their* data. Components never fetch. + Data arrives as props, or through a `DataPort` the host provides. Pieces + that take no collection need no port at all. +- Style with a single prebuilt sheet (`@corbits/react-ui/styles.css`) and no + Tailwind, or import `@corbits/react-ui/theme.css` into an existing Tailwind + v4 build. +- Choose dark mode by putting a `dark` class on an ancestor. The stylesheet + reads that class; the host owns switching. Mounting `ThemeProvider` is + optional — persistence and named presets, not a requirement to go dark. +- Mark their own React Server Components boundary. This package ships no + `"use client"` directives. + +## What it is not + +There is no CLI, no component generator, and no docs site. There is no +server, no database, and no default backend. `@tanstack/react-query` is +optional and only required if the host uses `createTanstackDataPort()`. + +The prebuilt stylesheet is not inert: it restyles the consuming page +(Tailwind preflight plus a base layer). Import it at the root of an app that +is willing to hand over those defaults, or use `theme.css` instead. + +Brand faces (Red Hat Display, Space Mono) are named by the theme and not +bundled. The stack falls through to system fonts unless the host loads them. + +## License + +LGPL-2.1-only. See [LICENSE](./LICENSE). + +The public surface is a semver commitment: every subpath in `exports` is +something we have promised not to break casually. Until 1.0 a minor bump may +still contain a breaking change; those are called out in [CHANGELOG.md](./CHANGELOG.md). diff --git a/README.md b/README.md index 3efbd902..20d25b02 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Import the prebuilt stylesheet once at the app root (no Tailwind build required) import "@corbits/react-ui/styles.css"; ``` -That sheet includes Tailwind preflight and a base layer — it restyles the page. If you already use Tailwind v4, import `@corbits/react-ui/theme.css` instead and let your own build generate utilities. Dark mode is a `dark` class on an ancestor; the library reads it and does not manage it. +That sheet includes Tailwind preflight and a base layer — it restyles the page. If you already use Tailwind v4, import `@corbits/react-ui/theme.css` instead and let your own build generate utilities. Dark mode is a `dark` class on an ancestor; the stylesheet reads it and does not manage it. Mount `ThemeProvider` only if you want the library to persist the choice and apply named presets. ```tsx import { Button } from "@corbits/react-ui/ui/button"; @@ -66,7 +66,7 @@ This package ships no `"use client"` directives. In a React Server Components ap Components take data through a `DataPort` (or none). The root entry is re-exports only and the package is side-effect free aside from the CSS files, so either import style tree-shakes to what you used. Tokens and keyframes live in `theme.css`; `styles.css` is the prebuilt sheet for hosts that do not run Tailwind. -See [ARCHITECTURE.md](./ARCHITECTURE.md) for the DataPort seam and the theme layer. +See [PRODUCT.md](./PRODUCT.md) for why this library exists, [ARCHITECTURE.md](./ARCHITECTURE.md) for the DataPort seam and the theme layer, and [IMPLEMENTATION.md](./IMPLEMENTATION.md) for toolchain and peers. ## Development