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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 52 additions & 0 deletions .changeset/4929-plugin-published-stylesheets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
---
'@object-ui/plugin-grid': patch
'@object-ui/plugin-kanban': patch
---

`@object-ui/plugin-grid` and `@object-ui/plugin-kanban` now publish a stylesheet —
`"./style.css"`, mapped to `dist/index.css` and compiled at build time from the package's
own sources (objectui#4929, maintainer ruling 2026-08-17, Direction 1).

**What was broken.** Only `@object-ui/components` and `@object-ui/fields` shipped CSS, and
each scans its own `src` only, so a class used exclusively by a plugin could not appear in
either sheet BY CONSTRUCTION. A published-state Vite app that installed one of these two
plugins and followed the quick-start rendered the grid or the board with **25 themed
utilities that had no source anywhere in the world** — `bg-muted/10`, `bg-card/60`,
`text-muted-foreground/60`, `ring-primary/40` and friends, ordinary appearance classes —
plus ~103 plain ones. Re-measured on the merged tree: the 21 the card listed all still hold,
and four more (`[&>h3]:text-foreground/80`, `border-l-primary/40`, `border-primary/30`,
`hover:text-primary`) that its literal-grep method could not see.

The plain utilities a consumer could in principle regenerate by pointing `@source` at the
package's `dist`. The themed ones they cannot, at all: they resolve `@theme` tokens declared
in `packages/components/src/index.css`, which that package does not publish. A build inside
this monorepo is their only possible producer — which is why the fix is a stylesheet we
ship, not documentation teaching consumers to hand-declare the theme and scan
`node_modules` (the advice objectui#4858 had just retired from the guides).

**The shape**, inherited from `@object-ui/fields` (objectui#4059): each package gains
`src/index.css` that `@reference`s the components entry — theme tokens, the class-based
`dark` variant and the animate plugin become available for resolution while emitting
nothing — plus `scripts/build-css.mjs`, which subtracts every rule components' published
sheet already ships. So these are **supplements, imported after** the components sheet, and
they are 16.30 kB and 11.41 kB rather than another ~170 kB each:

```css
@import 'tailwindcss';
@import '@object-ui/components/style.css';
@import '@object-ui/fields/style.css';
@import '@object-ui/plugin-grid/style.css';
@import '@object-ui/plugin-kanban/style.css';
```

Add a line only for the plugins you install; no other `@object-ui/plugin-*` package
publishes a stylesheet yet. The build step is shared
(`scripts/build-plugin-stylesheet.mjs`) so it is the pattern the next one inherits rather
than a file to copy, and it refuses to write a sheet that fails any of four assertions — no
rule may vanish, the subtraction must have removed something, the class count may not pass
a leak ceiling, and named themed utilities only this build can produce must still be
present.

Nothing is removed and no existing import changes: a consumer who does not import the new
sheets is exactly where they were, and the guides' "do not scan `node_modules`" advice
stays correct — it is now correct for plugins too.
17 changes: 17 additions & 0 deletions content/docs/guide/plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,23 @@ Kanban board component with drag-and-drop powered by @dnd-kit.

## How Plugins Work

### Stylesheets

A plugin's JavaScript is only half of what it renders with. `@object-ui/plugin-grid` and `@object-ui/plugin-kanban` publish a `style.css` of their own, and an app that installs one must import it after the base sheets:

```css
/* src/index.css */
@import "tailwindcss";
@import "@object-ui/components/style.css";
@import "@object-ui/fields/style.css";
@import "@object-ui/plugin-grid/style.css";
@import "@object-ui/plugin-kanban/style.css";
```

Each plugin sheet is compiled against the components theme and then has every rule that sheet already ships subtracted from it, so it carries only what the plugin adds. That includes the themed utilities (`bg-muted/10`, `bg-card/60`, `ring-primary/40`) which **no consumer-side configuration can produce** — the `@theme` block declaring their tokens lives in package source that is not published, so scanning `node_modules` cannot reach it ([#4929](https://github.com/objectstack-ai/objectui/issues/4929)). Skip the import and the view renders unstyled.

Add a line only for the plugins you install. The other `@object-ui/plugin-*` packages do not publish a stylesheet yet; the build step above is the pattern each of them will adopt when it needs one.

### Lazy Loading Architecture

Plugins use React's `lazy()` and `Suspense` to load heavy dependencies on-demand:
Expand Down
9 changes: 9 additions & 0 deletions content/docs/guide/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,15 @@ Each `style.css` is a stylesheet the package compiles from its own sources at bu

**Import them in that order.** `@object-ui/components/style.css` is the complete sheet: Tailwind's base layer, the `@theme` tokens and the utilities its components use. `@object-ui/fields/style.css` is a small supplement on top of it — only the ~155 utilities the field widgets add and the components sheet does not already carry, which is why it is a few kB rather than another 170. It is not a standalone stylesheet, and on its own it will not style anything.

**Plugin packages that publish a stylesheet need one line each.** `@object-ui/plugin-grid` and `@object-ui/plugin-kanban` ship the same kind of supplement, built the same way, so add whichever of them you install:

```css
@import "@object-ui/plugin-grid/style.css";
@import "@object-ui/plugin-kanban/style.css";
```

Without that line the plugin renders with no themed styling at all — its `bg-muted/10`, `bg-card/60` and `text-muted-foreground/60` have no other source in a published app, because the `@theme` block they resolve lives in package source that is never published ([#4929](https://github.com/objectstack-ai/objectui/issues/4929)). The remaining `@object-ui/plugin-*` packages ship no stylesheet yet; importing one that does not exist breaks the build, so add only the lines above.

That is the whole styling setup: you do not add `@source` lines for the ObjectUI packages, and pointing Tailwind at them inside `node_modules` only regenerates utilities these imports already gave you.

## Step 4: Render Your First Schema
Expand Down
9 changes: 9 additions & 0 deletions content/docs/guide/theming.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ Each `style.css` is a real export, mapped to that package's `dist/index.css` and

`@object-ui/fields/style.css` is a supplement, and the order matters: it is compiled against the components theme and then has every rule that sheet already ships subtracted from it, so it contains only the utilities the field widgets add — the tag colour map, the signature canvas cursor, the rating hover states, and 17 themed utilities such as `hover:bg-accent/30` and `ring-destructive/50` that no consumer-side configuration can generate, because the tokens they resolve live in unpublished package source. Import it before the components sheet, or alone, and those rules resolve against tokens that are not there yet.

`@object-ui/plugin-grid/style.css` and `@object-ui/plugin-kanban/style.css` are the same shape again, one per plugin you install:

```css
@import "@object-ui/plugin-grid/style.css";
@import "@object-ui/plugin-kanban/style.css";
```

Each is compiled against the components theme and then has that sheet's rules subtracted, which is why they are ~16 kB and ~11 kB rather than another 170 each. Between them they carry the 25 themed utilities the two plugins use and neither base sheet contains — `bg-muted/10`, `bg-card/60`, `ring-primary/40` and friends — which nothing on your side can generate ([#4929](https://github.com/objectstack-ai/objectui/issues/4929)). No other `@object-ui/plugin-*` package publishes a stylesheet yet.

Do **not** point Tailwind at the packages inside `node_modules` — neither with a v4 `@source` line nor a v3 `content` entry. Scanning the published files regenerates the shape-only utilities (`inline-flex`, `rounded-md`, `h-9`) the two sheets already contain, and it cannot produce the themed ones at all: the `@theme` block they come from lives in package source, which is not published. Your Tailwind entry goes on generating the classes *your* source uses, exactly as before.

To recolour ObjectUI, override the token values rather than the utilities — either the `:root` custom properties shown above, or a `Theme` object handed to `ThemeProvider` (see below). Both re-theme every component without any scanning.
Expand Down
9 changes: 8 additions & 1 deletion content/docs/guide/troubleshooting.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,14 @@ npx objectui doctor
@import '@object-ui/fields/style.css';
```

Two packages publish a `style.css`: `@object-ui/components` (the base sheet — theme tokens, base layer, its own utilities) and `@object-ui/fields` (a supplement carrying only what the field widgets add). The fields sheet is built by subtracting everything the components sheet already ships, so it must come **after** it; on its own it styles almost nothing.
`@object-ui/components` publishes the base sheet — theme tokens, base layer, its own utilities — and every other sheet is a supplement built by subtracting everything the base already ships, so each must come **after** it and none styles much on its own. `@object-ui/fields` carries what the field widgets add. `@object-ui/plugin-grid` and `@object-ui/plugin-kanban` carry what those two plugins add; add a line for each plugin package you install:

```css
@import '@object-ui/plugin-grid/style.css';
@import '@object-ui/plugin-kanban/style.css';
```

If a grid or a kanban board specifically looks wrong — column headers and card surfaces flat, drag feedback and selection rings missing — that plugin's sheet is the missing import. It did not exist before either: those packages emitted no CSS at all until [#4929](https://github.com/objectstack-ai/objectui/issues/4929), so on earlier versions the subpath does not resolve and upgrading is the fix, not a scanning path. The remaining `@object-ui/plugin-*` packages still publish no stylesheet.

If field widgets specifically look wrong — tag and badge colours flat, the rating stars not reacting to hover, the signature pad showing the wrong cursor — the fields import is the one that is missing. Note that it genuinely did not exist before: every release up to and including 17.3.0 declared the `@object-ui/fields/style.css` subpath while shipping no stylesheet at all ([#4059](https://github.com/objectstack-ai/objectui/issues/4059)), so on those versions the import fails to resolve and breaks the build. Upgrade rather than adding scanning paths.

Expand Down
10 changes: 10 additions & 0 deletions content/docs/plugins/plugin-grid.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ Advanced data grid with sorting, filtering, pagination, and row selection capabi
npm install @object-ui/plugin-grid
```

This package publishes a stylesheet. Import it after the base sheets, or the grid renders unstyled — the themed utilities it uses have no other source in a published app ([#4929](https://github.com/objectstack-ai/objectui/issues/4929)):

```css
/* src/index.css */
@import "tailwindcss";
@import "@object-ui/components/style.css";
@import "@object-ui/fields/style.css";
@import "@object-ui/plugin-grid/style.css";
```

<PluginLoader plugins={['grid']}>

## Interactive Examples
Expand Down
10 changes: 10 additions & 0 deletions content/docs/plugins/plugin-kanban.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,16 @@ Kanban board component with drag-and-drop powered by @dnd-kit.
npm install @object-ui/plugin-kanban
```

This package publishes a stylesheet. Import it after the base sheets, or the board renders unstyled — the themed utilities it uses have no other source in a published app ([#4929](https://github.com/objectstack-ai/objectui/issues/4929)):

```css
/* src/index.css */
@import "tailwindcss";
@import "@object-ui/components/style.css";
@import "@object-ui/fields/style.css";
@import "@object-ui/plugin-kanban/style.css";
```

<PluginLoader plugins={['kanban']}>

## Interactive Examples
Expand Down
13 changes: 13 additions & 0 deletions packages/plugin-grid/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,19 @@ Grid plugin for Object UI - Advanced data grid with sorting, filtering, and pagi
pnpm add @object-ui/plugin-grid
```

Then import the stylesheet this package publishes, after the base sheets. It is a
supplement — compiled against the `@object-ui/components` theme with that sheet's
rules subtracted — so the order matters, and without it the grid renders with no
themed styling at all ([#4929](https://github.com/objectstack-ai/objectui/issues/4929)):

```css
/* src/index.css */
@import "tailwindcss";
@import "@object-ui/components/style.css";
@import "@object-ui/fields/style.css";
@import "@object-ui/plugin-grid/style.css";
```

## Usage

### Registration is a side effect of the import
Expand Down
8 changes: 6 additions & 2 deletions packages/plugin-grid/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,11 @@
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.umd.cjs"
}
},
"./style.css": "./dist/index.css"
},
"scripts": {
"build": "vite build",
"build": "vite build && node scripts/build-css.mjs",
"test": "vitest run",
"type-check": "tsc --noEmit && tsc -p tsconfig.test.json",
"lint": "eslint ."
Expand All @@ -42,8 +43,11 @@
"@object-ui/data-objectstack": "workspace:*",
"@object-ui/sdui-parser": "workspace:*",
"@objectstack/spec": "^17.0.0",
"@tailwindcss/postcss": "^4.3.3",
"@vitejs/plugin-react": "^6.0.5",
"msw": "^2.15.0",
"postcss": "^8.5.26",
"tailwindcss": "^4.3.3",
"typescript": "^6.0.3",
"vite": "^8.2.1",
"vite-plugin-dts": "^5.0.3"
Expand Down
77 changes: 77 additions & 0 deletions packages/plugin-grid/scripts/build-css.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* Builds `dist/index.css` for `@object-ui/plugin-grid` — the CSS half of this
* package's `build` script, run after `vite build`.
*
* All of the reasoning lives in two places and neither is repeated here:
* `src/index.css`'s header explains the narrow `@reference` shape, and
* `scripts/build-plugin-stylesheet.mjs` at the repository root explains the
* subtraction and the assertions that guard it (objectui#4929). This file holds
* only what is specific to THIS package.
*/
import { dirname, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';

import postcss from 'postcss';
import tailwind from '@tailwindcss/postcss';

import { isEntrypoint } from '../../../scripts/invoked-as.mjs';
import { createPluginStylesheetBuilder } from '../../../scripts/build-plugin-stylesheet.mjs';

export const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
export const PACKAGE_NAME = '@object-ui/plugin-grid';

/**
* Utilities that MUST survive the subtraction, spanning both reasons a rule can
* be plugin-only.
*
* The first three resolve `@theme` tokens `@object-ui/components` declares but
* does not publish, so this build is the only producer they can ever have — if
* the subtraction over-reaches, these are what silently disappear, and no test
* that renders a grid in this repo would notice (every in-repo host compiles
* this package's source directly and never loads this sheet). The last three are
* plain utilities that simply are not in components' sheet.
*
* Deliberately a handful of named specimens, not a count: a threshold would have
* to be re-tuned every time a column renderer gains a class, and the edit that
* silences a real regression would look exactly like the edit that keeps it
* current.
*/
export const MUST_SURVIVE = [
'divide-border/50',
'focus:ring-destructive/30',
'text-muted-foreground/80',
'border-l-[3px]',
'bg-emerald-500/5',
'dark:bg-slate-950/40',
];

/**
* Leak ceiling, not a budget. Measured on this card: 126 classes survive the
* subtraction; a lost `source(none)` would put it in the thousands.
*/
export const CLASS_CEILING = 600;

export const builder = createPluginStylesheetBuilder({ postcss, tailwind });

export const buildOptions = {
packageRoot: PACKAGE_ROOT,
packageName: PACKAGE_NAME,
mustSurvive: MUST_SURVIVE,
classCeiling: CLASS_CEILING,
};

if (isEntrypoint(import.meta.url)) {
const { css, survivors, survivingClasses, droppedRules, droppedAtRules } =
await builder.build(buildOptions);
console.log(
`✓ built dist/index.css (${(css.length / 1024).toFixed(2)} kB) — ` +
`${survivors.size} rules kept (${survivingClasses.size} classes), ` +
`${droppedRules} rules + ${droppedAtRules} at-rules already in @object-ui/components' sheet`,
);
}
Loading
Loading