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
12 changes: 12 additions & 0 deletions .changeset/package-build-compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
---
"@pretable/core": minor
"@pretable/react": minor
"@pretable/stream-adapter": minor
"@pretable/ui": minor
---

Modernize the public package build architecture and support both React 18 and
React 19. All public packages retain first-class ESM and CommonJS package-name
imports, with an explicit ES2018 syntax and runtime API compatibility contract.
Generated filenames and private `dist` paths are not stable or supported; use
the documented package root and exported subpaths.
4 changes: 3 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,8 @@ jobs:
- run: pnpm install --frozen-lockfile
- run: pnpm -r --filter '@pretable/core...' --filter '@pretable/react...' --filter '@pretable/stream-adapter...' --filter '@pretable/ui...' build
- run: pnpm lint:packaging
- run: pnpm consumer:check
- run: pnpm react:compat

publish-preflight:
name: Publish preflight
Expand All @@ -308,7 +310,7 @@ jobs:
node-version: 24.19.0
cache: pnpm
- run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
- run: node ./scripts/check-security-audit-transition.mjs
- run: pnpm security:audit

api-report:
name: API Extractor — report freshness
Expand Down
4 changes: 3 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,13 @@ jobs:
registry-url: https://registry.npmjs.org

- run: pnpm install --frozen-lockfile --ignore-scripts --ignore-pnpmfile
- run: node ./scripts/check-security-audit-transition.mjs
- run: pnpm security:audit
- run: pnpm typecheck
- run: pnpm lint
- run: pnpm test
- run: pnpm build
- run: pnpm consumer:check
- run: pnpm react:compat
- run: pnpm lint:packaging
- run: pnpm publish:preflight

Expand Down
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ product direction.
npm install @pretable/react @pretable/ui
```

Peer dependencies: `react ^19.0.0` and `react-dom ^19.0.0`.
Peer dependencies: matching React and ReactDOM 18 or 19 releases. React 18.0.0
is the supported floor; React 17 and earlier are unsupported.

Import a theme and the grid skin once in your app entry point:

Expand All @@ -52,6 +53,10 @@ and `@pretable/ui/themes/material.css` are also available, as compatibility skin
for apps that already look like one of those. Tailwind v4 users can import
`@pretable/ui/tailwind.css` for token-backed utility aliases.

See [package compatibility](./docs/compatibility.md) for ESM and CommonJS
examples, legacy-resolver boundaries, runtime API requirements, and supported
public import paths.

## First Grid

```tsx
Expand Down
2 changes: 1 addition & 1 deletion apps/bench/src/__tests__/bench-runtime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ describe("bench runtime", () => {
* Asserting a specific string here would rebuild exactly that failure: it
* would pass forever no matter what the surface renders. These assert the
* RELATION instead — recorded equals rendered — which is the only claim that
* survives someone editing `packages/ui/src/tokens.css`.
* survives someone editing `packages/ui/tokens.css`.
*/
test("records the font stack the surface actually renders, and follows it when it changes", () => {
const dataset = createScenarioDataset("S1", { scale: "dev" });
Expand Down
2 changes: 1 addition & 1 deletion apps/bench/src/bench-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const UNREADABLE_FONT_STACK = "unreadable";
* reasoning about the wrong font.
*
* Reading the computed value is the point: a second hardcoded string would be
* correct for exactly as long as nobody touched `packages/ui/src/tokens.css`.
* correct for exactly as long as nobody touched `packages/ui/tokens.css`.
* `font-family` inherits, so the viewport card answers for the grid inside it.
*
* Called once per run, from `createBenchRequest`. Not on any per-row or
Expand Down
6 changes: 3 additions & 3 deletions apps/bench/tests/cascade-override.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import path from "node:path";
import { expect, test } from "@playwright/test";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const UI_SRC = path.resolve(__dirname, "../../../packages/ui/src");
const EXCEL_CSS = path.join(UI_SRC, "themes/excel.css");
const GRID_CSS = path.join(UI_SRC, "grid.css");
const UI_ROOT = path.resolve(__dirname, "../../../packages/ui");
const EXCEL_CSS = path.join(UI_ROOT, "themes/excel.css");
const GRID_CSS = path.join(UI_ROOT, "grid.css");

test("an unlayered consumer rule beats the layered grid default", async ({
page,
Expand Down
9 changes: 8 additions & 1 deletion apps/website/content/docs/getting-started/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ That's the full surface for a static grid. Two packages, one component, no confi

`npm i @pretable/react @pretable/ui` — the `react` package is the engine, `ui` ships the themes.

Requires `react ^19.0.0` and `react-dom ^19.0.0` as peer dependencies supplied by your application.
Requires matching React and ReactDOM 18 or 19 releases supplied by your
application. React 18.0.0 is the supported floor; React 17 and earlier are
unsupported.

## Import the styles

Expand All @@ -31,6 +33,11 @@ The theme file declares all `--pretable-*` tokens at `:root`; `grid.css` is the
Tailwind v4 users can additionally import `@pretable/ui/tailwind.css` to get `bg-pt-*`/`text-pt-*` utility shortcuts that alias the `--pretable-*` tokens. Density (compact / standard / spacious) is runtime-switchable by toggling `data-density="..."` on `<html>`.
</Callout>

Pretable publishes ESM and CommonJS entry points with ES2018 syntax. See the
[package compatibility guide](https://github.com/cacheplane/pretable/blob/main/docs/compatibility.md)
for older-bundler boundaries, required runtime APIs, and supported public import
paths.

Sort, filter, selection, and streaming rows are layered on top of what you just saw — see [/docs/streaming](/docs/streaming) for the streaming-adapter API while a guide for the rest is being written.

## What's next
Expand Down
2 changes: 1 addition & 1 deletion apps/website/content/docs/grid/api-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ interface SerializeRangesArgs<TRow, TRowId, TColumns> {
rowModelSnapshot: PretableRowModelSnapshot<TRow, TRowId, TColumns>;
columns: readonly PretableColumn<TRow>[];
copyWithHeaders?: boolean;
locale?: Intl.LocalesArgument;
locale?: PretableLocale;
scope: "all" | "loaded";
}
```
Expand Down
2 changes: 1 addition & 1 deletion apps/website/content/docs/grid/clipboard.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ import { serializeRanges } from "@pretable/react";
| `rowModelSnapshot` | `PretableRowModelSnapshot<TRow, TRowId, TColumns>` | immutable indexed source used for bounded range reads |
| `columns` | `readonly PretableColumn<TRow>[]` | column definitions (including the row-select column) |
| `copyWithHeaders` | `boolean` | mirror of the `copyWithHeaders` prop |
| `locale?` | `Intl.LocalesArgument \| undefined` | public presentation locale used by native number formatting |
| `locale?` | `PretableLocale \| undefined` | public presentation locale used by native number formatting |
| `scope` | `"all" \| "loaded"` | whether copied group aggregates fold every matching record or only the loaded window |

`scope` only matters when the copied selection contains group rows. It is the same scope the grid shows on screen, forwarded so a custom serializer cannot label a partial figure as a whole one: `"loaded"` means the aggregates fold only the records currently loaded, which is what a [server-filtered grid](/docs/server-data/totals) holding a window onto a larger result set has. `serializeRanges` passes it to each column's `formatAggregate`, so the copied text matches the rendered group row. It defaults to `"all"` — a serializer you call by hand, outside the grid, is copying everything it was given.
Expand Down
2 changes: 1 addition & 1 deletion apps/website/content/docs/theming/token-reference.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ The first three are read by the engine in JavaScript; the other five are CSS-onl
| `--pretable-font-sans` | Primary sans-serif family stack | font | `ui-sans-serif, system-ui, …` | `"Aptos Narrow", "Aptos", …` | `"Roboto Flex", "Roboto", …` |
| `--pretable-font-mono` | Monospace family stack (numeric cells, code) | font | `ui-monospace, SFMono-Regular, …` | `ui-monospace, "Cascadia Mono", …` | `"Roboto Mono", ui-monospace, …` |

> The stacks are elided above; each theme declares its full list at `:root` in `packages/ui/src/themes/<theme>.css`.
> The stacks are elided above; each theme declares its full list at `:root` in `packages/ui/themes/<theme>.css`.

> The `pretable` theme ships no webfont on purpose: a house theme that carries a font dependency cannot disappear into a host application, and it costs a network request before first paint. The stack resolves to whatever the platform already renders its UI in.

Expand Down
8 changes: 4 additions & 4 deletions apps/website/lib/docs/__tests__/docs-api-surface.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1147,8 +1147,8 @@ function normalizeTypeText(text: string): string {
*
* An OPTIONAL member's documented type may spell out the `| undefined` its `?`
* already implies — `grid/clipboard.mdx` writes `locale?` as
* `Intl.LocalesArgument | undefined` where the report writes
* `locale?: Intl.LocalesArgument`. Those two say exactly the same thing to the
* `PretableLocale | undefined` where the report writes
* `locale?: PretableLocale`. Those two say exactly the same thing to the
* type checker, so the trailing alternative is dropped from both sides rather
* than charged to the roster. It is dropped only for a member the report
* declares optional: on a required member, `| undefined` is a real difference.
Expand Down Expand Up @@ -2566,7 +2566,7 @@ const CONTRACT_TEST = path.join(
"packages/ui/src/__tests__/contract.test.ts",
);

const THEMES_DIR = path.join(REPO_ROOT, "packages/ui/src/themes");
const THEMES_DIR = path.join(REPO_ROOT, "packages/ui/themes");

/** The page that owes the reader the complete `--pretable-*` surface. */
const TOKEN_REFERENCE = "theming/token-reference.mdx";
Expand Down Expand Up @@ -4708,7 +4708,7 @@ describe("docs API surface matches the generated API reports", () => {
"",
...problems,
"",
"The stylesheet wins: packages/ui/src/themes/<theme>.css at `:root`.",
"The stylesheet wins: packages/ui/themes/<theme>.css at `:root`.",
].join("\n"),
).toEqual([]);
});
Expand Down
80 changes: 80 additions & 0 deletions docs/compatibility.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Package compatibility

Pretable's public packages support current applications and older build systems
through package-name imports. The compatibility contract is intentionally about
published entry points, not files inside a package's `dist` directory.

## Module formats

All four public packages ship first-class ESM and CommonJS entry points:

```ts
import { createPretable } from "@pretable/core";
```

```js
const { createPretable } = require("@pretable/core");
```

The same package-name import contract applies to `@pretable/react`,
`@pretable/ui`, and `@pretable/stream-adapter`. Package JavaScript is emitted
with ES2018 syntax. Generic legacy resolvers can use `main`, `module`, the
CommonJS build, and package-root CSS exports without understanding every modern
conditional-export convention.

Webpack 4 is not installed in this repository and is not a tested, supported, or
certified integration. The compatibility harness instead tests the package
metadata paths used by older bundlers with modern Webpack while conditional
exports are disabled. This proves those published paths resolve; it does not
make a broader claim about Webpack 4 itself.

Import only documented package paths. Private paths such as
`@pretable/core/dist/index.mjs` are unsupported and may change in any release.

## React versions

`@pretable/react` supports matching React and ReactDOM 18 or 19 releases:

```sh
npm install @pretable/react @pretable/ui react@18 react-dom@18
```

```sh
npm install @pretable/react @pretable/ui react@19 react-dom@19
```

React 18.0.0 is the tested floor. React 17 and earlier are unsupported because
the adapter relies on React 18 APIs such as `useId` and
`useSyncExternalStore`.

## Runtime APIs

ES2018 describes the syntax level of the published JavaScript, not every runtime
API used by Pretable. Applications must provide these APIs natively or through
appropriate polyfills:

- `AbortController`
- `BigInt`
- `Object.fromEntries`
- `ResizeObserver`
- `cancelAnimationFrame`
- `queueMicrotask`
- `requestAnimationFrame`
- `structuredClone`

Browser-only APIs are needed when the associated rendering behavior runs, not
merely when a framework-neutral package is imported in Node.js. Pretable does
not bundle global polyfills because the application owns its browser and server
runtime targets.

## CSS imports

Import public CSS from the package root:

```css
@import "@pretable/ui/themes/pretable.css";
@import "@pretable/ui/grid.css";
```

These package-root paths are the stable contract. Importing CSS from `dist` is a
private deep import and is unsupported.
9 changes: 9 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,4 +54,13 @@ export default tseslint.config(
"react-refresh/only-export-components": "off",
},
},
{
files: ["packages/react/src/**/*.tsx"],
rules: {
"@typescript-eslint/no-unused-vars": [
"error",
{ varsIgnorePattern: "^(?:createElement|Fragment)$" },
],
},
},
);
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,15 +26,17 @@
"bench:memory": "node ./scripts/bench-e2e.mjs apps/bench/tests/resident-cap-memory.spec.ts",
"bench:row-model:gate": "node ./scripts/bench-row-model-gate.mjs",
"build": "pnpm -r --filter './packages/*' build && pnpm -r --workspace-concurrency=1 --filter './apps/*' build",
"consumer:check": "node ./scripts/check-packed-consumers.mjs",
"dev:bench": "pnpm --filter @pretable/app-bench dev",
"format": "prettier --check .",
"format:write": "prettier --write .",
"lint": "pnpm -r lint",
"lint:packaging": "pnpm --filter @pretable/core lint:packaging && pnpm --filter @pretable/react lint:packaging && pnpm --filter @pretable/stream-adapter lint:packaging && pnpm --filter @pretable/ui lint:packaging",
"publish:preflight": "node ./scripts/publish-preflight.mjs",
"security:audit:transition": "node ./scripts/check-security-audit-transition.mjs",
"react:compat": "node ./scripts/check-react-compatibility.mjs",
"security:audit": "pnpm audit --audit-level low",
"seo:og": "node ./scripts/generate-og-image.mjs",
"test": "node --test scripts/__tests__/analyze-cdp.test.mjs scripts/__tests__/bench-comparator-provenance.test.mjs scripts/__tests__/bench-e2e.test.mjs scripts/__tests__/bench-matrix.test.mjs scripts/__tests__/check-bench-budgets.test.mjs scripts/__tests__/check-prod-deploy.test.mjs scripts/__tests__/check-security-audit-transition.test.mjs scripts/__tests__/check-type-performance.test.mjs scripts/__tests__/node-toolchain-contract.test.mjs scripts/__tests__/public-api-forgotten-exports.test.mjs scripts/__tests__/public-api-symbol-brands.test.mjs scripts/__tests__/publish-preflight.test.mjs scripts/__tests__/publish-public-packages.test.mjs scripts/__tests__/security-audit-workflow-contract.test.mjs scripts/__tests__/workspace-scripts-own-one-package.test.mjs && pnpm -r --filter './packages/*' build && pnpm -r --no-bail --filter './packages/*' test && pnpm -r --no-bail --workspace-concurrency=1 --filter './apps/*' test",
"test": "node --test scripts/__tests__/analyze-cdp.test.mjs scripts/__tests__/bench-comparator-provenance.test.mjs scripts/__tests__/bench-e2e.test.mjs scripts/__tests__/bench-matrix.test.mjs scripts/__tests__/check-bench-budgets.test.mjs scripts/__tests__/check-packed-consumers.test.mjs scripts/__tests__/check-prod-deploy.test.mjs scripts/__tests__/check-react-compatibility.test.mjs scripts/__tests__/check-type-performance.test.mjs scripts/__tests__/node-toolchain-contract.test.mjs scripts/__tests__/public-api-forgotten-exports.test.mjs scripts/__tests__/public-api-symbol-brands.test.mjs scripts/__tests__/public-package-build-contract.test.mjs scripts/__tests__/publish-preflight.test.mjs scripts/__tests__/publish-public-packages.test.mjs scripts/__tests__/security-audit-workflow-contract.test.mjs scripts/__tests__/workspace-scripts-own-one-package.test.mjs && pnpm -r --filter './packages/*' build && pnpm -r --no-bail --filter './packages/*' test && pnpm -r --no-bail --workspace-concurrency=1 --filter './apps/*' test",
"typecheck": "pnpm -r --filter './packages/*' build && pnpm -r --filter './packages/*' typecheck && pnpm -r --workspace-concurrency=1 --filter './apps/*' typecheck",
"typecheck:performance": "pnpm --filter '@pretable/core...' build && node ./scripts/check-type-performance.mjs",
"typecheck:public": "pnpm --filter '@pretable/react...' build && pnpm exec tsc -p type-tests/tsconfig.json --noEmit && pnpm exec tsc -p type-tests/tsconfig.react.json --noEmit"
Expand All @@ -53,6 +55,7 @@
"@types/react": "^19.2.18",
"@types/react-dom": "^19.2.4",
"@vitejs/plugin-react": "^6.0.5",
"acorn": "8.18.0",
"eslint": "^10.8.0",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.3",
Expand All @@ -64,12 +67,14 @@
"react-dom": "^19.2.8",
"semver": "^7.8.5",
"source-map": "0.8.0",
"tsup": "^8.5.1",
"tsdown": "0.22.14",
"tsx": "^4.23.11",
"typescript": "^6.0.3",
"typescript-eslint": "^8.66.0",
"vite": "^8.2.1",
"vitest": "^4.1.10",
"webpack": "5.110.2",
"webpack-cli": "7.2.3",
"yaml": "^2.8.4"
}
}
2 changes: 1 addition & 1 deletion packages/core/api-extractor.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "../../api-extractor.base.json",
"mainEntryPointFilePath": "<projectFolder>/dist/index.d.ts"
"mainEntryPointFilePath": "<projectFolder>/dist/index.d.mts"
}
Loading