diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72d1831c..dc791ae7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,30 +1,31 @@ -name: CI +name: ci on: - push: - branches: [main] pull_request: + push: branches: [main] jobs: - verify: + check: runs-on: ubuntu-latest - strategy: - matrix: - node-version: [24] + timeout-minutes: 15 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} + node-version: 24 - uses: oven-sh/setup-bun@v2 - with: - bun-version: 1.4.0 - run: bun install --frozen-lockfile - - run: bun run typecheck - - run: bun run lint - - run: bun run format:check - - run: bun test + - run: bun run check - run: bun run build - run: bun run contrast-test - run: bun run dep-guard + - run: bun run test:e2e + - name: node consumer smoke + run: | + set -euo pipefail + TARBALL="$PWD/$(npm pack --silent)" + mkdir -p "$RUNNER_TEMP/c" && cd "$RUNNER_TEMP/c" + npm init -y >/dev/null && npm pkg set type=module >/dev/null + npm install "$TARBALL" + node -e 'import("@corbits/react-ui").then((m) => { for (const n of ["Button", "ChatThread", "Badge"]) if (typeof m[n] !== "function") throw new Error("missing export: " + n); })' diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml new file mode 100644 index 00000000..a03ad390 --- /dev/null +++ b/.github/workflows/cla.yml @@ -0,0 +1,53 @@ +name: CLA Assistant + +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened, synchronize] + +permissions: + actions: write + contents: write + pull-requests: write + statuses: write + +concurrency: + group: cla-${{ github.event.pull_request.number || github.event.issue.number || github.run_id }} + cancel-in-progress: true + +jobs: + cla: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - name: Maintainer fast path + if: >- + github.event_name == 'pull_request_target' && + (contains(fromJSON('["TheGreatAxios","brianjfox"]'), github.event.pull_request.user.login) || + endsWith(github.event.pull_request.user.login, '[bot]')) + run: echo "Maintainer or bot pull request; CLA not required." + - name: CLA Assistant + if: >- + ((github.event.comment.body == 'recreate-signatures' || + github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || + github.event_name == 'pull_request_target') && + !(github.event_name == 'pull_request_target' && + (contains(fromJSON('["TheGreatAxios","brianjfox"]'), github.event.pull_request.user.login) || + endsWith(github.event.pull_request.user.login, '[bot]'))) + # corbitsdev/cla-assistant-action v2.6.1-node24: upstream v2.6.1 on node24. + uses: corbitsdev/cla-assistant-action@ef6d3e51db8232fe93090810f13bde30497c1d74 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + path-to-document: "https://github.com/${{ github.repository }}/blob/main/CLA.md" + path-to-signatures: "signatures/version1/cla.json" + branch: "cla-signatures" + allowlist: TheGreatAxios,brianjfox,*[bot] + custom-notsigned-prcomment: >- + Thank you for your contribution. Before it can be merged, please read our + [Contributor License Agreement](https://github.com/${{ github.repository }}/blob/main/CLA.md) + and sign it by posting a new comment on this pull request containing + exactly the line below (nothing else): + custom-pr-sign-comment: "I have read the CLA Document and I hereby sign the CLA" + custom-allsigned-prcomment: "All contributors have signed the CLA." diff --git a/.gitignore b/.gitignore index e857b6b6..2d2a0bbf 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,9 @@ node_modules/ dist/ +*.tsbuildinfo *.tgz +coverage/ .env -.env.local -*.tsbuildinfo +.env.* .DS_Store -scratch/ build/ -.worktrees/ -.agent-state/ diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..f8f6ca8f --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,54 @@ +# AGENTS.md + +## Purpose + +`@corbits/react-ui` is the React component library for agent and workflow +surfaces: chat, runs, artifacts, charts, layout and controls, with a Tailwind v4 +theme. It owns the components, hooks, theme tokens and the prebuilt stylesheet. +It does not fetch data, talk to a backend, or import server-side packages; data +arrives as props. + +## Layout + +- `src/ui/`: components. +- `src/blocks/`: composed screens (login form, auth layout). +- `src/hooks/`: shared hooks (controllable state, popovers, list selection, + reduced motion). +- `src/test/`: test helpers. +- `src/lib/`: internal helpers; not exported unless listed in `src/index.ts`. +- `src/index.ts`: the hand-written root barrel. +- `src/theme.css`, `src/styles.css`: tokens and keyframes; the prebuilt sheet + entry. +- `stories/`: Ladle stories, kept out of `src/` so they never ship in `dist/`. +- `scripts/dep-guard.mjs`, `scripts/contrast-test.mjs`: the CI gates. +- `scripts/test-dom-setup.ts`: happy-dom preload for unit tests (`bunfig.toml`). +- `e2e/`: checks against the built package. + +## Rules + +- Sibling imports are relative and end in `.js`; there is no path alias. +- Public API goes in `src/index.ts`. A public module that statically imports an + optional peer stays out of the barrel and gets its own `exports` entry. + `dep-guard` fails if one reaches the barrel. +- No `@workbench/*` imports, and no server-side packages in a component. +- A `ui/` or `blocks/` module that calls a hook, creates a context, defines an + inline handler or imports an optional peer starts with `"use client"`; + stateless components and `hooks/`/`lib/` stay unmarked. `dep-guard` checks + both ways. +- A framework-level library the consumer already has is a peer; adding a peer is + a breaking change. +- Accessibility is a merge requirement: full keyboard operation, visible focus + that returns to the opener when an overlay closes, real elements over roles, + an accessible name on every control, colour never the only channel, and + `prefers-reduced-motion` respected. +- Generic names only (agent, workflow, artifact, ...); no product brands in a + module, prop, type or fixture. +- No `any` without a comment explaining why the type system leaves no + alternative. + +## Local development + +```sh +bun install +bun run check +``` diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md deleted file mode 100644 index 505be88b..00000000 --- a/ARCHITECTURE.md +++ /dev/null @@ -1,422 +0,0 @@ -# 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. 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.** Everything `src/index.ts` re-exports, and -every entry in `exports`, is something we have promised not to break casually. Both are -hand-written; a module not listed is internal, and keeping a module internal is the cheap -direction. Only modules that import an optional peer get their own subpath. `lib/chart-geometry` is internal: it is rendering machinery whose -names appear in no public prop type. `lib/chart-palette` is public — a consumer painting -its own data marks needs the same ramp, in the same order, under the same rules. - -**Tree-shaking is a requirement.** Output is per-file ESM — one `.js` and one `.d.ts` -per source file, no bundling — and `sideEffects` is declared CSS-only. The root entry is -re-exports only, so importing from the root bundles just what you used. - -**Abstraction has to earn its place.** No config indirection, no speculative extension -points. There is no CLI, no component generator and no docs site, and that is deliberate. - -## The build - -`bun run build` is three steps, joined by `&&`, in order: - -| Step | Tool | Output | -| ----------- | ---------------------------- | ----------------------------------------------------------- | -| `build:js` | `tsc -p tsconfig.build.json` | `dist/**/*.js` and `dist/**/*.d.ts`, one per source file | -| `build:css` | Tailwind v4 CLI | `dist/styles.css`, plus `dist/theme.css` copied from source | - -`tsc` emits the JavaScript and the declarations in one pass. Nothing bundles, so nothing -can merge two modules into one chunk. `prepack` runs the build, so npm publishes a fresh -`dist/`. The gates (`contrast-test`, `dep-guard`) run in CI after the build. - -Source imports are relative and carry `.js` extensions. There is no path alias, so the -emitted files are valid ESM for Node as well as for every bundler. - -Every component module that calls a hook, creates a context, defines an inline event -handler, or imports an optional peer (Radix dialog, dropdown-menu or tooltip, or -`sonner`) starts with `"use client"`, and `tsc` carries the directive into `dist/`. A React Server Components consumer renders any -component directly; stateless components stay server-renderable. Plain-React bundlers -ignore the directive. `dep-guard` enforces the rule, and checks `dist/` after a build. - -## No fetch, ever - -Components in this package **never fetch**, and never import a server-side package. A -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 as -props — the host owns its own data layer, caching and pagination. - -## `CommandPalette` - -`CommandPalette` is a global-search overlay with no idea what it is searching. It takes -`groups: CommandPaletteGroup[]` — pre-matched, pre-ordered, pre-paginated — plus `loading`, -`error` and `hasMore`/`onLoadMore`, and renders the states every such surface needs: -grouped results, loading, empty, error, "load more". Matching, ranking, debouncing and the -data source are entirely the caller's; a consumer wiring in server-searched entities keeps -that fetch, its debounce, and its pagination cursor in its own state, not in this -component. - -Only `item.title` and `item.subtitle` are ever rendered — never `item.id`. A consumer that -hands in a raw identifier as a title has nothing else to fall back on, but the component -itself never does that substitution for you. - -Keyboard state lives in `useCommandPaletteNavigation`, a separate headless hook: it tracks -the active row by id rather than index, because the row a person is on has usually just -been resorted by a fresh keystroke, and an index would silently point at a different item -than the one they were looking at. - -`ToolPicker` is the deliberate exception to caller-owns-matching. The palette searches an -unbounded, possibly server-backed space, so the caller owns the fetch, debounce and cursor; -the picker is a form control over a bounded, already-fetched catalog, and it needs the match -outcome itself to split "empty catalog" from "no matches." Self-filtering is scoped to that -case — anything unbounded still belongs to the caller. - -The dialog's fade is the existing `Dialog` opacity transition — no scale, no position — and -that transition already collapses under `prefers-reduced-motion` at the theme layer. A -palette a person opens dozens of times a day should not also compete with itself for -attention. - -## The theme layer - -`src/theme.css` is the only CSS source. It carries the design tokens **and** a base layer: -the default border color, the ground, the type, the reduced-motion opt-out, the component -keyframes, and one `:focus-visible` ring for the whole app. Primitives do not declare their -own focus ring. - -The build produces two artifacts from it, neither maintained by hand: - -- **`dist/styles.css`** — Tailwind compiles `src/styles.css` (two `@import` lines: - Tailwind, then the theme) into a standalone sheet. The consumer needs no Tailwind. -- **`dist/theme.css`** — a byte copy of `src/theme.css`, for a consumer who already runs - Tailwind v4 and wants the utilities generated by their own build. - -That second path costs one `@import` because the theme carries its own `@source "./"`. -Tailwind resolves `@source` relative to the file containing it, so it means `src/` when we -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 a `dark` class on an ancestor, with an OS fallback when no -class is set. The stylesheet reads `.dark` (`@custom-variant dark (&:is(.dark *))`) -and, under `@media (prefers-color-scheme: dark)`, applies the dark tokens to -`:root` when neither `.dark` nor `.light` is present — so a zero-JS host on a -dark OS first-paints dark, and removing `.dark` alone is not light. `.light` -is a `:root`-only sentinel that opts out of the media query; it carries no -token block, so it must live on `documentElement`. `dark:` utilities still -require a real `.dark` ancestor — the media path moves tokens only. -`ThemeProvider` is an optional helper that applies `.dark`/`.light` (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 - -`contrast-test` runs in CI after the build. It parses the **built** `dist/styles.css` -for the `:root` and `.dark` custom properties and derives its pairs from token _names_, -never from listed hexes, so a new `--warning` / `--warning-foreground` is covered on the -next run with no edit. Six rules: - -1. every `X-foreground` clears 4.5:1 on its own `X`; -2. `foreground` and `muted-foreground` clear 4.5:1 on every surface they can land on - (`background`, `card`, `popover`, `muted`); -3. `primary-emphasis` clears 4.5:1 on `background` and `card` — it is used as text and as - a border; -4. `input` and `ring` clear the 3:1 UI-component threshold on both surfaces; -5. every `chart-N` clears 3:1 against both surfaces; -6. a card is separable from the page by at least 1.2:1, satisfied by _either_ its fill or - its border. - -Both modes run every time, with dark treated as an override layer over light so a token -dark does not restate is still checked at its inherited value. - -Two token decisions follow from this and are worth knowing before you edit the theme. -Backgrounds are white in light and pure black in dark — fixing the ground first is what -makes everything downstream measurable. And a role that fails its measurement gets a new -token, never a nudged hex: `--primary` is the fill orange, `--primary-emphasis` the darker -one legal for rings, borders and text; `--border` is decorative while `--input` is the -control boundary that must clear 3:1. - -### The chart palette - -The five `--chart-*` tokens are a **fixed order, not a pool**. Slot 1 is always the first -series in the data. Colour carries identity, so filtering a series out never repaints the -survivors. - -Their steps and ordering — blue, orange, violet, green, red — were selected by running a -palette validator rather than by eye: each step sits inside its mode's lightness band, -clears the chroma floor, holds ≥3:1 against both the page and a card, and the order keeps -adjacent pairs separable under simulated protanopia and deuteranopia. Light mode clears the -ΔE 8 target (worst adjacent pair 10.5); dark mode lands at 7.2, which is legal **only** -alongside a second, non-colour channel. - -That is why `seriesDash()` exists next to `seriesColor()`, and why every chart ships a -legend, direct labels and a data table rather than treating them as polish: the palette is -compliant _because_ they are there. Past five series `seriesColor` returns the muted ink -rather than cycling or inventing a hue — use `foldSeries` so the label says "lumped -together" too. The contrast gate re-checks the 3:1 half on every build; the colour-vision -half cannot be re-derived from hex pairs, so if you re-step these tokens, re-run the -validator yourself. - -## Pieces, then one wrapper - -A composite surface — an inspector, a step rail, a chat thread — is built as small -stateless pieces plus at most one composition wrapper. Each piece takes everything -through props, holds no state of its own, and renders on its own (`LiveRunBanner`, -`StepList`, `InspectorKv`, `InspectorPanelTitle`, `SidebarPanelBody`). A wrapper -contains no markup the pieces do not already carry: it only decides which pieces -render and in what order, so replacing one piece never means forking the wrapper. - -Behaviour and arithmetic split out the same way: a reusable behaviour becomes a headless -hook in `src/hooks/` (`use-scroll-current-into-view` renders nothing and returns a ref), -and layout math becomes plain functions in `src/lib/` (`chart-geometry` is numbers in -and numbers out, checkable without rendering anything). - -## Layout - -| | | -| --------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `src/ui/` | Components. Primitives, collection surfaces, shells, and the domain families. | -| `src/lib/` | Non-component source: `utils`, the per-domain shapes, and the internal chart palette and geometry. | -| `src/hooks/` | 7 headless hooks: `use-command-palette-navigation`, `use-controllable-state`, `use-delayed-autofocus`, `use-dismissable-popover`, `use-list-selection`, `use-prefers-reduced-motion`, `use-scroll-current-into-view`. | -| `src/blocks/` | Multi-file compositions (`login`). | -| `src/theme.css` | Tokens, keyframes, base layer. The only CSS source. | -| `src/styles.css` | Two `@import`s. The entry Tailwind compiles into `dist/styles.css`. | -| `src/index.ts` | The root barrel. Hand-written: add a public module here. | -| `scripts/contrast-test.mjs` | The theme gate. Reads `dist/styles.css`. | -| `scripts/dep-guard.mjs` | The forbidden-import gates. | -| `dist/` | Build output. Generated, not committed; the only thing `files` publishes. | - -## Small-stateless-components audit - -A survey of `src/ui` (150 components) and `src/blocks` (3 block modules) -against five smells, done ahead of a fix pass (CL-5633). It is not a -line-by-line review of every file; it is a targeted grep-and-read over the -patterns below, cross-checked against what similar components in the -library already do. - -This is a historical record of that pass. Several components it names — -`TenantSelector`, `ThreadSwitcher`, `SubagentDock`, `OnboardingTour`, -`DitherBackground`, `SidebarRail`, `StepSidebar`, `LiveRunInspector` and -others — were removed later, when a consumer-usage audit pruned every module -no known consumer imports. The findings and the standing rules they produced -still apply to what remains. - -Every surveyed module is re-exported from the package root (`src/index.ts`), -so "public surface" doesn't distinguish findings the way it might in an app — -everything here is public. Blast radius instead counts how many other -non-test, non-story modules under `src/` import the component directly; it -is a proxy for "how many places a change here could be felt," not a measure -of downstream (workbench) usage, which this repo can't see. - -### (a) Internal state that should be controlled - -The convention already exists: `Sidebar`/`StepSidebar` take `collapsed` + -`onToggle`, `Dialog`/`CommandPalette` take `open` + `onOpenChange`. Both -families hold zero layout state internally — the parent owns it, full stop. -The components below didn't follow either shape; they held `open`/`expanded` -in `useState` with no way for a parent to read or drive it. - -| Component | Path | Blast radius | Status | -| -------------------------------------- | ------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------------------------------------------------- | -| `ToolNarrative` | `src/ui/tool-narrative.tsx` | 3 importers | **Fixed** — added optional `open`/`onOpenChange` | -| `ToolBlock` | `src/ui/tool-block.tsx` | 2 importers | **Fixed** — added optional `open`/`onOpenChange` | -| `NotificationsBell` | `src/ui/notifications-bell.tsx` | 1 importer | **Fixed** — added optional `open`/`onOpenChange` | -| `TenantSelector` | `src/ui/tenant-selector.tsx` | 1 importer | **Fixed** — added optional `open`/`onOpenChange` | -| `ThreadSwitcher` | `src/ui/thread-switcher.tsx` | 1 importer | **Fixed** — added optional `open`/`onOpenChange` | -| `ReasoningBlock` (was `ActivityBlock`) | `src/ui/reasoning-block.tsx` | 1 importer | **Fixed** — added optional `open`/`onOpenChange` over the native `
` | -| `SubagentDock` (per-row disclosure) | `src/ui/subagent-dock.tsx` | 1 importer | Deferred — lifting it means a parent tracking one open flag per subagent row (a map, not a boolean); a bigger surface change than this pass covers | - -All five fixes are additive: `open`/`onOpenChange` are optional, and the -component still manages its own state when they're omitted, so nothing that -calls these components today changes behavior. - -A few more components hold local `open`/`expanded`-shaped state and were -checked against the same bar, but don't belong on the fix list: -`ApprovalCard`'s per-detail "show more" is a text -truncation toggle, not a layout region — nobody outside the row has a reason -to know or set it. `ConfirmButton`'s `armed` state is already documented as -deliberately uncontrolled (`defaultArmed` only seeds the initial value). -`SidebarItemRow`'s `mounted` flag, `FileInput`'s `dragging` and -`LibrarySearchInput`'s `focused` are transient, self-contained UI state with -no parent-relevant meaning. `Command`'s `query`/`activeIndex` and -`TenantSelector`'s `activeIndex` are listbox/combobox navigation state, -which is what `use-command-palette-navigation` already exists to -encapsulate for the one case (`CommandPalette`) that needed it shared. - -### (b) Config-object props vs. children/slots - -Audited every array-of-object prop in `src/ui`/`src/blocks` -(`actor-summary`, `token-mosaic`, `intake-form`, `add-artifact-dialog`, -`kind-picker`, `toggle-list`, `filter-bar`, `workflow-catalog`, -`kind-card-grid`, `progress-checklist`, `step-list`, `horizontal-stepper`, -and others). **No findings.** Every one of these is a collection-rendering -component — a table row set, a step rail, a filter list — consistent with -the props-only data flow above, where components -render data the host doesn't have pre-built React elements for (it has -domain objects: a `WorkflowStep`, a `FilterSpec`). Slots are -used elsewhere in the library exactly where the content genuinely is -caller-arbitrary markup (`NotificationsBell`'s `children`, `AuthLayout`'s -`panel`). Swapping a config-array prop for children in a collection -component would mean the host hand-building list markup the component -exists to standardize — a regression, not a cleanup. - -### (c) Imperative logic that belongs in a hook - -| Finding | Components | Blast radius | Status | -| -------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- | -| Outside-click + Escape dismissal, hand-rolled three times | `NotificationsBell`, `TenantSelector`, `ThreadSwitcher` | 3 components, 3 importers combined | **Fixed** — extracted `useDismissablePopover` (`src/hooks/use-dismissable-popover.ts`) | -| `matchMedia("(prefers-reduced-motion: reduce)")`, read once and never re-checked, duplicated three times | `AnimatedNumber`, `DitherCanvas`, `use-scroll-current-into-view` | 3 components/hooks, 5 importers combined | **Fixed** — extracted `usePrefersReducedMotion` (`src/hooks/use-prefers-reduced-motion.ts`), now reactive via `useSyncExternalStore` | -| Spotlight measurement (`getBoundingClientRect`, `scrollIntoView`, resize/scroll listeners) | `OnboardingTour` | 1 importer | Deferred — tightly coupled to tour-step semantics (target selectors, centering a step with no target); low reuse value elsewhere today | - -`CommandPalette`'s keyboard/query navigation already lives in -`use-command-palette-navigation.ts` — this is the pattern the fixes above -extend, not a gap. `DitherBackground`'s pointer-driven canvas loop is -self-contained and has no sibling that needs the same behavior. - -### (d) Colors not sourced from theme tokens - -Grepped every `.tsx`/`.css` file under `src/ui`/`src/blocks` for hex/rgb/ -named-color literals and for Tailwind's fixed `text-white`/`bg-black`-style -utilities. - -`DitherCanvas`'s ink fallback (`src/ui/dither-canvas.tsx`) hardcoded -`#e98428` — the **light**-theme value of `--primary` — used unconditionally -as the fallback for the instant before the CSS custom property can be read. -`--primary` is `#bf6b20` in dark mode, so this fallback rendered the wrong -orange under dark. **Fixed**: it now falls back to the canvas's own computed -`color`, which the theme's base layer already ties to `--foreground`, for -whichever theme is active. - -Two other `bg-black/*` uses (`Dialog`'s overlay, `OnboardingTour`'s scrim) -were checked and are fine: a modal/spotlight overlay is conventionally black -at a fixed opacity in both themes — it's dimming the page behind it, not -carrying theme-tinted content, so there's no light/dark pair for it to be -inconsistent with. - -### (e) Missing `prefers-reduced-motion` support - -The theme's base layer already collapses every CSS `animation`/`transition` -duration to near-zero globally under `prefers-reduced-motion: reduce` (see -"The theme layer" above) — opt-out at the foundation, not per-component — so -the ~50 components using Tailwind's `transition-*`/`animate-*` utilities -inherit it for free and were not re-checked individually. - -The gap is JS-driven motion, which that CSS rule cannot reach: -`requestAnimationFrame` loops and imperative `scrollIntoView` calls. All -four such call sites were audited: - -| Component | Path | Before | Status | -| ------------------------------ | ------------------------------------------- | -------------------------------------------------------- | -------------------------------------------------------------------------------------------- | -| `AnimatedNumber` | `src/ui/animated-number.tsx` | One-shot `matchMedia` check per count-up | **Fixed** — now uses `usePrefersReducedMotion`, re-runs if the OS preference flips mid-count | -| `DitherCanvas` | `src/ui/dither-canvas.tsx` | One-shot `matchMedia` check per mount | **Fixed** — same | -| `use-scroll-current-into-view` | `src/hooks/use-scroll-current-into-view.ts` | One-shot `matchMedia` check per scroll | **Fixed** — same | -| `DitherBackground` | `src/ui/dither-background.tsx` | Already listens for `change` on its own `MediaQueryList` | Reviewed, no change — already the correct, live-reactive pattern | - -### Summary - -- **150 components surveyed** across `src/ui` and `src/blocks`. -- (a) 6 components reviewed as controlled-state candidates; 5 fixed, 1 - deferred, 7 more reviewed and confirmed as legitimately internal. -- (b) 12 config-object props reviewed; 0 findings. -- (c) 3 duplicated-imperative-logic findings; 2 fixed (covering 6 - components), 1 deferred. -- (d) 1 single-theme color-literal finding; fixed. -- (e) 4 JS-driven motion call sites audited; 3 fixed, 1 already correct. - -**8 components/hooks fixed in this pass**: `NotificationsBell`, -`TenantSelector`, `ThreadSwitcher`, `ToolBlock`, `ToolNarrative`, -`AnimatedNumber`, `DitherCanvas`, `use-scroll-current-into-view` — plus three -new shared hooks, `useDismissablePopover`, `usePrefersReducedMotion` and -`useControllableState` (the controlled/uncontrolled resolution the other two -and `ToolBlock`/`ToolNarrative` all share). - -**Deferred, with reasons**: `SubagentDock`'s per-row disclosure (needs -list-keyed controlled state — bigger surface change than this pass), and -`OnboardingTour`'s measurement logic (tour-specific, low reuse value as a -hook today). - -## Standing pattern - -The audit above isn't a one-off cleanup; the four rules below are the bar a -new component in this library is expected to clear. Each has exemplars -already in the tree. - -**State that describes layout or visibility is controlled, not owned.** If a -piece of state answers "is this open/collapsed/expanded" and a reasonable -host might want to read or drive it — closing every other panel when one -opens, restoring a collapsed sidebar from a saved preference — it's a prop -pair: `open` + `onOpenChange`, or `collapsed` + `onToggle`. `Sidebar` sets -this precedent; `Dialog` and `CommandPalette` apply it to popups; -`NotificationsBell`, `ToolBlock` and `ToolNarrative` do too, all -uncontrolled-by-default so adding the pair is never a breaking change. The -exception is state a parent has no legitimate reason to want: a -text-truncation toggle stays local. If -you're unsure which side of that line something is on, ask "would a real -caller ever need to read or set this from outside" — not "could they." - -The controlled-or-uncontrolled resolution itself is one hook, -`use-controllable-state`, not a ternary copy-pasted into every component that -needs it — `use-dismissable-popover`, `ToolBlock` and `ToolNarrative` all -call it rather than hand-rolling `value ?? internalState`. It also carries -the dev-mode warning if a caller switches between controlled and -uncontrolled after mount (the same thing React's own `` warns about), -so that check lives in one place too. - -**Arbitrary content is a slot; a domain collection is a data prop.** A -component whose job is to render caller-supplied content it has no opinion -about (`NotificationsBell`'s `children`, `AuthLayout`'s `panel`) takes -`children` or a named slot. A component whose job is to render a -_collection_ of the library's own domain shapes (a list of `WorkflowStep`, a -list of chat messages) takes that collection as a data prop — that's what -makes a table, a list and a step rail interchangeable consumers of one -loading/empty/error contract. Don't reach for a config-object prop as a -substitute for either of these; if you're tempted to add one, first check -whether the content is actually caller-arbitrary (make it `children`) or -actually a collection this library already has a shape for (use that -shape). - -**Non-trivial imperative behavior lives in a headless hook, not the -component body.** Event listeners, focus management, measurement, timers, -keyboard handling — if two components would otherwise duplicate the same -`useEffect`, that's the hook boundary. `use-command-palette-navigation`, -`use-dismissable-popover`, `use-prefers-reduced-motion` and -`use-controllable-state` are the existing examples: each owns exactly the -imperative part and returns refs/values, no markup. Before writing a second -`addEventListener` pair that looks like one already in the tree, grep -`src/hooks` first. - -**Every color comes from a token, with no unconditional literal fallback.** -`src/theme.css` is the only place a hex, rgb or named color belongs. A -component that reads a CSS custom property at runtime (`DitherCanvas` -reading `--primary` for a canvas fill, since canvas can't use `var()` -directly) may need a fallback for the instant before styles are attached — -that fallback must resolve through the DOM (`getComputedStyle(...).color`, -tied to `--foreground` by the base layer), never a hardcoded hex, because a -literal is by construction correct in at most one theme. - -**Motion outside CSS checks `prefers-reduced-motion`, live.** The theme's -global CSS rule handles every `transition-*`/`animate-*` utility for free — -don't re-implement that check for a component using only CSS transitions. -The rule doesn't reach `requestAnimationFrame` loops or imperative calls -like `scrollIntoView`; those call `usePrefersReducedMotion()` rather than a -one-shot `matchMedia(...).matches` read that goes stale the moment the OS -setting changes. - -## Known limits - -decision, not by oversight. - -- **Tests exist but are partial.** `bun test` runs 338 tests co-located with their - subjects (`src/**/*.test.tsx`), plus shared harnesses under `src/test/` — the - measurable half of appearance (contrast, in both modes) is gated in the build, - and a growing slice of behaviour is now covered by these, but most components - still have no test.- **A rendering gallery exists but is partial.** Ladle (`bun run stories`, `bun run -stories:build`) renders 42 stories under `stories/`; most of the ~90 components have - none, so visual review of an uncovered component still means rendering the package - inside a consumer app. -- **The prebuilt stylesheet restyles the consuming page** — it carries Tailwind's - preflight and a base layer. See the README before importing it. -- **The theme assumes Tailwind v4 CSS variables.** A consumer on a Tailwind - config-object setup has to translate them, or import `dist/styles.css` and ignore the - theme layer entirely. diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 58885c7f..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,160 +0,0 @@ -# Changelog - -All notable changes to `@corbits/react-ui` are documented here. The format follows -[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this package follows -[Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -Until 1.0, a minor bump may contain a breaking change; breaking changes are always called -out under their own heading. Every entry in the package's `exports` map is part of that -contract, and so is everything the root re-exports — modules that are not exported are internal and may change in any release. - -## [Unreleased] - -## [0.2.0] - 2026-09-25 - -### Breaking changes - -- **Git installs are no longer supported.** The `prepare` hook that built `dist/` on a - `github:corbitsdev/react-ui#` install is gone, so such an install ships no build - output. Install from npm: `npm add @corbits/react-ui`. -- **84 modules removed — every module no known consumer imports.** A usage audit across - the workbench, portal, scout and solutions-builder codebases showed these surfaces were - unreachable from any consumer, so they were deleted rather than carried as semver - promises. The removed clusters: the entire `DataPort` seam (`lib/data-port`, - `lib/tanstack-data-port`, `hooks/use-collection-state`) and its dependents - (`data-table`, `thread-list`, `command-queue`); the now-inbox surfaces (`mail-detail`, - `triage-pane`, `context-strip`, `activity-timeline`, `moment-walker`, `actor-summary`); - all scheduling (`schedule-*`, `run-once-flow`, - `recurrence-input`); the workflow registry (`workflow-registry-*`, `workflow-catalog`, - `workflow-dock`, `workflow-status-badge`); step-graph visualization (`step-graph`, - `step-graph-node`, `step-sidebar`, `step-primitives`, `lib/step-graph-layout`); - `subagent-dock`, `chat-dock` and `message-list`; the canvas/artifact-host surfaces - (`access-notice`, `canvas-host`, `canvas-host-chrome`, `generative-block-view`, - `artifact-detail`, `artifact-gallery`, `add-artifact-dialog`); and the unused - primitives and screens (`dial`, `pagination`, `radio-group`, `pulsing-ring`, - `dashboard`, `dashboard-section`, `whats-new`, `onboarding-tour`, - `reconnecting-overlay`, `stat-tile`, `delta-badge`, `count-table`, `heatmap`, - `kind-picker`, `toggle-list`, `managed-list`, `tenant-selector`, `thread-switcher`, - `section-nav`, `sidebar-rail`, `sidebar-section`, `read-block`, `render-rail`, - `failed-run-notice`, `live-run-header`, `live-run-inspector`, `time-range-control`, - `library-search-input`, `catalog-glyph`, `command`, `lib/command-registry`, - `dither-background`, and the hooks that only served them). `agent-turn` and - `activity-block` stayed — `chat-thread` uses them. The package is now 113 - public modules. **`@tanstack/react-query` is no longer a peer dependency** — - nothing in the package imports it. -- **`ActivityBlock` is renamed `ReasoningBlock`** (module `reasoning-block`), - aligned with `reasoningText()` naming. The prop `working` is now `streaming`, - and it gains `durationLabel` plus the optional `open`/`onOpenChange` - controlled pair while staying a native `
`. -- **`AuthLayout` no longer defaults its decorative panel to `DitherCanvas`.** The panel is - now an explicit `panel` slot with no fallback — pass `` or any other - content. A caller that omitted `panel` to get the dither canvas for free now gets a - bare panel instead. (`DitherBackground` was among the removed modules.) -- **`ui/command` is removed.** `Command` was a second, weaker inline command surface next - to `CommandPaletteInline` — index-based keyboard navigation (which loses the highlighted - row when the list re-sorts under a keystroke) and self-filtering, where the palette - family deliberately leaves matching to the caller. `commandMatches` is gone - with the component. The `./ui/command` subpath is gone. `lib/command-registry` - (and `CommandAction`) is also in the unconsumed prune above. -- **A classless root now follows `prefers-color-scheme`.** Under - `@media (prefers-color-scheme: dark)`, `:root` receives the dark tokens when neither - `.dark` nor `.light` is present — a host that toggled dark mode by adding and removing - `.dark` must now also add `.light` to `documentElement` for explicit light, or dark-OS - users will see dark anyway. `dark:` utilities still require a real `.dark` ancestor. - `ThemeProvider` writes `.light` for resolved-light mode, so hosts on the provider need - no change. - -- **Five helper modules are internal.** `lib/csv`, `lib/url`, `lib/utils` (`cn`), - `lib/workflow-registry` and `ui/chat-dock-timing` leave the root barrel and the - `exports` map, taking with them `cn`, `parseCsv` and the CSV caps, `isSafeUrl` / - `toSafeHref`, the `CHAT_DOCK_*` timings, and the workflow status and scope tables - (`WORKFLOW_STATUS_*`, `WORKFLOW_SCOPE_BADGE_TONE`, `LIVE_RUN_PHASE_*`, - `GATE_KIND_LABEL`, `workflowStatusLive`, `workflowScopeLabel`) with their types. - `StepListItem` and `StepDisplayStatus` stay public from `ui/step-list`; - `GateShellModel` and `GateKind` from `ui/gate-block`. For `cn`, compose `clsx` and - `tailwind-merge` directly: `twMerge(clsx(...inputs))`. -- **Per-module subpaths are removed; import from the root.** The `exports` map is now - `.`, the six optional-peer modules below, `styles.css`, `theme.css` and - `package.json`. `@corbits/react-ui/ui/button` and every other per-module subpath no - longer resolve: `import { Button } from "@corbits/react-ui"`. The root barrel is - hand-written and `scripts/generate-exports.mjs` is gone. -- **Four peers are optional and their modules are subpath-only.** `@radix-ui/react-dialog`, - `@radix-ui/react-dropdown-menu`, `@radix-ui/react-tooltip` and `sonner` move to - `peerDependenciesMeta`. `ui/dialog`, `ui/command-palette`, `ui/mic-permission-dialog`, - `ui/menu`, `ui/tooltip` and `ui/toast` leave the root barrel; import them by subpath - (`import { Toaster, toast } from "@corbits/react-ui/ui/toast"`). - -### Changed - -- **Build emits JavaScript with `tsc`.** SWC is gone; one `tsc` pass writes `dist/**/*.js` - and `.d.ts`. `contrast-test` and `dep-guard` run in CI instead of inside `build`. - -### Added - -- **`"use client"` on stateful components.** The 45 `ui/` and `blocks/` modules that call - a hook, create a context, define an inline event handler, or import a Radix dialog, - dropdown-menu or tooltip, or `sonner`, ship the directive, so a React Server Components app renders them without - its own client wrapper. - -- **`ui/shimmer-text` and `ui/thinking-label`** — a token-pure shimmer sweep - over text, and a rotating-verb status label built on it. Verb rotation is - width-stable (stacked grid, crossfade) and stops under reduced motion via - `usePrefersReducedMotion`. - -### Added - -- **Voice/dictation family** — `ui/mic-button` (permission-state mic toggle), - `ui/voice-waveform` (level bars), `ui/dictation-status-line` (composer status - row) and `ui/mic-permission-dialog`. Presentational only: permission state, - levels and copy arrive as props; no `getUserMedia`/`SpeechRecognition` here. - -## [0.1.0] - 2026-09-17 - -Nothing has been published before this, so the list below is what the surface consists of -rather than what changed. - -- **197 public modules** — 152 UI components, 5 block modules, 16 hooks and 24 `lib` modules, - importable by subpath (`@corbits/react-ui/ui/button`) or from the root. Output is - per-file ESM with matching declarations; the package is side-effect free apart from its - CSS, so either import style bundles only what you used. -- **A prebuilt stylesheet** — `@corbits/react-ui/styles.css` carries the tokens, the base - layer, the component keyframes and every utility the components use, so a consumer needs - no Tailwind and no build configuration. Consumers already on Tailwind v4 import - `@corbits/react-ui/theme.css` instead. Both are generated from one source, - `src/theme.css`. -- **The theme** — brand color, radius and type tokens for light and dark, plus a base - layer carrying the ground, the default border color, the type and one `:focus-visible` - ring. Dark mode is opt-in via a `dark` class on an ancestor; the host owns switching. -- **The `DataPort` seam** — components never fetch. `lib/data-port` declares the type, - `lib/tanstack-data-port` is the default implementation over TanStack Query, and any - object satisfying `DataPort` drops in with no component changes. Collections only. -- **`use-collection-state`** — collapses a collection into - `loading | error | empty | ready` and renders nothing, so a table, list, grid or step - rail branches on the same thing. -- **Component families** — base primitives (`button` `input` `card` `dialog` `table` - `tabs` `badge` `switch` `command` `toast` `skeleton`), collection surfaces (`data-table` - `sortable-table` `managed-list` `list-detail` `pagination`), shells (`page-shell` - `chat-shell` `sidebar` `top-bar` `dashboard`), and the chat, workflow, schedule, - artifact, analytics/chart, mail and activity families. -- **A validated chart palette** — five fixed-order series tokens, with `seriesDash` as the - mandatory second, non-colour channel and `foldSeries` past five series. -- **Gates** — `bun run build` ends in a WCAG contrast test over the built `dist/styles.css` - and `prepack` runs the build, so a failing theme cannot be published. `dep-guard` fails - on an import from a forbidden unpublished scope, on any import of - `@tanstack/react-query` outside the adapter, and on anything reachable from the root - barrel importing an optional peer. -- **Peer dependencies** — React 18 or 19, `react-dom`, `lucide-react` (0.545 or 1.x), - `sonner`, `@radix-ui/react-dialog`, `@radix-ui/react-dropdown-menu`, - `@radix-ui/react-slot` and `@radix-ui/react-tooltip`. - **`@tanstack/react-query` is optional** — needed only for the TanStack adapter, which is - why that adapter is importable by subpath but absent from the root entry. - -#### Notes - -- The package ships **no `"use client"` directives**. A React Server Components consumer - marks its own client boundary; see the README. -- `lib/chart-geometry` and `lib/chart-palette` are internal and deliberately not exported. - -[Unreleased]: https://github.com/corbitsdev/react-ui/compare/v0.2.0...HEAD -[0.2.0]: https://github.com/corbitsdev/react-ui/compare/v0.1.0...v0.2.0 -[0.1.0]: https://github.com/corbitsdev/react-ui/releases/tag/v0.1.0 diff --git a/CLA.md b/CLA.md new file mode 100644 index 00000000..201eec8b --- /dev/null +++ b/CLA.md @@ -0,0 +1,64 @@ +# Contributor License Agreement (CLA) + +Version 1.0 + +This Contributor License Agreement ("Agreement") is entered into between the contributor ("Contributor") and ABK Labs, Inc. ("Maintainer"). + +By submitting any Contribution to a project maintained by Maintainer, the Contributor agrees to the following terms. + +## 1. Definitions + +"Contribution" means any source code, object code, documentation, test cases, designs, specifications, bug fixes, enhancements, comments, pull requests, commits, issues containing code, or other materials intentionally submitted to a project maintained by Maintainer. + +## 2. Copyright Ownership + +The Contributor retains ownership of the Contributor's copyrights in the Contribution. + +No transfer of copyright ownership is required by this Agreement. + +## 3. License Grant to Maintainer + +The Contributor grants Maintainer a perpetual, worldwide, non-exclusive, irrevocable, royalty-free license to: + +- use, reproduce, modify, distribute, display, perform, and sublicense the Contribution; +- combine the Contribution with other software and works; +- distribute the Contribution under the project's current license; +- distribute the Contribution under future versions of the project's license; +- distribute the Contribution under alternative open-source, source-available, commercial, proprietary, or dual-license terms. + +## 4. License Grant to the Public + +The Contributor agrees that the Contribution may be distributed as part of the applicable project under the project's then-current license terms, including LGPL-2.1 or any successor license adopted by Maintainer. + +## 5. Contributor Representations + +The Contributor represents and warrants that: + +1. The Contributor created the Contribution or otherwise has sufficient rights to submit it. +2. The Contributor has the legal authority to grant the rights described in this Agreement. +3. To the best of the Contributor's knowledge, the Contribution does not knowingly infringe the intellectual property rights of any third party. +4. Any third-party material included in the Contribution has been properly disclosed and is compatible with the rights granted under this Agreement. + +## 6. Corporate Contributions + +If a Contribution is submitted on behalf of an employer or other legal entity, the person accepting this Agreement represents that they are authorized to bind that entity to this Agreement. + +## 7. No Obligation + +Maintainer is not obligated to use, distribute, maintain, support, or accept any Contribution. + +## 8. Disclaimer + +Except as expressly stated in this Agreement, the Contribution is provided "AS IS" without warranties or conditions of any kind. + +## 9. Electronic Acceptance + +The Contributor agrees that electronic acceptance of this Agreement, including acceptance through a website, source-control platform, click-through process, pull-request workflow, or similar mechanism, shall have the same force and effect as a handwritten signature. + +## 10. Governing Law + +This Agreement shall be governed by the laws of the State of California, excluding its conflict-of-law provisions. + +--- + +By submitting a Contribution, the Contributor acknowledges that they have read and agree to this Agreement. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 73b361e7..20fa485e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,199 +1,48 @@ # Contributing -Thanks for looking. This is an installed npm component library, published to the npm -registry and developed with [bun](https://bun.sh) as its package manager and task runner. -Read [ARCHITECTURE.md](./ARCHITECTURE.md) first — most of the rules below only make sense -once that is clear. - -## How it works - -Components never fetch; data arrives as props. The root entry is hand-written re-exports -only, and the package is side-effect free apart from its CSS, so either import style -tree-shakes. Tokens and keyframes live in `theme.css`; `styles.css` is the prebuilt sheet -for hosts that do not run Tailwind. [PRODUCT.md](./PRODUCT.md) covers why the library -exists and [IMPLEMENTATION.md](./IMPLEMENTATION.md) the toolchain and peers. - -## Getting set up +## Development ```sh bun install -bun run build -``` - -There is no database and no backend. If a change needs one, it belongs in the -consumer's data layer, not in a component — data arrives as props. - -## Gates - -```sh -bun run build # tsc (JS + types) → Tailwind -bun run typecheck # tsc --noEmit -bun run lint # oxlint -bun run format:check # oxfmt --check (`bun run format` rewrites) -bun run dep-guard # forbidden-import checks over src/ +bun run check ``` -All five must be clean before a change lands. There is no CI in this repository yet, so -running them locally is the only thing standing between a change and a release. -`typecheck` is not negotiable and `any` is not a way past it: the escapes that exist each -carry a comment explaining why the type system leaves no alternative, and a new one needs -the same. - -## Touching the theme - -The contrast gate is described in [ARCHITECTURE.md](./ARCHITECTURE.md#contrast-is-gated). -A failing pair prints as `FAIL :1 (>=) ` and exits non-zero. -Two things follow: +`bun run check` runs typecheck, lint, format check and unit tests. `bun run format` rewrites the tree. -- **Change tokens in `src/theme.css`, then run `bun run build`.** Editing `dist/` changes - nothing — it is generated output and is gitignored. -- **Do not add hex pairs to the test.** The pairs are derived from token _names_. If a - token you added is not covered, the fix is a naming rule in `checksFor`, not a - hardcoded pair. +Contributors sign the [CLA](CLA.md) on their first PR; the CLA bot explains how. -If you re-step a `--chart-*` token, re-run the palette validator for the lightness band, -chroma floor and colour-vision separation; those cannot be re-derived from hex pairs, and -the build gate does not cover them. Record the result in the change. +`bun run test:e2e` checks the built package: every `exports` target exists and the +root entry loads under Node with every optional peer blocked. Run `bun run build` +first. CI also runs `bun run contrast-test` (theme contrast floors, reads +`dist/styles.css`) and `bun run dep-guard` (forbidden imports, see `AGENTS.md`) +after the build. -## The dep-guard +## Theme and stories -`scripts/dep-guard.mjs` walks every `.ts` / `.tsx` under `src/` and enforces two rules. -The forbidden strings are named here deliberately — a guard whose forbidden string is a -secret is a guard nobody can comply with. +Change tokens in `src/theme.css`, then run `bun run build`; `dist/` is generated. +A failing contrast pair prints `FAIL :1 (>=) `. Fix it +with a naming rule in `checksFor`, never a hardcoded hex pair. -1. **No `@workbench/*`.** A published file that imports an unpublished scope is - uninstallable for everyone outside the project that defines it, and the failure lands - in the _consumer's_ install, where they cannot fix it. -2. **Nothing reachable from the root barrel imports an optional peer.** Re-exporting a - module that statically imports an optional peer makes that peer mandatory for every - root import, so such a module must be kept out of the barrel and given its own `exports` entry. +`bun run stories` starts the Ladle workbench over every component against +`src/theme.css`; `bun run stories:build` writes a static copy to `build/`. A new +component ships with a story for each meaningful state (loading, empty, error). -The same rule, not machine-checked, applies to server-side packages: a component must -never import one. They carry database drivers, and pulling one into a browser bundle is a -category error. Data reaches a component through props. - -Wanting to relax any of this is the signal to add a prop — never to import your way -around it. - -## Adding a component +## Commit messages -1. Write the file under `src/{ui,lib,hooks,blocks}/`. Imports of sibling modules are - **relative and carry a `.js` extension** (`../lib/utils.js`) — there is no path alias. -2. If it is public API, add `export * from ".//.js";` to `src/index.ts`. - Machinery stays unlisted: keeping something internal is cheap; taking an export back - after it ships is not. -3. If it is public but statically imports an _optional_ peer, leave it out of - `src/index.ts` and add a `.//` entry to `exports` in `package.json` instead. - `dep-guard` fails if it reaches the barrel. -4. If a `ui/` or `blocks/` module calls a hook, creates a context, defines an inline - event handler, or imports an optional peer, start it with `"use client"`; - `@radix-ui/react-slot` alone does not count. Leave stateless components and - `hooks/` / `lib/` modules unmarked. `dep-guard` fails either way you get it wrong. -5. If it needs a new package, decide deliberately: a framework-level library the consumer - already has is a **peer** dependency, a small implementation detail is a real - dependency. Adding a peer is a breaking change for existing consumers. -6. Run the four gates, then verify a clean install. +Commit subjects and PR titles follow [Conventional Commits](https://www.conventionalcommits.org): `feat`, `fix`, `refactor`, `test`, `docs`, `build`, `ci`, `perf`, and `chore(release): x.y.z` for releases. +Add `!` only for public API breaks: removed or renamed exports, changed signatures, newly required params. Peer and dependency range changes are `build(deps):` with no `!`. +Keep subjects imperative, lowercase after the colon, 72 characters or less, and free of ticket IDs. +Every PR links its issue with a `Closes ` line in the PR body. -## Verifying a clean install +## Releasing -A green build proves the package compiles. It does not prove it **installs and renders**, -and the failures that matter most — a dependency you have in this repo but did not -declare, a subpath missing from `exports`, a class the stylesheet does not carry — are -invisible until someone installs into a tree that does not already have them. +Releases are manual. On a clean, up-to-date `main`: ```sh -npm pack --dry-run # inspect the file list -npm pack # → corbits-react-ui-.tgz +npm version -m "chore(release): %s" +git push --follow-tags +gh release create "v$(node -p 'require("./package.json").version')" --generate-notes +npm publish ``` -`npm pack` is kept here as a verification command because it is the packer the registry -sees; bun is still the package manager for everything else. Delete the tarball when you -are done — it must not be committed. - -Install that tarball plus the peers into a throwaway consumer app, built from scratch each -time (a directory matching the gitignored `scratch/` pattern is a convenient home for it). -Check two consumers: plain React, and a server-components app using the components both -from a server component and from a `"use client"` file the consumer owns. Build the -consumer from empty every time — one that has accumulated packages from a previous run -will happily compile against a dependency you forgot to declare, which is the exact bug -this catches. - -Two things worth asserting while you are there: - -- **Tree-shaking.** Build one entry that imports a single component and one that imports - everything, and compare. A single component must not drag in the others. -- **The stylesheet.** Check that the classes the rendered markup emits actually have rules - in `dist/styles.css`. Tailwind extracts classes statically, so a utility assembled at - runtime is missing from the sheet and the component is silently unstyled. - -## The component workbench - -`bun run stories` starts [Ladle](https://ladle.dev) — a live, hot-reloading canvas over -every published component, running against `src/theme.css` so what you see is what a -consumer gets. `bun run stories:build` produces a static build of the same canvas for -anyone who wants to check it without running the dev server. - -Stories live under `stories/`, not next to their component in `src/`. SWC and `tsc` -compile all of `src/`, so a colocated `*.stories.tsx` would ship in `dist/`. Keeping them -in a separate top-level directory keeps `dist/` exactly the set of files that matter. - -The theme toggle in Ladle's own top bar switches the real `.dark` class from -`src/theme.css` — not a canvas-only colour swap — so a component's dark-mode tokens, -contrast and all, are what you're actually looking at. - -**A new component's story is part of what "done" means for it**, not a follow-up: at -least one story per meaningful state (loading, empty, error, wherever the component has -one), with fixture data that reads like something a person would actually see — a name, a -title, a sentence — never an id-shaped placeholder. A prop the workbench can't exercise is -a prop nobody has actually looked at rendered. - -## Accessibility - -These components end up in other people's products. Accessibility is a merge requirement, -not a follow-up. - -- **Keyboard.** Every interactive element is reachable and operable from the keyboard - alone — Tab and Shift-Tab in DOM order, Enter/Space to activate, Escape to dismiss an - overlay, arrow keys within a composite widget rather than a Tab stop per option. -- **Focus management.** Focus must always be somewhere sensible and always visible. An - overlay takes focus on open, traps it while modal, and **returns it to the element that - opened it** on close. `src/theme.css` ships one `:focus-visible` ring for the whole app; - primitives do not declare their own and must not suppress it. Never `outline: none` - without a replacement that measures at least 3:1. -- **`aria-*` and semantics.** Prefer a real element over a `div` with a role. Where a role - is needed, wire the states that go with it: `aria-expanded` on a disclosure, - `aria-current` on the active nav item, `aria-live` on a region that updates out of band, - `aria-busy` on a collection **from `isFetching`, not `isLoading`**. Every control has an - accessible name; an icon-only button needs `aria-label`. -- **Colour is never the only channel.** Anything encoded in colour is encoded a second way - too — a shape, an icon, a dash pattern, a label. Charts are the sharp case: the - palette's dark-mode separation is compliant only _because_ every chart ships a legend, - direct labels and a data table. -- Respect `prefers-reduced-motion` for anything that animates. - -Check your item in both modes and at 200% zoom before opening a change. - -## Naming and scope - -- **Generic names only.** Name for the job — agent, workflow, analytics, mail, schedule, - artifact, activity. No product-brand names in a module name, a prop, a type or a - fixture. A name in a published `exports` map is a name we cannot take back without a - major version. -- **The minimum that works.** No wrapper that only forwards props, no config indirection, - no speculative extension point. No CLI, no component generator, no docs site. -- Comments explain _why_. The code already says what. - -## Pull requests - -- Keep commits focused, and keep the diff to the change you are describing. -- Explain _why_ in the commit message; the code already says what. -- Run the gates locally and say so in the pull request: `dep-guard`, `typecheck`, `lint`, - and `build` including the contrast gate. Nothing runs them for you yet. -- Contributions are accepted under the repository's LGPL-2.1-only licence. - -## Commit messages - -Commit subjects and PR titles follow [Conventional Commits](https://www.conventionalcommits.org): `feat`, `fix`, `refactor`, `test`, `docs`, `build`, `ci`, `perf`, and `chore(release): x.y.z` for releases. -Add `!` only for public API breaks: removed or renamed exports, changed signatures, newly required params. Peer and dependency range changes are `build(deps):` with no `!`. -Keep subjects imperative, lowercase after the colon, 72 characters or less, and free of ticket IDs. -Every PR links its issue with a `Closes ` line in the PR body. +Bump minor only for breaking API changes; everything else is a patch. `prepack` builds `dist/` from the tagged commit. diff --git a/IMPLEMENTATION.md b/IMPLEMENTATION.md deleted file mode 100644 index 5f621f69..00000000 --- a/IMPLEMENTATION.md +++ /dev/null @@ -1,71 +0,0 @@ -# Implementation - -Concrete toolchain, peers, formats and publish layout. Design of the -no-fetch rule 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`), `@radix-ui/react-slot` (`^1.2.3`). -- Optional peers (`peerDependenciesMeta`), each imported only by subpath-only - modules kept out of the root barrel, each with its own `exports` entry: - `@radix-ui/react-dialog` (`^1.1.15`; `ui/dialog`, `ui/command-palette`, - `ui/mic-permission-dialog`), `@radix-ui/react-dropdown-menu` (`^2.1.16`; - `ui/menu`), `@radix-ui/react-tooltip` (`^1.2.8`; `ui/tooltip`), `sonner` - (`^2.0.7`; `ui/toast`). -- 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 three steps, joined by `&&`: - -| Step | Tool | Output | -| ----------- | ---------------------------- | ----------------------------------------------------------- | -| `build:js` | `tsc -p tsconfig.build.json` | `dist/**/*.js` and `dist/**/*.d.ts`, one per source file | -| `build:css` | Tailwind v4 CLI | `dist/styles.css`, plus `dist/theme.css` copied from source | - -Source imports are relative and carry `.js` extensions. There is no path -alias. `prepack` runs the build. CI runs `contrast-test` (reads `dist/styles.css`) -and `dep-guard` after it. - -Other scripts: `bun run typecheck`, `bun run lint` (oxlint), `bun run format` / `format:check` (oxfmt), -`bun run dep-guard` (`scripts/dep-guard.mjs` — fails if any file imports -`@workbench/*`), `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 *));` -plus a `@media (prefers-color-scheme: dark)` fallback that applies dark -tokens to `:root` when neither `.dark` nor `.light` is set. Removing `.dark` -alone is not light on a dark-OS host — `.light` on `documentElement` is the -explicit opt-out. `dark:` utilities still require a real `.dark` ancestor. -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`/`.light`, `data-theme`, and `color-scheme` on a root (default -`document.documentElement` — a scoped `root` cannot escape the OS-following -media query, which only inspects `:root`) 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. - -## Publish - -Registry install is the documented path (`npm` / `pnpm` / `yarn` / -`bun add @corbits/react-ui`). `publishConfig.access` is `public`. diff --git a/LICENSE b/LICENSE index f6683e74..c6487f4f 100644 --- a/LICENSE +++ b/LICENSE @@ -1,501 +1,176 @@ - GNU LESSER GENERAL PUBLIC LICENSE - Version 2.1, February 1999 - - Copyright (C) 1991, 1999 Free Software Foundation, Inc. - - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - -[This is the first released version of the Lesser GPL. It also counts - as the successor of the GNU Library Public License, version 2, hence - the version number 2.1.] - - Preamble - - The licenses for most software are designed to take away your -freedom to share and change it. By contrast, the GNU General Public -Licenses are intended to guarantee your freedom to share and change -free software--to make sure the software is free for all its users. - - This license, the Lesser General Public License, applies to some -specially designated software packages--typically libraries--of the -Free Software Foundation and other authors who decide to use it. You -can use it too, but we suggest you first think carefully about whether -this license or the ordinary General Public License is the better -strategy to use in any particular case, based on the explanations below. - - When we speak of free software, we are referring to freedom of use, -not price. Our General Public Licenses are designed to make sure that -you have the freedom to distribute copies of free software (and charge -for this service if you wish); that you receive source code or can get -it if you want it; that you can change the software and use pieces of -it in new free programs; and that you are informed that you can do -these things. - - To protect your rights, we need to make restrictions that forbid -distributors to deny you these rights or to ask you to surrender these -rights. These restrictions translate to certain responsibilities for -you if you distribute copies of the library or if you modify it. - - For example, if you distribute copies of the library, whether gratis -or for a fee, you must give the recipients all the rights that we gave -you. You must make sure that they, too, receive or can get the source -code. If you link other code with the library, you must provide -complete object files to the recipients, so that they can relink them -with the library after making changes to the library and recompiling -it. And you must show them these terms so they know their rights. - - We protect your rights with a two-step method: (1) we copyright the -library, and (2) we offer you this license, which gives you legal -permission to copy, distribute and/or modify the library. - - To protect each distributor, we want to make it very clear that -there is no warranty for the free library. Also, if the library is -modified by someone else and passed on, the recipients should know -that what they have is not the original version, so that the original -author's reputation will not be affected by problems that might be -introduced by others. - - Finally, software patents pose a constant threat to the existence of -any free program. We wish to make sure that a company cannot -effectively restrict the users of a free program by obtaining a -restrictive license from a patent holder. Therefore, we insist that -any patent license obtained for a version of the library must be -consistent with the full freedom of use specified in this license. - - Most GNU software, including some libraries, is covered by the -ordinary GNU General Public License. This license, the GNU Lesser -General Public License, applies to certain designated libraries, and -is quite different from the ordinary General Public License. We use -this license for certain libraries in order to permit linking those -libraries into non-free programs. - - When a program is linked with a library, whether statically or using -a shared library, the combination of the two is legally speaking a -combined work, a derivative of the original library. The ordinary -General Public License therefore permits such linking only if the -entire combination fits its criteria of freedom. The Lesser General -Public License permits more lax criteria for linking other code with -the library. - - We call this license the "Lesser" General Public License because it -does Less to protect the user's freedom than the ordinary General -Public License. It also provides other free software developers Less -of an advantage over competing non-free programs. These disadvantages -are the reason we use the ordinary General Public License for many -libraries. However, the Lesser license provides advantages in certain -special circumstances. - - For example, on rare occasions, there may be a special need to -encourage the widest possible use of a certain library, so that it becomes -a de-facto standard. To achieve this, non-free programs must be -allowed to use the library. A more frequent case is that a free -library does the same job as widely used non-free libraries. In this -case, there is little to gain by limiting the free library to free -software only, so we use the Lesser General Public License. - - In other cases, permission to use a particular library in non-free -programs enables a greater number of people to use a large body of -free software. For example, permission to use the GNU C Library in -non-free programs enables many more people to use the whole GNU -operating system, as well as its variant, the GNU/Linux operating -system. - - Although the Lesser General Public License is Less protective of the -users' freedom, it does ensure that the user of a program that is -linked with the Library has the freedom and the wherewithal to run -that program using a modified version of the Library. - - The precise terms and conditions for copying, distribution and -modification follow. Pay close attention to the difference between a -"work based on the library" and a "work that uses the library". The -former contains code derived from the library, whereas the latter must -be combined with the library in order to run. - - GNU LESSER GENERAL PUBLIC LICENSE - TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION - - 0. This License Agreement applies to any software library or other -program which contains a notice placed by the copyright holder or -other authorized party saying it may be distributed under the terms of -this Lesser General Public License (also called "this License"). -Each licensee is addressed as "you". - - A "library" means a collection of software functions and/or data -prepared so as to be conveniently linked with application programs -(which use some of those functions and data) to form executables. - - The "Library", below, refers to any such software library or work -which has been distributed under these terms. A "work based on the -Library" means either the Library or any derivative work under -copyright law: that is to say, a work containing the Library or a -portion of it, either verbatim or with modifications and/or translated -straightforwardly into another language. (Hereinafter, translation is -included without limitation in the term "modification".) - - "Source code" for a work means the preferred form of the work for -making modifications to it. For a library, complete source code means -all the source code for all modules it contains, plus any associated -interface definition files, plus the scripts used to control compilation -and installation of the library. - - Activities other than copying, distribution and modification are not -covered by this License; they are outside its scope. The act of -running a program using the Library is not restricted, and output from -such a program is covered only if its contents constitute a work based -on the Library (independent of the use of the Library in a tool for -writing it). Whether that is true depends on what the Library does -and what the program that uses the Library does. - - 1. You may copy and distribute verbatim copies of the Library's -complete source code as you receive it, in any medium, provided that -you conspicuously and appropriately publish on each copy an -appropriate copyright notice and disclaimer of warranty; keep intact -all the notices that refer to this License and to the absence of any -warranty; and distribute a copy of this License along with the -Library. - - You may charge a fee for the physical act of transferring a copy, -and you may at your option offer warranty protection in exchange for a -fee. - - 2. You may modify your copy or copies of the Library or any portion -of it, thus forming a work based on the Library, and copy and -distribute such modifications or work under the terms of Section 1 -above, provided that you also meet all of these conditions: - - a) The modified work must itself be a software library. - - b) You must cause the files modified to carry prominent notices - stating that you changed the files and the date of any change. - - c) You must cause the whole of the work to be licensed at no - charge to all third parties under the terms of this License. - - d) If a facility in the modified Library refers to a function or a - table of data to be supplied by an application program that uses - the facility, other than as an argument passed when the facility - is invoked, then you must make a good faith effort to ensure that, - in the event an application does not supply such function or - table, the facility still operates, and performs whatever part of - its purpose remains meaningful. - - (For example, a function in a library to compute square roots has - a purpose that is entirely well-defined independent of the - application. Therefore, Subsection 2d requires that any - application-supplied function or table used by this function must - be optional: if the application does not supply it, the square - root function must still compute square roots.) - -These requirements apply to the modified work as a whole. If -identifiable sections of that work are not derived from the Library, -and can be reasonably considered independent and separate works in -themselves, then this License, and its terms, do not apply to those -sections when you distribute them as separate works. But when you -distribute the same sections as part of a whole which is a work based -on the Library, the distribution of the whole must be on the terms of -this License, whose permissions for other licensees extend to the -entire whole, and thus to each and every part regardless of who wrote -it. - -Thus, it is not the intent of this section to claim rights or contest -your rights to work written entirely by you; rather, the intent is to -exercise the right to control the distribution of derivative or -collective works based on the Library. - -In addition, mere aggregation of another work not based on the Library -with the Library (or with a work based on the Library) on a volume of -a storage or distribution medium does not bring the other work under -the scope of this License. - - 3. You may opt to apply the terms of the ordinary GNU General Public -License instead of this License to a given copy of the Library. To do -this, you must alter all the notices that refer to this License, so -that they refer to the ordinary GNU General Public License, version 2, -instead of to this License. (If a newer version than version 2 of the -ordinary GNU General Public License has appeared, then you can specify -that version instead if you wish.) Do not make any other change in -these notices. - - Once this change is made in a given copy, it is irreversible for -that copy, so the ordinary GNU General Public License applies to all -subsequent copies and derivative works made from that copy. - - This option is useful when you wish to copy part of the code of -the Library into a program that is not a library. - - 4. You may copy and distribute the Library (or a portion or -derivative of it, under Section 2) in object code or executable form -under the terms of Sections 1 and 2 above provided that you accompany -it with the complete corresponding machine-readable source code, which -must be distributed under the terms of Sections 1 and 2 above on a -medium customarily used for software interchange. - - If distribution of object code is made by offering access to copy -from a designated place, then offering equivalent access to copy the -source code from the same place satisfies the requirement to -distribute the source code, even though third parties are not -compelled to copy the source along with the object code. - - 5. A program that contains no derivative of any portion of the -Library, but is designed to work with the Library by being compiled or -linked with it, is called a "work that uses the Library". Such a -work, in isolation, is not a derivative work of the Library, and -therefore falls outside the scope of this License. - - However, linking a "work that uses the Library" with the Library -creates an executable that is a derivative of the Library (because it -contains portions of the Library), rather than a "work that uses the -library". The executable is therefore covered by this License. -Section 6 states terms for distribution of such executables. - - When a "work that uses the Library" uses material from a header file -that is part of the Library, the object code for the work may be a -derivative work of the Library even though the source code is not. -Whether this is true is especially significant if the work can be -linked without the Library, or if the work is itself a library. The -threshold for this to be true is not precisely defined by law. - - If such an object file uses only numerical parameters, data -structure layouts and accessors, and small macros and small inline -functions (ten lines or less in length), then the use of the object -file is unrestricted, regardless of whether it is legally a derivative -work. (Executables containing this object code plus portions of the -Library will still fall under Section 6.) - - Otherwise, if the work is a derivative of the Library, you may -distribute the object code for the work under the terms of Section 6. -Any executables containing that work also fall under Section 6, -whether or not they are linked directly with the Library itself. - - 6. As an exception to the Sections above, you may also combine or -link a "work that uses the Library" with the Library to produce a -work containing portions of the Library, and distribute that work -under terms of your choice, provided that the terms permit -modification of the work for the customer's own use and reverse -engineering for debugging such modifications. - - You must give prominent notice with each copy of the work that the -Library is used in it and that the Library and its use are covered by -this License. You must supply a copy of this License. If the work -during execution displays copyright notices, you must include the -copyright notice for the Library among them, as well as a reference -directing the user to the copy of this License. Also, you must do one -of these things: - - a) Accompany the work with the complete corresponding - machine-readable source code for the Library including whatever - changes were used in the work (which must be distributed under - Sections 1 and 2 above); and, if the work is an executable linked - with the Library, with the complete machine-readable "work that - uses the Library", as object code and/or source code, so that the - user can modify the Library and then relink to produce a modified - executable containing the modified Library. (It is understood - that the user who changes the contents of definitions files in the - Library will not necessarily be able to recompile the application - to use the modified definitions.) - - b) Use a suitable shared library mechanism for linking with the - Library. A suitable mechanism is one that (1) uses at run time a - copy of the library already present on the user's computer system, - rather than copying library functions into the executable, and (2) - will operate properly with a modified version of the library, if - the user installs one, as long as the modified version is - interface-compatible with the version that the work was made with. - - c) Accompany the work with a written offer, valid for at - least three years, to give the same user the materials - specified in Subsection 6a, above, for a charge no more - than the cost of performing this distribution. - - d) If distribution of the work is made by offering access to copy - from a designated place, offer equivalent access to copy the above - specified materials from the same place. - - e) Verify that the user has already received a copy of these - materials or that you have already sent this user a copy. - - For an executable, the required form of the "work that uses the -Library" must include any data and utility programs needed for -reproducing the executable from it. However, as a special exception, -the materials to be distributed need not include anything that is -normally distributed (in either source or binary form) with the major -components (compiler, kernel, and so on) of the operating system on -which the executable runs, unless that component itself accompanies -the executable. - - It may happen that this requirement contradicts the license -restrictions of other proprietary libraries that do not normally -accompany the operating system. Such a contradiction means you cannot -use both them and the Library together in an executable that you -distribute. - - 7. You may place library facilities that are a work based on the -Library side-by-side in a single library together with other library -facilities not covered by this License, and distribute such a combined -library, provided that the separate distribution of the work based on -the Library and of the other library facilities is otherwise -permitted, and provided that you do these two things: - - a) Accompany the combined library with a copy of the same work - based on the Library, uncombined with any other library - facilities. This must be distributed under the terms of the - Sections above. - - b) Give prominent notice with the combined library of the fact - that part of it is a work based on the Library, and explaining - where to find the accompanying uncombined form of the same work. - - 8. You may not copy, modify, sublicense, link with, or distribute -the Library except as expressly provided under this License. Any -attempt otherwise to copy, modify, sublicense, link with, or -distribute the Library is void, and will automatically terminate your -rights under this License. However, parties who have received copies, -or rights, from you under this License will not have their licenses -terminated so long as such parties remain in full compliance. - - 9. You are not required to accept this License, since you have not -signed it. However, nothing else grants you permission to modify or -distribute the Library or its derivative works. These actions are -prohibited by law if you do not accept this License. Therefore, by -modifying or distributing the Library (or any work based on the -Library), you indicate your acceptance of this License to do so, and -all its terms and conditions for copying, distributing or modifying -the Library or works based on it. - - 10. Each time you redistribute the Library (or any work based on the -Library), the recipient automatically receives a license from the -original licensor to copy, distribute, link with or modify the Library -subject to these terms and conditions. You may not impose any further -restrictions on the recipients' exercise of the rights granted herein. -You are not responsible for enforcing compliance by third parties with -this License. - - 11. If, as a consequence of a court judgment or allegation of patent -infringement or for any other reason (not limited to patent issues), -conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot -distribute so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you -may not distribute the Library at all. For example, if a patent -license would not permit royalty-free redistribution of the Library by -all those who receive copies directly or indirectly through you, then -the only way you could satisfy both it and this License would be to -refrain entirely from distribution of the Library. - -If any portion of this section is held invalid or unenforceable under any -particular circumstance, the balance of the section is intended to apply, -and the section as a whole is intended to apply in other circumstances. - -It is not the purpose of this section to induce you to infringe any -patents or other property right claims or to contest validity of any -such claims; this section has the sole purpose of protecting the -integrity of the free software distribution system which is -implemented by public license practices. Many people have made -generous contributions to the wide range of software distributed -through that system in reliance on consistent application of that -system; it is up to the author/donor to decide if he or she is willing -to distribute software through any other system and a licensee cannot -impose that choice. - -This section is intended to make thoroughly clear what is believed to -be a consequence of the rest of this License. - - 12. If the distribution and/or use of the Library is restricted in -certain countries either by patents or by copyrighted interfaces, the -original copyright holder who places the Library under this License may add -an explicit geographical distribution limitation excluding those countries, -so that distribution is permitted only in or among countries not thus -excluded. In such case, this License incorporates the limitation as if -written in the body of this License. - - 13. The Free Software Foundation may publish revised and/or new -versions of the Lesser General Public License from time to time. -Such new versions will be similar in spirit to the present version, -but may differ in detail to address new problems or concerns. - -Each version is given a distinguishing version number. If the Library -specifies a version number of this License which applies to it and -"any later version", you have the option of following the terms and -conditions either of that version or of any later version published by -the Free Software Foundation. If the Library does not specify a -license version number, you may choose any version ever published by -the Free Software Foundation. - - 14. If you wish to incorporate parts of the Library into other free -programs whose distribution conditions are incompatible with these, -write to the author to ask for permission. For software which is -copyrighted by the Free Software Foundation, write to the Free -Software Foundation; we sometimes make exceptions for this. Our -decision will be guided by the two goals of preserving the free status -of all derivatives of our free software and of promoting the sharing -and reuse of software generally. - - NO WARRANTY - - 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO -WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. -EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR -OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY -KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE -IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE -LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME -THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN -WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY -AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU -FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR -CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE -LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING -RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A -FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF -SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH -DAMAGES. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Libraries - - If you develop a new library, and you want it to be of the greatest -possible use to the public, we recommend making it free software that -everyone can redistribute and change. You can do so by permitting -redistribution under these terms (or, alternatively, under the terms of the -ordinary General Public License). - - To apply these terms, attach the following notices to the library. It is -safest to attach them to the start of each source file to most effectively -convey the exclusion of warranty; and each file should have at least the -"copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This library is free software; you can redistribute it and/or - modify it under the terms of the GNU Lesser General Public - License as published by the Free Software Foundation; either - version 2.1 of the License, or (at your option) any later version. - - This library is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public - License along with this library; if not, see . - -Also add information on how to contact you by electronic and paper mail. - -You should also get your employer (if you work as a programmer) or your -school, if any, to sign a "copyright disclaimer" for the library, if -necessary. Here is a sample; alter the names: - - Yoyodyne, Inc., hereby disclaims all copyright interest in the - library `Frob' (a library for tweaking knobs) written by James Random Hacker. - - , 1 April 1990 - Moe Ghoul, President of Vice +GNU LESSER GENERAL PUBLIC LICENSE +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts as the successor of the GNU Library Public License, version 2, hence the version number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. + +We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. + +GNU LESSER GENERAL PUBLIC LICENSE +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. + +(For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. + +When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: + + a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. + + e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. + + b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. + +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). + +To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + + one line to give the library's name and an idea of what it does. + Copyright (C) year name of author + + This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in +the library `Frob' (a library for tweaking knobs) written +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 +Ty Coon, President of Vice That's all there is to it! diff --git a/PRODUCT.md b/PRODUCT.md deleted file mode 100644 index 3e269720..00000000 --- a/PRODUCT.md +++ /dev/null @@ -1,55 +0,0 @@ -# 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 from the root (`@corbits/react-ui`) and bundle only what they used. -- Render collection surfaces against _their_ data. Components never fetch. - Data arrives as props. Pieces that take no collection need no extra - host wiring. -- 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 — or nothing at - all: with no class set, the stylesheet follows `prefers-color-scheme`. - `light` on `documentElement` is the explicit light opt-out. 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. - -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: everything the root re-exports, and every entry 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 930c6c0d..b3204ea7 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ This renders the user message, a collapsed reasoning block, a finished tool call - **Corbits** is the set of published packages around Interchange that turn an agent into a product. This package is the UI bucket: it runs in the host web app and plugs into the host's data layer, not into the hub or the sidecar. - It pairs with the hub modules whose data it renders, such as [`@corbits/artifacts`](https://github.com/corbitsdev/corbits-artifacts), [`@corbits/mailbox`](https://github.com/corbitsdev/corbits-mailbox) and [`@corbits/cron`](https://github.com/corbitsdev/corbits-cron). -## Usage +## Reference Import from the root; it tree-shakes to what you use. Only the optional-peer modules have their own subpath: @@ -262,7 +262,6 @@ The host app owns the connection to the hub; components only take props. - Stateful components ship `"use client"`. Client wrappers you wrote for them still work, and can go. - 84 unused modules are removed, `ui/command` is removed, `ActivityBlock` is renamed `ReasoningBlock`, and `AuthLayout` no longer renders `DitherCanvas` by default. - With neither `.dark` nor `.light` on the root, the theme follows the OS. Hosts that toggle `.dark` must add `.light` for an explicit light choice, or mount `ThemeProvider`. -- The full list is in the [changelog](https://github.com/corbitsdev/react-ui/blob/main/CHANGELOG.md). ## License diff --git a/SECURITY.md b/SECURITY.md deleted file mode 100644 index 2927717b..00000000 --- a/SECURITY.md +++ /dev/null @@ -1,62 +0,0 @@ -# Security policy - -## Reporting a vulnerability - -Please do **not** open a public issue for a security problem. - -Report it privately through GitHub's private vulnerability reporting on this -repository: **Security → Report a vulnerability**. That opens a channel visible -only to the maintainers. - -Useful things to include: which component and which package version, what an -attacker can do, and the smallest reproduction you can manage. If you have a -suggested fix, say so — but a clear report without one is still worth sending. - -We aim to acknowledge a report within a few working days, and to keep you posted -while it is being worked on. Please give us a reasonable window to ship a fix -before disclosing publicly. - -## Supported versions - -This package has not reached a stable release. Fixes land on `main` and ship in the -next release from the latest version; there are no maintained release branches, and -patches are not backported to earlier 0.x versions. - -## What is in scope - -Defects in the components this package ships: - -- a component that renders untrusted input in a way that executes it — - `dangerouslySetInnerHTML` on caller-supplied content, an `href` that accepts a - `javascript:` URL, a `src` or `srcDoc` an attacker controls; -- a component that leaks data across a boundary the API establishes — a stale - `DataPort` cache key that lets one tenant's rows render under another's request, - or an error surface that renders a raw server response including tokens or - internal identifiers; -- a component that defeats a security affordance a consumer would reasonably assume: - an overlay that does not trap focus while modal, a confirm step that can be - bypassed, `rel="noopener"` missing from a `target="_blank"` link; -- credentials, tokens, internal hostnames or real data in a shipped file or a - fixture; -- a declared dependency the package does not actually need, an undeclared one it - does, or anything that runs at install time — the package has no install scripts, - and adding one would be a supply-chain change. - -## What is not - -- **Authentication and authorization.** This package ships no auth. The `login` and - `access-notice` blocks are _presentation_: they render a form and a message. What - a submission does, whether a session is valid, and who may see a route are the - host's, and a host that trusts a client-side check owns that decision. -- **Fetching, caching and transport.** Components never fetch. A `DataPort` - implementation is the consumer's wiring, including the `request` objects handed to - `createTanstackDataPort()`. A port that sends a request without - credentials scoping, over plain HTTP, or with a cache key that ignores the tenant - is theirs to fix. -- **Sanitisation of data you supply.** A component that renders a string renders the - string you give it. Sanitise before it reaches the component. -- **A patched or vendored copy.** A vulnerability introduced by an edit to the - installed files is not a defect here, though we would still like to hear if our - structure invited it. -- Vulnerabilities in dependencies should be reported upstream, though we are glad to - hear about them. diff --git a/package.json b/package.json index 292d943a..5d20af7a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,17 @@ "name": "@corbits/react-ui", "version": "0.2.0", "description": "React components for agent and workflow surfaces.", - "homepage": "https://github.com/corbitsdev/react-ui", + "keywords": [ + "agents", + "components", + "corbits", + "interchange", + "react", + "tailwind", + "ui", + "workflows" + ], + "homepage": "https://github.com/corbitsdev/react-ui#readme", "bugs": { "url": "https://github.com/corbitsdev/react-ui/issues" }, @@ -13,7 +23,9 @@ "url": "git+https://github.com/corbitsdev/react-ui.git" }, "files": [ - "dist" + "dist", + "README.md", + "LICENSE" ], "type": "module", "sideEffects": [ @@ -56,20 +68,20 @@ "access": "public" }, "scripts": { - "build": "bun run clean && bun run build:js && bun run build:css", - "clean": "rm -rf dist", - "build:js": "tsc -p tsconfig.build.json", - "build:css": "tailwindcss -i src/styles.css -o dist/styles.css && cp src/theme.css dist/theme.css", + "build": "rm -rf dist && tsc -p tsconfig.build.json && bun run build:css", + "prepack": "bun run build", "typecheck": "tsc --noEmit", - "test": "bun test", "lint": "oxlint", "format": "oxfmt", "format:check": "oxfmt --check", + "test": "bun test src --pass-with-no-tests", + "test:e2e": "bun test e2e", + "check": "bun run typecheck && bun run lint && bun run format:check && bun run test", + "build:css": "tailwindcss -i src/styles.css -o dist/styles.css && cp src/theme.css dist/theme.css", "dep-guard": "node scripts/dep-guard.mjs", "contrast-test": "node scripts/contrast-test.mjs", "stories": "ladle serve --host 127.0.0.1", - "stories:build": "ladle build", - "prepack": "bun run build" + "stories:build": "ladle build" }, "dependencies": { "class-variance-authority": "^0.7.1", diff --git a/src/hooks/use-controllable-state.ts b/src/hooks/use-controllable-state.ts index c21330df..725fd65f 100644 --- a/src/hooks/use-controllable-state.ts +++ b/src/hooks/use-controllable-state.ts @@ -4,10 +4,10 @@ export type ControllableStateAction = T | ((previous: T) => T); export type UseControllableStateOptions = { /** Controlled value. Supply this with `onChange` to lift state to a parent. */ - readonly value?: T; + readonly value?: T | undefined; /** Seeds the hook's own state when `value` is not supplied. */ readonly defaultValue: T; - readonly onChange?: (value: T) => void; + readonly onChange?: ((value: T) => void) | undefined; /** Identifies the caller in the dev-mode controlled/uncontrolled warning below. */ readonly name: string; }; diff --git a/src/hooks/use-dismissable-popover.ts b/src/hooks/use-dismissable-popover.ts index 633ec513..4fa40a77 100644 --- a/src/hooks/use-dismissable-popover.ts +++ b/src/hooks/use-dismissable-popover.ts @@ -7,8 +7,8 @@ import { export type UseDismissablePopoverOptions = { /** Controlled open flag. Supply this with `onOpenChange` to lift state to a parent. */ - readonly open?: boolean; - readonly onOpenChange?: (open: boolean) => void; + readonly open?: boolean | undefined; + readonly onOpenChange?: ((open: boolean) => void) | undefined; /** Seeds the hook's own state when `open` is not supplied. */ readonly defaultOpen?: boolean; /** Escape closes the popover and returns focus to the trigger. Default `true`. */ diff --git a/src/ui/agent-turn.tsx b/src/ui/agent-turn.tsx index c47dd88c..9d60cdf3 100644 --- a/src/ui/agent-turn.tsx +++ b/src/ui/agent-turn.tsx @@ -19,7 +19,7 @@ export type AgentTurnProps = { readonly identity: AgentIdentity; /** Rich body for the answer — a markdown renderer. */ readonly children?: ReactNode; - readonly now?: number; + readonly now?: number | undefined; /** e.g. "Thought for 4s". Shown on the reasoning block once it stops streaming. */ readonly reasoningDurationLabel?: string; readonly className?: string; diff --git a/src/ui/artifact-notice.tsx b/src/ui/artifact-notice.tsx index 3f121165..cf202191 100644 --- a/src/ui/artifact-notice.tsx +++ b/src/ui/artifact-notice.tsx @@ -21,7 +21,7 @@ export function ArtifactNotice({ /** Overrides the pending/empty wording — use for a specific failure. */ message, }: { - readonly pending?: boolean; + readonly pending?: boolean | undefined; readonly message?: string; }) { if (message !== undefined) { diff --git a/src/ui/chart-frame.tsx b/src/ui/chart-frame.tsx index 9385cee4..377db4cd 100644 --- a/src/ui/chart-frame.tsx +++ b/src/ui/chart-frame.tsx @@ -42,7 +42,7 @@ export type ChartFrameProps = { readonly table: ChartTable; /** The plot itself — an ``. */ readonly children: ReactNode; - readonly className?: string; + readonly className?: string | undefined; }; /** diff --git a/src/ui/dialog.tsx b/src/ui/dialog.tsx index 538c4cfa..2ed42c43 100644 --- a/src/ui/dialog.tsx +++ b/src/ui/dialog.tsx @@ -54,7 +54,7 @@ export function DialogContent({ side = "center", ...props }: React.ComponentProps & { - side?: DialogSide; + side?: DialogSide | undefined; }) { return ( diff --git a/src/ui/message-bubble.tsx b/src/ui/message-bubble.tsx index 02d0c136..11f9995e 100644 --- a/src/ui/message-bubble.tsx +++ b/src/ui/message-bubble.tsx @@ -8,8 +8,8 @@ import { cn } from "../lib/utils.js"; export type MessageBubbleProps = { readonly message: ChatMessage; /** Retry affordance for a failed send. Omit and a failure is only reported. */ - readonly onRetry?: () => void; - readonly now?: number; + readonly onRetry?: (() => void) | undefined; + readonly now?: number | undefined; /** Rich body — a markdown renderer. Defaults to the message's plain text. */ readonly children?: ReactNode; readonly className?: string; diff --git a/src/ui/parts-renderer.tsx b/src/ui/parts-renderer.tsx index fd40205f..36adf7d4 100644 --- a/src/ui/parts-renderer.tsx +++ b/src/ui/parts-renderer.tsx @@ -76,7 +76,7 @@ function ReasoningPartView({ durationMs, }: { text: string; - durationMs?: number; + durationMs?: number | undefined; }) { const [open, setOpen] = useState(false); if (text.trim().length === 0) return null; @@ -132,7 +132,7 @@ function FilePartView({ }: { name: string; mediaType: string; - url?: string; + url?: string | undefined; }) { const content = ( <> diff --git a/src/ui/provider-mark.tsx b/src/ui/provider-mark.tsx index 13bffece..c0055bc5 100644 --- a/src/ui/provider-mark.tsx +++ b/src/ui/provider-mark.tsx @@ -17,7 +17,7 @@ export type ProviderMarkProps = { */ readonly logo?: ReactNode; readonly size?: "sm" | "md"; - readonly className?: string; + readonly className?: string | undefined; }; const SIZE_CLASS = { diff --git a/src/ui/reasoning-block.tsx b/src/ui/reasoning-block.tsx index 17ebada4..c19ee730 100644 --- a/src/ui/reasoning-block.tsx +++ b/src/ui/reasoning-block.tsx @@ -15,7 +15,7 @@ export type ReasoningBlockProps = { /** Summary once idle when no `durationLabel` is supplied. Defaults to "Thinking". */ readonly idleLabel?: string; /** e.g. "Thought for 4s". Becomes the summary once reasoning has stopped streaming. */ - readonly durationLabel?: string; + readonly durationLabel?: string | undefined; readonly defaultOpen?: boolean; /** * Controlled open state. Pair with `onOpenChange` to lift it to a parent — diff --git a/src/ui/sidebar-item-row.tsx b/src/ui/sidebar-item-row.tsx index 0be312a2..80f87af0 100644 --- a/src/ui/sidebar-item-row.tsx +++ b/src/ui/sidebar-item-row.tsx @@ -20,7 +20,7 @@ export type SidebarItemRowProps = { * one more frame (~150ms) so the leave transition gets to play. List * membership is the consumer's data — nothing here manages it for you. */ - readonly leaving?: boolean; + readonly leaving?: boolean | undefined; readonly onSelect?: () => void; readonly className?: string; }; diff --git a/src/ui/tooltip.tsx b/src/ui/tooltip.tsx index 58971203..ca95cb56 100644 --- a/src/ui/tooltip.tsx +++ b/src/ui/tooltip.tsx @@ -34,7 +34,7 @@ export function InfoTooltip({ }: InfoTooltipProps) { return ( - +