diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index ffb72de4..84273837 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -34,7 +34,7 @@ body: id: version attributes: label: OpenGridX version - placeholder: "3.7.0" + placeholder: "e.g. 3.9.0 (npm ls @opencorestack/opengridx)" validations: required: true - type: dropdown diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dadc168e..7412406f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,6 +69,9 @@ jobs: - name: Build library (fails if React is bundled) run: npm run build:lib + - name: Package contents and size budget (no test artifacts; DataGrid alone within budget) + run: npm run check:package + - name: Package smoke suite (packed tarball in React 18 and 19 apps) run: npm run test:smoke -- --skip-lib diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 6f4c82a0..ce9713e9 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -47,6 +47,9 @@ jobs: - name: Build library (fails if React is bundled) run: npm run build:lib + - name: Package contents and size budget + run: npm run check:package + - name: Package smoke suite (packed tarball in React 18 and 19 apps) run: npm run test:smoke -- --skip-lib diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ed0e7a3..fe74c95a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,23 @@ --- +## [3.9.1] — 2026-10-13 + +### Fixed + +- **The npm package no longer ships the theme test baselines.** `lib/theme/__baseline__/` (three JSON files, 1.6 MB, added in 3.7.0) was published because `files` excluded tests, screenshots and snapshots but not that folder. Unpacked size 9.4 → 7.8 MB. +- The demo's home page badge showed v3.7.0; it and the header now read the version from `package.json`. + +### Internal + +- **`npm run check:package`**, run in CI and before publishing: fails when `npm pack` would ship a test artifact or when `DataGrid` alone, minified and gzipped, grows past its budget (92 KB; 85.5 KB now). + +### Documentation + +- Roadmap brought up to date (3.5.1 – 3.9.1 shipped; new features on hold until the backlog of fixes, refactors and performance work is closed; the 4.0 feature-modules proposal). `docs/performance.md`: edit, paste, fill, group toggle and select-all timings at 1M rows and how to keep rows memoised. README: spreadsheet editing, Ask AI panel, `localeText`, RTL and Theme Builder in the feature list; bundle sizes re-measured. `rtl.md` and `components/pagination.md` no longer say their labels cannot be translated. Architecture notes on the column-window memo and the locale context. The wiki home links the newer feature pages. + +--- + ## [3.9.0] — 2026-10-13 ### Added diff --git a/CLAUDE.md b/CLAUDE.md index d5330e85..41fec5cd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,7 +14,7 @@ This file is auto-loaded by Claude Code and other AI coding assistants. It provi - **Build:** `npm run build:lib` produces the published `dist/opengridx.es.js` / `dist/opengridx.umd.js`. `npm run build` type-checks the lib and builds the **demo site**. - **Published:** yes — on npm as `@opencorestack/opengridx` with external consumers. Treat public types and behaviour changes as semver-relevant. - **Lint:** `npm run lint` (ESLint). Must pass before every commit. -- **Current version:** 3.9.0 +- **Current version:** 3.9.1 --- @@ -157,6 +157,10 @@ The GitHub wiki is generated from `docs/` + `wiki/` by `scripts/build-wiki.mjs` --- +## v3.9.1 — significant changes + +- **`npm run check:package`** (after `build:lib`; in `ci.yml` and `npm-publish.yml`): fails if `npm pack` would ship a test artifact (`*.test.*`, `__baseline__`, `__screenshots__`, `__snapshots__`, `__fixtures__`) and if `DataGrid` alone, minified with esbuild and gzipped, exceeds `DATAGRID_GZIP_BUDGET` (92 KB; 85.5 KB at 3.9.0). `files` in package.json works by exclusion: add a negation for any new kind of test folder under `lib/`. Raise the budget only in the commit that needs it + ## v3.9.0 — significant changes - **Rows stay memoised.** Every prop passed to `Row` must keep its identity unless that row changed: handlers go through the latest-ref pattern (`useGridRowInteractions`: stable `handleRowClick` / `handleSelectionChange`), per-row state is passed only to the row it concerns (`editingCell`, `focusedCellField`, `cellRange`), and `useGridVirtualization` builds `virtualColumns` in its own memo over column inputs only. `DataGrid.rowMemo.test.tsx` counts `Row` renders through `isCellEditable` (`Cell` is memoised, so `renderCell` would not show them) @@ -189,7 +193,7 @@ The GitHub wiki is generated from `docs/` + `wiki/` by `scripts/build-wiki.mjs` - **RTL**: read/write horizontal scroll only through `getScrollStart` / `setScrollStart` (`lib/utils/direction.ts`; scroll state is the offset from the inline start, never negative); map arrows with `logicalArrowKey(key, isRtl(el))`; panels with `getPanelPosition`. `pinnedColumns.left` / `right` are logical (inline start / end): sticky offsets use `insetInlineStart` / `insetInlineEnd` (jsdom: `style.getPropertyValue('inset-inline-start')`), borders `border-inline-*`, box-shadow x-offsets need `:dir(rtl)` mirrors. The column menu pins by screen side. `useGridScrollSync` observes `dir` mutations - Testing RTL: Playwright's headless Chromium runs with `--hide-scrollbars`; with classic scrollbars and `scrollbar-gutter: stable`, RTL cannot reach `scrollLeft` 0 — compare against the scroll origin. Reproduce on macOS with `defaults write chrome-headless-shell AppleShowScrollBars Always`. The grid's `scrollbar-width` / `scrollbar-color` disable `::-webkit-scrollbar` in Chromium - Header filter strings are in `localeText` (`headerFilter*`, resolved in `lib/utils/headerFilterLocale.ts`) -- Not in this release: the time-sliced pipeline (Option A of `docs/superpowers/specs/2026-10-12-nonblocking-pipeline-proposal.md`) is held for 3.7.0 until its overhead is within ~20 % +- Not in this release: the time-sliced pipeline (Option A of `docs/superpowers/specs/2026-10-12-nonblocking-pipeline-proposal.md`) is held (backlog) until its overhead is within ~20 % ## v3.5.1 — significant changes diff --git a/README.md b/README.md index bca3d17f..a1b5e388 100644 --- a/README.md +++ b/README.md @@ -76,11 +76,13 @@ Without a `height` prop the grid fills its container (`height: 100%`, or the fre - **Fully Customizable**: Slots system for replacing any component (pagination, overlays, toolbar). - **Export Functionality**: Built-in CSV, Excel (HTML `.xls`, or real `.xlsx` with the optional ExcelJS peer), JSON, PDF (optional jsPDF peer) and Print export. - **Clipboard**: `Ctrl+C` / `Cmd+C` copies the selected rows' visible columns as TSV for Excel/Sheets. +- **Spreadsheet editing** (v3.4–3.5): paste from Excel / Google Sheets into a cell range (parsed per column type, through `processRowUpdate`), Delete clears a range, **undo / redo** (`undoRedo`, Ctrl+Z / Ctrl+Shift+Z), and a **fill handle** that extends series, patterns or copies (Ctrl/Cmd+D / R). - **Header Filters** (v3.5): `headerFilters` adds a filter row under the column headers (text, number, date and select controls with an operator menu) that edits the same `filterModel` as the filter panel and the quick filter. - **Cell Range Selection** (v3.3): drag, Shift+click or Shift+arrows select a rectangle of cells; copy it as TSV and see its count, sum and average in a status bar (`cellSelection`, `showCellSelectionStats`). -- **AI Toolkit** (v3.4): `@opencorestack/opengridx/ai` turns your columns into a JSON Schema for your own model and validates its reply before the grid applies it (`getGridAiSchema`, `validateGridAiState`). No AI SDK, no row data sent by default. +- **AI Toolkit** (v3.4–3.5): `@opencorestack/opengridx/ai` turns your columns into a JSON Schema for your own model and validates its reply before the grid applies it (`getGridAiSchema`, `validateGridAiState`); `aiAssistant` adds an **Ask AI panel** whose changes show as removable chips with one-click undo, and `createGridAgentTools` gives agents typed tools. No AI SDK, no row data sent by default. +- **Internationalisation** (v3.9): every built-in string (menus, toolbar, panels, AI panel, pagination, screen-reader announcements) is a `localeText` key, and **right-to-left** layouts work with `dir="rtl"` (v3.6), including pinned columns. - **Accessibility**: WCAG 2.1 AA — full ARIA roles and keyboard navigation. -- **Theming**: CSS variable API, `DataGridThemeProvider` with 5 built-in themes (`darkTheme`, `roseTheme`, `emeraldTheme`, `amberTheme`, `compactTheme`) and custom themes. +- **Theming**: CSS variable API, `DataGridThemeProvider` with 5 built-in themes (`darkTheme`, `roseTheme`, `emeraldTheme`, `amberTheme`, `compactTheme`) and custom themes; the demo's **Theme Builder** edits every theme key live and exports the result as code. - **Column & Row Reordering**: Drag-and-drop column reordering and row reordering. - **Inline Cell Editing**: Full inline editing with `processRowUpdate` validation. - **State Persistence**: Save and restore grid state (sort, filter, pagination, columns, density) via `useGridStateStorage`. @@ -120,7 +122,7 @@ This means that **Cursor**, **GitHub Copilot**, **Windsurf**, and other AI agent | `rowHeight` | `number` | `52` | Row height in pixels. | | `headerHeight` | `number` | `56` | Header height in pixels. | | `noRowsLabel` | `string` | `'No Data'` | Custom empty-state message. | -| `localeText` | `GridLocaleText` | — | Overrides for the pager and empty-state strings (i18n). | +| `localeText` | `GridLocaleText` | — | Overrides for every built-in UI string (v3.9; pagination and the empty state before). | | `ariaLabel` | `string` | — | Accessible name for the grid. | | `initialState` | `GridInitialState` | — | Starting sort, filter, pagination, column and density state (uncontrolled). | | `onStateChange` | `(state: GridState) => void` | — | Fires when sort, filter, pagination, columns or density change. | @@ -490,12 +492,14 @@ Built-in themes: `darkTheme`, `roseTheme`, `emeraldTheme`, `amberTheme`, `compac | Artifact | Size | Gzipped | Notes | | :--- | :--- | :--- | :--- | -| **Core ES Module** (`opengridx.es.js`) | 331 KB | **86 KB** | Use this — tree-shakeable | -| **Core UMD** (`opengridx.umd.js`) | 233 KB | **73 KB** | CommonJS / CDN compat | -| **Styles** (`opengridx.css`) | 65 KB | **10 KB** | `import '@opencorestack/opengridx/styles'` | +| **What an app ships for `DataGrid`** (your bundler minifies) | 271 KB | **87 KB** | Measured v3.9.0: esbuild, minified, React external, `DataGrid` only | +| **Core ES Module** (`opengridx.es.js`, as published) | 493 KB | 130 KB | Unminified with readable names; use this — tree-shakeable | +| **Core UMD** (`opengridx.umd.js`) | 357 KB | 112 KB | CommonJS / CDN compat | +| **Styles** (`opengridx.css`) | 81 KB | **12 KB** | `import '@opencorestack/opengridx/styles'` | +| **AI toolkit** (`/ai`, optional import) | 20 KB | **7 KB** | Adds nothing unless imported | | **ExcelJS** (optional peer dep) | — | — | `npm install exceljs` | | **jsPDF** (optional peer dep) | — | — | `npm install jspdf jspdf-autotable` | -| **npm package download** | — | **~1.1 MB** | Compressed tarball (includes `lib/` source, docs and source maps) | +| **npm package download** | — | **~2.5 MB** | Compressed tarball (includes `lib/` source, docs and source maps; 7.8 MB unpacked) | - **Tree-shaking Ready**: ES Module build — bundlers (Vite, Webpack) only include what you use. - **Zero UI Dependencies**: No MUI, Ant Design, or Radix. Pure React + vanilla CSS. diff --git a/demo/Home.tsx b/demo/Home.tsx index d550f910..24376c2f 100644 --- a/demo/Home.tsx +++ b/demo/Home.tsx @@ -1,4 +1,5 @@ import './Home.css'; +import { version as packageVersion } from '../package.json'; interface HomeProps { onNavigate?: (id: string) => void; @@ -63,7 +64,7 @@ export default function Home(_props: HomeProps) {
OpenGridX Logo
- OpenGridX v3.7.0 + OpenGridX v{packageVersion}

The elite, high-performance DataGrid for modern React. Built to handle massive data with a premium developer experience. diff --git a/demo/public/llms.txt b/demo/public/llms.txt index 22d57684..5ed8822e 100644 --- a/demo/public/llms.txt +++ b/demo/public/llms.txt @@ -1,6 +1,6 @@ # OpenGridX — AI Agent Context # Package: @opencorestack/opengridx -# Version: 3.9.0 +# Version: 3.9.1 # Docs: https://opencorestack.github.io/OpenGridX/ # This file is machine-readable. AI developers: start here. diff --git a/docs/architecture/datagrid-orchestration.md b/docs/architecture/datagrid-orchestration.md index 56166cd4..330bb4f0 100644 --- a/docs/architecture/datagrid-orchestration.md +++ b/docs/architecture/datagrid-orchestration.md @@ -113,10 +113,12 @@ in an effect, so **keep the call order when you move or add a hook**. In particu `GridToolbarSlot`, `GridAiAssistantArea` (the `aiAssistant` panel or `slots.aiAssistantPanel` while open, anchored under the toolbar), `GridStandaloneColumnPanel`, `GridListView` (list view) or `GridViewport` (`components/DataGrid/GridViewport.tsx`, the `role="grid"` viewport: `Header`, `GridPinnedRows` top, `GridEmptyState`, `GridVirtualRows`, `GridPinnedRows` bottom, `GridAggregationFooter`), `GridCellSelectionStatsArea` (`showCellSelectionStats` with `cellSelection`), then `slots.footer` or `GridPaginationArea`, `GridLiveRegion`, -`GridLoadingOverlay` and `GridErrorOverlay`. `GridRoot` builds the overlay slot elements and passes the +`GridLoadingOverlay` and `GridErrorOverlay`. `GridRoot` also provides `GridLocaleContext` (v3.9) around all of them: `localeText`, kept stable while shallowly equal (`useShallowStableValue`), resolved once by `resolveGridLocale` (`lib/utils/gridLocale.ts`); components and `Row` read it with `useGridLocale()` instead of a prop, so `Row` gets no prop that changes per render. `GridRoot` builds the overlay slot elements and passes the rest of its props on to `GridViewport`. The props all three row renderers take alike are built once in `GridViewport` (`rowRenderProps`) and spread into each. `rowRenderProps.cellRange` is the cell range's render rectangle; each row renderer turns it into a per-row `cellRange` (`getRowCellRange`), `null` for rows outside it, and `Row` is memoised with a value comparison of that prop, so a drag only re-renders the rows the -range enters or leaves. `rowRenderProps.fillHandle` (v3.5) adds `fillHandleCol` to the one row that draws the fill +range enters or leaves. The same rule holds for every row prop (v3.9): `editingCell` goes only to the row being +edited, `focusedCellField` only to the focused row, and the row click and checkbox handlers keep one identity +(`useGridRowInteractions` reads the latest handlers when called). `DataGrid.rowMemo.test.tsx` guards it. `rowRenderProps.fillHandle` (v3.5) adds `fillHandleCol` to the one row that draws the fill handle (a single selected cell gets an empty column interval, so it draws the handle but no range). diff --git a/docs/architecture/use-grid-virtualization.md b/docs/architecture/use-grid-virtualization.md index 17cc32be..30728bf2 100644 --- a/docs/architecture/use-grid-virtualization.md +++ b/docs/architecture/use-grid-virtualization.md @@ -90,6 +90,8 @@ Row heights include an expanded detail panel's height, so the prefix sums accoun Columns use the same search on `unpinnedAccWidths` (also inclusive), relative to the left edge of the unpinned area (`scrollLeft - leftPinnedWidth`), widened by 6 columns on each side. The skipped widths become `__spacer_left__` / `__spacer_right__` entries (`isSpacer: true`) in `virtualColumns`. +The column window is computed in its own `useMemo` (v3.9), over the column parts of the layout (`unpinnedAccWidths`, `unpinnedColsWithWidth`, the pinned column lists and widths), `scrollLeft` and the viewport width only. A row change (an edit), a vertical scroll or a row-height change therefore keeps the same `virtualColumns` array, and `Row`, which is memoised on its props, does not re-render for it. `useLayout` returns the row and column parts from separate memos (v3.7.1), so the column fields keep their identity while rows change. + When a column declares `colSpan` / `rowSpan`, `DataGrid` widens this window afterwards (`useGridSpanRowWindow` / `useGridSpanColumnWindow`) so a span whose origin is outside the window is not cut. --- diff --git a/docs/components/pagination.md b/docs/components/pagination.md index 4d892cee..93da7359 100644 --- a/docs/components/pagination.md +++ b/docs/components/pagination.md @@ -41,7 +41,7 @@ You can replace the internal pagination UI using the `pagination` slot: ## 🌍 Internationalisation -Pass `localeText` on `` to override the text labels in the Pagination component (rows-per-page, range, and page-counter labels). The rows-per-page select takes its accessible name from the `paginationRowsPerPage` label. The navigation landmark label (`aria-label="Pagination"`) and the button labels and titles (`Go to next page`, `Next page`, …) are hardcoded and not overrideable via `localeText`. +Pass `localeText` on `` to override the text labels in the Pagination component (rows-per-page, range, and page-counter labels). The rows-per-page select takes its accessible name from the `paginationRowsPerPage` label. Since v3.9 the navigation landmark label (`paginationLabel`, default `"Pagination"`) and each button's label and tooltip (`paginationNextPageLabel` / `paginationNextPageTooltip` → `"Go to next page"` / `"Next page"`, and the same for First, Previous and Last) are `localeText` keys as well. | Key | Type | Default | | :--- | :--- | :--- | diff --git a/docs/features/rtl.md b/docs/features/rtl.md index 42f6b1cd..a1edd0c2 100644 --- a/docs/features/rtl.md +++ b/docs/features/rtl.md @@ -42,7 +42,7 @@ The column menu is labelled by screen side: in RTL *Pin to Left* adds the column ## Known limitations -- **Menu and panel labels are hard-coded English** ("Pin to Left", "Sort Ascending"); there is no `localeText` key for the column menu yet. +- ~~Menu and panel labels are hard-coded English~~: since v3.9 every built-in string, including the column menu (`columnMenuPinLeft`, `columnMenuSortAscending`, …), is a `localeText` key. See *Locale text for every UI string* in [`API_REFERENCE.md`](../API_REFERENCE.md). - **Header filter operator symbols** are mirrored by the browser's bidi rules (`∋` shows as `∈`), as in any RTL text. - **`direction` set through CSS** (a class or stylesheet, not the `dir` attribute) and changed at runtime is not detected; scroll the grid or remount it with a `key`. - **Headless Chromium tests:** Playwright launches Chromium with `--hide-scrollbars`. With classic scrollbars (Linux) `scrollbar-gutter: stable` still reserves the gutter, and in RTL Chromium then cannot scroll the last gutter-width of the start into view (`scrollLeft` stops at minus the gutter). A plain scroller without the grid does the same and real browsers with visible scrollbars do not. Tests must compare with the browser's scroll origin, as `DataGrid.rtl.browser.test.tsx` does. To reproduce on macOS: `defaults write chrome-headless-shell AppleShowScrollBars Always` (undo with `defaults delete chrome-headless-shell`). diff --git a/docs/performance.md b/docs/performance.md index d6ac0c3c..86089b4d 100644 --- a/docs/performance.md +++ b/docs/performance.md @@ -136,7 +136,7 @@ Measured with `npm run bench` (`bench/`), which installs the packed npm package | 500,000 (3.5.0) | 295 ms | 493 / 641 ms | 59 ms | 958 ms | 331 ms | 0 % | 39.4 MB | | 1,000,000 | 621 ms | 467 / 467 ms | 109 ms | 1,813 ms | 693 ms | 0 % | 76.9 MB | -### Before and after the faster sort, quick filter and grouping (unreleased) +### Before and after the faster sort, quick filter and grouping (3.6.0) Medians of 5 runs, the same machine, the 3.5.1 code (`122d431`) and the changed code (`e1df2f1`) measured back to back on 2026-10-12 (`bench/results/2026-10-12-apple-m1-darwin-before.json` and `bench/results/2026-10-12-apple-m1-darwin.json`). Load average at the start of each run: 2.9 (before) and 4.6 (after); an IDE, a browser and other tools were open. Row counts after every filter and grouping were identical in both runs. @@ -162,6 +162,24 @@ What changed: sort keys are read once per row into typed arrays and strings are - **Sorting, the quick filter and grouping run on the main thread in one piece.** Up to 100k rows they finish in a few frames; at 1M a sort blocks the page for about 0.35–0.6 s in every engine, grouping for 0.7 s (1.8 s in Firefox), and the first quick-filter keystroke for about 1.8–3 s. For sorting or searching a million rows interactively, use server-side sorting and filtering. - **Reach:** past the browser's height cap rows cannot be scrolled to, and in Firefox the grid stops scrolling altogether. See [Reach](#reach-the-browser-height-ceiling). +### Editing, selection and grouping at 1M rows (3.8 – 3.9) + +Measured in Chromium on an Apple M1, 1,000,000 rows, from the event to the end of its long task (best of three; not part of `npm run bench`, which covers mount, scroll, sort and filter). + +| Interaction | 3.7.0 | 3.8.0 | | +| :--- | ---: | ---: | :--- | +| Edit one cell | 561 ms | ~35 ms | the row store is updated in place; id lists over the visible rows are reused | +| Paste 1,000 × 10 cells | ~790 ms | ~45 ms | | +| Undo / redo of that paste | ~790 ms | ~45 ms | | +| Fill 1,000 cells | 809 ms | ~50 ms | | +| Expand a group | 279 ms | ~135 ms | leaf row metadata built once per tree | +| Collapse a group | ~120 ms | ~1 ms | | +| Select all | ~270 ms | ~270 ms | bounded by building 1M-id sets for the array `rowSelectionModel` | + +3.9.0 keeps rows memoised: selecting a row, starting or committing an edit, and vertical scrolling re-render only the rows they change (a click re-rendered every visible row, now one). React's own work per interaction is about 3–5 ms at 100k rows. + +To keep this, change rows by passing new row objects (never mutate them), keep `columns` and `localeText` shallowly stable (inline objects of plain values are fine; define function entries outside the render). Handlers such as `onRowClick` may be inline: the grid passes rows its own stable handlers. + ### Method - **The app is the built package.** `bench/app` installs the tarball from `npm pack`, like a consumer, and is a production build (no React development checks). diff --git a/docs/roadmap.md b/docs/roadmap.md index ca9257d8..35665380 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -85,14 +85,32 @@ This document tracks the current status of features in `OpenGridX` and outlines | **3.3.0** ✅ | shipped 7 Oct | **Cell range selection** (opt-in `cellSelection` prop): mouse drag, Shift+click, Shift+arrows; Ctrl+C / Cmd+C copies the block as TSV | Selection status bar (count, sum, average of selected cells); wrapped header text; React Compiler compatibility check | | **3.4.0** ✅ | shipped 8 Oct | **Paste from Excel / Google Sheets** into the selection, through `processRowUpdate` (values parsed per column type or `valueParser`, non-editable cells skipped, `onClipboardPaste` summary); **Delete clears a range**; built-in inputs and editors use the shared `Input` / `Checkbox` (`Input variant="cell"`). **Undo / redo** for cell edits and pastes (Ctrl+Z, Ctrl+Shift+Z / Ctrl+Y, `apiRef.undo()` / `redo()`) | **AI toolkit, part 1** in a new `@opencorestack/opengridx/ai` entry point: `getGridAiSchema(columns)` (JSON Schema of the filter, sort, grouping, aggregation, pivot and visibility models the columns allow) and `validateGridAiState(json, columns)` (drops unknown fields and operators, coerces values, returns errors) | | **3.5.0** ✅ | shipped 9 Oct | **AI toolkit, part 2**: `aiAssistant={{ onPrompt }}` Ask AI panel (chips, Undo, voice) that calls the app's own model through `createGridAiPromptHandler`; only validated replies apply; `createGridAgentTools(apiRef, columns)` (plain tool objects for CopilotKit, the Vercel AI SDK, provider tool calling or WebMCP) | **Header filter row** (`headerFilters`); **fill handle** (series, Alt copy, Ctrl/Cmd+D / R); `rowGroupingModel` controlled/uncontrolled pair | +| **3.5.1** ✅ | shipped 10 Oct | **Content height capped to the browser's limit** (Firefox collapsed grids taller than ~17.9M px) | Dev warning names the reachable row count | +| **3.6.0** ✅ | shipped 12 Oct | **Sorting 3–5× faster** (typed-array keys) | Next.js `'use client'` banner; optional peers safe in webpack / Turbopack; **RTL parts 1 and 2** (pinned columns); header filter `localeText` | +| **3.7.0** ✅ | shipped 13 Oct | **Theme Builder** demo page + 16 theme keys | `DataGrid.tsx` split (`GridRoot` / `GridViewport`, composite hooks); types split per domain; demo smoke test | +| **3.7.1** ✅ | shipped 13 Oct | Audit fixes: exports survive a throwing `valueGetter`; **formula escaping of `valueFormatter` text** (security) | Quick filter matches shown text; date paste fixes; list view theme; CSS reset scoped to the grid | +| **3.8.0** ✅ | shipped 13 Oct | **Edits at 1M rows 10–20× faster** (in-place row store; edit ~35 ms, paste ~45 ms) | Group expand ~2×, collapse ~1 ms | +| **3.8.1** ✅ | shipped 13 Oct | **Grid buttons keep their own styles** (AI panel, toolbar active states) | Exact `bigint` sort; stricter number text; dead-code cleanup; every export documented | +| **3.9.0** ✅ | shipped 13 Oct | **`localeText` for every UI string** (~200 keys) | 9 theme keys + Theme Builder controls; rows stay memoised through selection and editing; demo sidebar by priority | +| **3.9.1** ✅ | shipped 13 Oct | **npm package no longer ships 1.6 MB of test baselines** | `check:package` guard in CI; version badges read `package.json`; docs brought up to date | +| 3.10.0 | open | AI change chips translatable | Backlog work below | | Freeze | 19 Dec – 5 Jan | Bug fixes only | — | +### Now: backlog first (feature hold since 13 Oct) + +New features (the *Next* list below) wait until the open fixes, refactors and performance work are done. Tracked in [`superpowers/plans/2026-10-13-audit-tracker.md`](superpowers/plans/2026-10-13-audit-tracker.md): + +* **Refactors** (no behaviour change): about 40 `as unknown as` casts (R4), files over 600 lines such as `GridToolbar.tsx` (R5), hooks with very wide parameter lists such as keyboard navigation (R6). +* **Performance**: the time-sliced filter / sort pipeline (held: 1.5–2× slower in total, needs to be within ~20 %); select-all at 1M rows (~270 ms, bounded by the array `rowSelectionModel`). +* **Demo**: syntax highlighting in the source viewer. +* **Bundle size (4.0)**: an app using only `DataGrid` ships 87 KB gzipped; opt-in [feature modules](superpowers/specs/2026-10-13-feature-modules-proposal.md) would save ~20 KB. Breaking, so planned for 4.0. + ### Across the quarter * ✅ **Published benchmark** (done 9 Oct): reproducible 10k / 100k / 500k / 1M-row numbers (scroll frame times, sort, filter and grouping time, memory, the reachable row) in [`docs/performance.md`](performance.md#benchmarks); rerun with `npm run bench`. * ✅ **Next.js App Router guide** (done 12 Oct): [`docs/features/nextjs.md`](features/nextjs.md), verified with `next build` / `next start` on Next.js 15.5 and 16.4. The ES bundle now starts with `'use client'`. -* ✅ **RTL check** (done 12 Oct): [`docs/features/rtl.md`](features/rtl.md). Fixed: text alignment, horizontal scrolling and column virtualization, arrow keys, the resize and fill handles, range outlines and auto-scroll, toolbar panel placement. Pinned columns remain a limitation (Q1 2027). -* **Demo site**: source viewer on all examples (`DocsLayout` with Vite `?raw`) and syntax highlighting, done alongside each release's demo page. +* ✅ **RTL check** (done 12 Oct): [`docs/features/rtl.md`](features/rtl.md). Fixed: text alignment, horizontal scrolling and column virtualization, arrow keys, the resize and fill handles, range outlines and auto-scroll, toolbar panel placement; pinned columns followed in RTL part 2. +* **Demo site**: ✅ source viewer on every example (`DocsLayout` with Vite `?raw`); syntax highlighting still open. ### Definition of done for each feature @@ -115,7 +133,7 @@ This document tracks the current status of features in `OpenGridX` and outlines * **Undo must cover rejected updates**: a `processRowUpdate` that throws or a paste that partly fails must leave the history consistent. * **Wrong AI filters** that look plausible: the validator, visible chips and undo are required, not optional. -## 🔭 Next (Q1 2027) +## 🔭 Next (Q1 2027) — on hold until the backlog above is closed * **Row reordering inside tree data and row groups**, touch-friendly (MUI #4821, 405 👍, not in MUI X). @@ -125,7 +143,7 @@ This document tracks the current status of features in `OpenGridX` and outlines * **Sparklines** and a charts adapter that passes grid state to any chart library. * **AI toolkit, part 3**: smart paste (unstructured text → pending rows) and `apiRef.getAiContext()` for "summarise selection"; an `opengridx-mcp` docs server for coding assistants. * **Tailwind / shadcn theme preset**, sticky header against page scroll. -* ✅ **RTL, part 2** (done 12 Oct, [`rtl.md`](features/rtl.md)): pinned columns and pinned system columns with logical sticky offsets and mirrored shadows, column menu pinning by screen side, mirrored pagination arrows and tree chevrons, a direction change on a mounted grid, column reorder by drag checked. Remaining: hard-coded English menu labels (no `localeText` yet), and the bidi-mirrored header filter operator symbols. +* ✅ **RTL, part 2** (done 12 Oct, [`rtl.md`](features/rtl.md)): pinned columns and pinned system columns with logical sticky offsets and mirrored shadows, column menu pinning by screen side, mirrored pagination arrows and tree chevrons, a direction change on a mounted grid, column reorder by drag checked. Remaining: the bidi-mirrored header filter operator symbols (the menu labels became `localeText` keys in 3.9). ### Not planned @@ -136,6 +154,9 @@ This document tracks the current status of features in `OpenGridX` and outlines ## ✅ Implemented Features (Recent) +### 3.3 – 3.9 *(2026-10-07 → 2026-10-13)* +- Spreadsheet editing (range selection, paste, undo / redo, fill handle), the AI toolkit (schema, validator, Ask AI panel, agent tools), header filters, the 1M-row benchmark and height cap, 3–5× faster sorting, RTL, the Theme Builder, a full audit with security and correctness fixes, 10–20× faster edits at 1M rows and `localeText` for every string. See the release table above and [CHANGELOG.md](../CHANGELOG.md). + ### 3.x releases *(2026-09-29 → 2026-10-01)* - **3.0.0** correctness release (rows never modified, typed generics, React 18 support), **3.0.1** fill-height layout fix, **3.1.0** `sortComparator` and column auto-size, **3.2.0** non-blocking PDF export, **3.2.1** Safari boolean editor fix, **3.2.2** linear-time export regexes. Details in [CHANGELOG.md](../CHANGELOG.md); upgrade guide in [migration/v2-to-v3.md](migration/v2-to-v3.md). - Package smoke suite against the packed tarball in React 18 and 19 apps, pull-request CI, community files and a wiki generated from these docs. @@ -158,5 +179,5 @@ This document tracks the current status of features in `OpenGridX` and outlines - **Core hook tests**: `useGridRowPipeline`, `useGridControlledState`, `useGridKeyboardNavigation` now have full test coverage (25 new tests). - **`CLAUDE.md`**: Root-level AI context file auto-loaded by Claude Code and other AI coding assistants. -* **Demo app (partial)**: 32 interactive examples across 6 categories (Main features, Advanced features, Components, Customization, Tutorials, Resources) with categorized sidebar navigation and per-page Table of Contents. Source code viewer present on 6 of 32 examples. Syntax highlighting and public deployment are in the upcoming Demo Site section above. +* **Demo app**: 56 pages in 13 categories ordered by what developers look for first (3.9), a per-page table of contents, the source viewer on every example, and public deployment on GitHub Pages with a smoke test of every route. Syntax highlighting is still open. * **Quickstart & Installation Guides**: Comprehensive onboarding documentation for developers. diff --git a/docs/superpowers/plans/2026-10-13-audit-tracker.md b/docs/superpowers/plans/2026-10-13-audit-tracker.md index 15295c7d..c482a22b 100644 --- a/docs/superpowers/plans/2026-10-13-audit-tracker.md +++ b/docs/superpowers/plans/2026-10-13-audit-tracker.md @@ -70,3 +70,4 @@ Baselines (Chromium, M1, 1M rows): edit 508 ms · paste/undo/redo ~790 ms · fil - 2026-10-13 — P8 done (row memoisation), regression test proven to fail without the fixes. - 2026-10-13 — R7 done (agent, reviewed): 196 localeText keys via GridLocaleContext; added useShallowStableValue so an inline localeText of strings keeps rows memoised. Unit 1972, browser 585/585 in 3 engines. - 2026-10-13 — Released 3.9.0 (CI run 38381619839). +- 2026-10-13 — 3.9.1 patch (PR flow): baseline exclusion, check:package, version badges, docs. Cherry-picked from release/3.10.0. diff --git a/docs/superpowers/specs/2026-10-13-feature-modules-proposal.md b/docs/superpowers/specs/2026-10-13-feature-modules-proposal.md new file mode 100644 index 00000000..2d97b31a --- /dev/null +++ b/docs/superpowers/specs/2026-10-13-feature-modules-proposal.md @@ -0,0 +1,50 @@ +# Proposal: opt-in feature modules (4.0) + +Status: proposal, not scheduled. Replaces audit item P9 ("lazy or modular opt-in features"). + +## Why + +An app that imports only `DataGrid` ships 87–88 KB gzipped (271 KB minified, esbuild, React external; measured 2026-10-13 on 3.9.0). Much of it is features the app may never turn on. + +Measured share of the minified bundle (esbuild metafile, `DataGrid`-only entry): + +| Part | Minified | Wired how | +| :--- | :--- | :--- | +| Cell selection | 10.0 KB | hook in the core hook order | +| Header filter row (+ utils) | 11.3 KB | component, visible at mount | +| AI assistant panel + chip logic + hook | ~13 KB | hook + panel | +| Fill handle | 4.6 KB | hook | +| Paste | 4.3 KB | hook | +| Pivot logic | 4.5 KB | hook + utils | +| Spanning | 4.2 KB | hook | +| Undo / redo | 2.9 KB | hook | +| Clipboard | 2.7 KB | hook | +| Columns panel | 3.4 KB | component, on demand | + +Roughly 60 KB minified (~20 KB gzipped, ~23 %) is opt-in functionality. + +## Options considered + +1. **Lazy-load the on-demand UI** (`React.lazy` for the AI panel, columns panel, pivot): ~6 KB gzipped once the header filter row is excluded (it renders at mount, so lazy loading would flicker). Costs: a loading moment on first open, extra chunk files, no gain for the UMD build, SSR edge cases. **Rejected for now**: too little gain for the risk. +2. **Feature modules (recommended for 4.0).** Features register through an explicit list, so a bundler drops what is not imported: + + ```tsx + import { DataGrid } from '@opencorestack/opengridx'; + import { cellSelection, fillHandle, undoRedo } from '@opencorestack/opengridx/features'; + + + ``` + + Each module brings its hook (called at its fixed slot in the hook order) and its components. A grid without the module ignores the related props, with a development warning naming the missing module. +3. **Separate entry points per feature without registration**: still needs the core to reach the hooks, so it reduces to option 2. + +## Constraints for option 2 + +- **Hook order stays fixed.** `DataGrid` keeps one call site per feature slot and calls either the module's hook or a no-op of the same shape. A module may never add or remove hook calls between renders: the `features` list must be fixed for the grid's lifetime (development error if it changes). +- `apiRef` methods of a missing feature exist and warn (they are part of `GridApi`'s type). +- Breaking: apps using these features must add the module. A 3.x compatibility entry (`@opencorestack/opengridx/all`) registers every module, so migration is one import. +- The package smoke suite needs a "minimal" fixture that asserts the bundle size, so a later change cannot pull a module back into the core. + +## Expected result + +~20 KB gzipped less for an app that uses none of the modules (87 → ~67 KB), measured the same way. diff --git a/llms.txt b/llms.txt index 22d57684..5ed8822e 100644 --- a/llms.txt +++ b/llms.txt @@ -1,6 +1,6 @@ # OpenGridX — AI Agent Context # Package: @opencorestack/opengridx -# Version: 3.9.0 +# Version: 3.9.1 # Docs: https://opencorestack.github.io/OpenGridX/ # This file is machine-readable. AI developers: start here. diff --git a/package-lock.json b/package-lock.json index 3e4c5b09..4c8b03c8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@opencorestack/opengridx", - "version": "3.9.0", + "version": "3.9.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@opencorestack/opengridx", - "version": "3.9.0", + "version": "3.9.1", "license": "MIT", "devDependencies": { "@babel/core": "^7.29.7", diff --git a/package.json b/package.json index 27b6b80f..1d904955 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@opencorestack/opengridx", - "version": "3.9.0", + "version": "3.9.1", "description": "OpenGridX: High-performance React data infrastructure. Unlock advanced Row Grouping, Excel Export, and Column Pinning without the usual \"Pro\" gatekeeping. Built for speed, scale, and complete architectural freedom. Fully open, virtualization-ready, and feature-complete.", "type": "module", "main": "./dist/opengridx.umd.js", @@ -29,6 +29,7 @@ "!lib/**/*.test.tsx", "!lib/**/__screenshots__", "!lib/**/__snapshots__", + "!lib/**/__baseline__", "docs/architecture", "docs/assets", "docs/components", @@ -65,7 +66,8 @@ "coverage": "vitest run --coverage", "prepublishOnly": "npm run build:lib", "wiki:build": "node scripts/build-wiki.mjs", - "check:compiler": "node scripts/check-react-compiler.mjs" + "check:compiler": "node scripts/check-react-compiler.mjs", + "check:package": "node scripts/check-package.mjs" }, "keywords": [ "react", diff --git a/scripts/check-package.mjs b/scripts/check-package.mjs new file mode 100644 index 00000000..4c4aab25 --- /dev/null +++ b/scripts/check-package.mjs @@ -0,0 +1,44 @@ +// Checks what `npm publish` would ship, after `npm run build:lib`: +// 1. no test artifact is in the package (tests, baselines, screenshots, snapshots, fixtures); +// `files` in package.json works by exclusion, so a new kind of artifact slips in unnoticed; +// 2. what an app ships for `DataGrid` alone (minified with esbuild, React and the optional peers +// external, gzipped) stays within its budget. Raise the budget deliberately, in the same commit +// as the change that needs it. +import { execFileSync } from 'node:child_process'; +import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs'; +import { tmpdir } from 'node:os'; +import { join, resolve } from 'node:path'; +import { gzipSync } from 'node:zlib'; +import { build } from 'esbuild'; + +const ROOT = resolve(import.meta.dirname, '..'); +const DATAGRID_GZIP_BUDGET = 92 * 1024; // 3.9.0: 87.2 KB +const FORBIDDEN = [/\.test\.[cm]?[jt]sx?$/, /\.browser\.test\./, /__baseline__\//, /__screenshots__\//, /__snapshots__\//, /__fixtures__\//]; + +let failed = false; +const fail = (message) => { console.error(`check-package: ${message}`); failed = true; }; + +const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm'; +const [pack] = JSON.parse(execFileSync(npm, ['pack', '--dry-run', '--json', '--ignore-scripts'], { cwd: ROOT, encoding: 'utf8' })); +const leaked = pack.files.map(f => f.path).filter(path => FORBIDDEN.some(re => re.test(path))); +if (leaked.length > 0) fail(`${leaked.length} test file(s) would be published: ${leaked.slice(0, 5).join(', ')}${leaked.length > 5 ? ', …' : ''}. Exclude them in package.json "files".`); +else console.log(`check-package: ${pack.entryCount} files, no test artifacts (unpacked ${(pack.unpackedSize / 1048576).toFixed(1)} MB)`); + +const dir = mkdtempSync(join(tmpdir(), 'ogx-size-')); +try { + const entry = join(dir, 'entry.mjs'); + writeFileSync(entry, `export { DataGrid } from ${JSON.stringify(join(ROOT, 'dist/opengridx.es.js'))};\n`); + const outfile = join(dir, 'out.js'); + await build({ + entryPoints: [entry], bundle: true, minify: true, format: 'esm', outfile, logLevel: 'error', + external: ['react', 'react-dom', 'react/jsx-runtime', 'exceljs', 'jspdf', 'jspdf-autotable'], + }); + const gzip = gzipSync(readFileSync(outfile)).length; + const kb = (n) => `${(n / 1024).toFixed(1)} KB`; + if (gzip > DATAGRID_GZIP_BUDGET) fail(`DataGrid alone is ${kb(gzip)} gzipped, over the ${kb(DATAGRID_GZIP_BUDGET)} budget.`); + else console.log(`check-package: DataGrid alone ${kb(gzip)} gzipped (budget ${kb(DATAGRID_GZIP_BUDGET)})`); +} finally { + rmSync(dir, { recursive: true, force: true }); +} + +if (failed) process.exit(1); diff --git a/wiki/Home.md b/wiki/Home.md index 80c60d58..07ff1a29 100644 --- a/wiki/Home.md +++ b/wiki/Home.md @@ -28,6 +28,8 @@ npm install @opencorestack/opengridx - **Data:** [Virtualization](Virtualization) · [Filtering & Search](Filtering) · [Sorting & Pagination](Sorting-and-Pagination) · [Infinite Scroll](Infinite-Scroll) · [Data Source](Data-Source) - **Interaction:** [Editing & Reordering](Editing-and-Reordering) · [Row Selection](Row-Selection) · [Clipboard](Clipboard) · [Keyboard & Accessibility](Keyboard-and-Accessibility) +- **Spreadsheet:** [Cell Range Selection](Cell-Range-Selection) · [Undo & Redo](Undo-Redo) · [Fill Handle](Fill-Handle) · [Header Filters](Header-Filters) +- **AI & integration:** [AI Toolkit](AI-Toolkit) · [Next.js](Nextjs) · [Right-to-Left](Right-to-Left) · [Theming](Theming) · [Performance](Performance) - **Layout:** [Pinning](Pinning) · [Column Grouping](Column-Grouping) · [Cell Spanning](Cell-Spanning) · [Master-Detail](Master-Detail) · [List View](List-View) - **Analysis:** [Tree Data & Grouping](Tree-Data-and-Grouping) · [Aggregation & Pivot](Aggregation-and-Pivot) - **Output:** [Export Guide](Export-Guide) · [PDF Export](PDF-Export) · [State Persistence](State-Persistence)