Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
04464ab
feat(locale): translate the AI assistant's change chips
asif7774 Oct 13, 2026
9fc6599
docs: feature-modules proposal for the opt-in bundle size; record P9 …
asif7774 Oct 13, 2026
defece9
docs: close audit item B7 (duplicate ids already warn)
asif7774 Oct 13, 2026
4a56e8b
docs: bring roadmap, performance, README and wiki up to date; stop pu…
asif7774 Oct 13, 2026
9d5a4fb
feat(demo): syntax-highlight the source viewer; update the integratio…
asif7774 Oct 13, 2026
695879f
ci: guard the npm package contents and the DataGrid-only bundle size
asif7774 Oct 13, 2026
0d12f11
refactor(toolbar): split GridToolbar into its panels and share their …
asif7774 Oct 13, 2026
00b2c74
refactor(export): one file per export format
asif7774 Oct 13, 2026
f51c29e
refactor(export): move the xlsx cell, format and image helpers to exc…
asif7774 Oct 13, 2026
74009c2
refactor(cell-selection): move the range drag into useGridCellRangeDrag
asif7774 Oct 13, 2026
0fb4926
refactor(keyboard): move keydown handling into handleGridKeyDown
asif7774 Oct 13, 2026
df226da
refactor(types): remove 28 as-unknown-as casts
asif7774 Oct 13, 2026
0d5cdd9
ci: turn the review insights into guards
asif7774 Oct 13, 2026
a4ea746
docs: tidy CLAUDE.md and CHANGELOG after rebasing onto 3.9.1
asif7774 Oct 13, 2026
e044823
perf(selection): build the select-all set once and reuse it
asif7774 Oct 13, 2026
71247ba
docs: close the time-sliced pipeline; archive its branch
asif7774 Oct 13, 2026
5f3afc5
docs(roadmap): mark the 3.10 backlog items done
asif7774 Oct 13, 2026
8283d64
chore(release): 3.10.0
asif7774 Oct 13, 2026
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
6 changes: 6 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,12 @@ jobs:
- name: Lint
run: npm run lint

- name: Versions agree (llms.txt, CLAUDE.md, CHANGELOG vs package.json)
run: npm run check:versions

- name: React Compiler baseline (no new CompileError or skip)
run: npm run check:compiler

- name: Type-check (lib, tests, demo, e2e)
run: npm run typecheck

Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ jobs:
- name: Package contents and size budget
run: npm run check:package

- name: Versions agree (llms.txt, CLAUDE.md, CHANGELOG vs package.json)
run: npm run check:versions

- name: Package smoke suite (packed tarball in React 18 and 19 apps)
run: npm run test:smoke -- --skip-lib

Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,26 @@

---

## [3.10.0] — 2026-10-13

### Added

- **The AI assistant's change chips are translatable**: 11 `localeText` keys (`aiAssistantChipSearch`, `ChipNoFilters`, `ChipSort(field, direction)`, `ChipNoSorting`, `ChipGroup`, `ChipNoGrouping`, `ChipSummary(fn, field)`, `ChipNoSummaries`, `ChipPivot`, `ChipNoPivot`, `ChipVisibility(field, visible)`), English by default; filter operators in the chips use `filterPanelOperators`. `buildAiChips` takes the texts as an optional third argument.

### Performance

- **Select all at 1,000,000 rows takes ~130 ms instead of ~260 ms** (deselect ~95 instead of ~120), with no API change. The selection set built for the new model is reused when that array comes back as `rowSelectionModel` (instead of `new Set(model)` again), and a set built by selecting all rows is known to contain them, so the header checkbox's "all selected" count is skipped. The shared selection set is typed `ReadonlySet` internally, so nothing can change it in place.

### Internal

- **Guards that fail CI instead of relying on review:** `check:versions` (llms.txt, CLAUDE.md and CHANGELOG must name the `package.json` version), `check:compiler` (fails on any React Compiler result outside a short allowlist, so a refactor that stops it from optimizing a hook is caught), ESLint `no-explicit-any` turned on (it was off; the library had none, the demo's 9 are now typed) and a rule against new `as unknown as` casts in the library outside the documented boundary files.

- **Refactors (no behaviour change):** `GridToolbar` split into its panels with a shared `useToolbarPanel` (861 → 492 lines); the exporters one file per format; the xlsx cell helpers in `excelCells`; the cell range drag in `useGridCellRangeDrag`; keydown handling in a plain, unit-tested `handleGridKeyDown` function. Type casts `as unknown as` 43 → 15: the row pipeline, data source and grouping are called with the row type, the row store is typed with it, and column widenings go through one documented helper. DOM, styles and behaviour unchanged (theme baseline and the full browser suite in three engines).

- Demo: the source viewer on every example page is syntax-highlighted (`CodeBlock`, prism-react-renderer), and the integration tips under it are corrected (inline columns and handlers are fine since 3.0 / 3.9; replace rows instead of mutating them).

---

## [3.9.1] — 2026-10-13

### Fixed
Expand Down
11 changes: 9 additions & 2 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.1
- **Current version:** 3.10.0

---

Expand Down Expand Up @@ -157,6 +157,13 @@ The GitHub wiki is generated from `docs/` + `wiki/` by `scripts/build-wiki.mjs`

---

## v3.10.0 — significant changes

- **CI guards** (each fails the build): `check:versions` (`llms.txt` + its demo copy, CLAUDE.md "Current version" and a CHANGELOG entry must match `package.json`; bump them together), `check:compiler` (React Compiler: only the allowlisted `EXPECTED` results in `scripts/check-react-compiler.mjs`), `check:package` (no test artifacts, DataGrid-only size budget), ESLint `no-explicit-any` (now `error`, also in the demo) and `no-restricted-syntax` against `as unknown as` in `lib/` outside the boundary files listed in `eslint.config.js`
- **Types (R4)**: a `GridColDef<R>` passed to an untyped public surface (callback params, toolbar, store) goes through `untypedColumn` / `untypedColumns` (`lib/utils/untypedColumns.ts`), the one place that widening is written; narrowing untyped → `GridColDef<R>` / `R[]` is a plain `as`. `useDataGrid` types `setRows` / `replaceRows` / `rowsLookup` with `R`; call generic hooks with `<R>` explicitly when an untyped argument would otherwise infer `GridRowModel`. Remaining `as unknown as` (15) are synthetic rows, third-party module typings, `window` / CSS custom properties: do not add new ones elsewhere
- R5 splits (no behaviour change): `GridToolbar` panels in `AggregationPanel` / `ColumnsPanelWrapper` / `FilterPanelWrapper` with shared `useToolbarPanel` (portal, position, Escape / outside press; `onClose` through a ref); exporters one file per format (`exportCsv`, `exportExcelHtml`, `exportJson`, `printGrid`, shared `exportRows`; `utils/export/index.ts` only re-exports); xlsx helpers in `excelCells.ts`; the range drag in `useGridCellRangeDrag`, called from inside `useGridCellSelection` (hook order unchanged); keydown handling in `handleGridKeyDown(event, context)` (`hooks/core/gridKeyDown.ts`, a plain function over a typed `GridKeyDownContext`, unit-testable without rendering; `useGridKeyboardNavigation` calls it from a `useCallback` with the same dependencies). A hook that takes refs as parameters must list them in its dependency arrays, or React Compiler skips it (`PreserveManualMemo`)
- Demo code samples go through `demo/components/CodeBlock.tsx` (prism-react-renderer, demo-only devDependency); live output panels (JSON state, logs) stay plain `<pre>`

## 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
Expand Down Expand Up @@ -193,7 +200,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 (backlog) until its overhead is within ~20 %
- The time-sliced pipeline (Option A of `docs/superpowers/specs/2026-10-12-nonblocking-pipeline-proposal.md`) is closed: 1.5–2× slower in total; branch `archive/timesliced-pipeline-2026-10-12` kept for reference

## v3.5.1 — significant changes

Expand Down
10 changes: 10 additions & 0 deletions demo/components/CodeBlock.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
.docs-code-block--highlighted {
padding: 16px;
border-radius: 8px;
overflow-x: auto;
white-space: pre;
font-family: ui-monospace, 'JetBrains Mono', Menlo, monospace;
font-size: 13px;
line-height: 1.55;
tab-size: 4;
}
27 changes: 27 additions & 0 deletions demo/components/CodeBlock.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Highlight, themes } from 'prism-react-renderer';
import './CodeBlock.css';

interface CodeBlockProps {
code: string;
language?: 'tsx' | 'typescript' | 'css' | 'json' | 'bash';
}

/** Syntax-highlighted source (prism-react-renderer, VS Code dark theme) for the demo's code samples. */
export function CodeBlock({ code, language = 'tsx' }: CodeBlockProps) {
return (
<Highlight code={code.trimEnd()} language={language} theme={themes.vsDark}>
{({ className, style, tokens, getLineProps, getTokenProps }) => (
// Prism hands out its theme colours as style objects per line and token.
<pre className={`docs-code-block docs-code-block--highlighted ${className}`} style={style}>
<code>
{tokens.map((line, i) => (
<div key={i} {...getLineProps({ line })}>
{line.map((token, key) => <span key={key} {...getTokenProps({ token })} />)}
</div>
))}
</code>
</pre>
)}
</Highlight>
);
}
23 changes: 9 additions & 14 deletions demo/components/DocsLayout.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import React, { useState } from 'react';
import './DocsLayout.css';
import { CodeBlock } from './CodeBlock';

interface DocsLayoutProps {
title: string;
Expand Down Expand Up @@ -58,9 +59,7 @@ export function DocsLayout({ title, description, children, sourceCode }: DocsLay
</div>
{showSource && (
<div className="docs-source-content">
<pre className="docs-code-block">
<code>{sourceCode}</code>
</pre>
<CodeBlock code={sourceCode} />
</div>
)}
</div>
Expand All @@ -75,23 +74,20 @@ export function DocsLayout({ title, description, children, sourceCode }: DocsLay

<h3 id="dependencies">Dependencies & Imports</h3>
<div className="docs-source-content" style={{ marginTop: 12, marginBottom: 24 }}>
<pre className="docs-code-block" style={{ background: '#1e1e1e', padding: '16px', borderRadius: '8px', color: '#d4d4d4', overflowX: 'auto' }}>
<code>{`// Core imports should exclusively target the published package
<CodeBlock code={`// Core imports should exclusively target the published package
import { DataGrid, GridColDef } from '@opencorestack/opengridx';
import '@opencorestack/opengridx/styles'; // Don't forget the base CSS styles`}</code>
</pre>
import '@opencorestack/opengridx/styles'; // Don't forget the base CSS styles`} />
</div>

<h3 id="best-practices">Integration Best Practices</h3>
<ul style={{ lineHeight: 1.6, color: '#4b5563', paddingLeft: 20 }}>
<li><strong style={{ color: '#111827' }}>Memorization:</strong> Wrap complex data processing or extensive column arrays in <code>useMemo</code> hooks so React does not recreate large references constantly.</li>
<li><strong style={{ color: '#111827' }}>Stable Methods:</strong> Supply stable functional references to event handlers like callbacks (e.g., <code>useCallback</code>) to prevent re-renders when rendering heavily customized cells.</li>
<li><strong style={{ color: '#111827' }}>Performance:</strong> Try to declare <code>columns</code> completely outside the component if it lacks interactive state.</li>
<li><strong style={{ color: '#111827' }}>Rows:</strong> change a row by passing a new object (<code>rows.map(r =&gt; r.id === id ? {'{ ...r, ...changes }'} : r)</code>), never by mutating it: the grid caches per row object.</li>
<li><strong style={{ color: '#111827' }}>Columns and handlers:</strong> inline <code>columns</code> and handlers such as <code>onRowClick</code> are fine. Columns are compared shallowly (a new inline <code>valueGetter</code> function counts as a change), and rows receive the grid's own stable handlers.</li>
<li><strong style={{ color: '#111827' }}>Height:</strong> give the grid a bounded height (a <code>height</code> prop or a sized parent) so it virtualizes. See <em>Performance</em> in the docs.</li>
</ul>

<div className="docs-source-content" style={{ marginTop: 16 }}>
<pre className="docs-code-block" style={{ background: '#1e1e1e', padding: '16px', borderRadius: '8px', color: '#d4d4d4', overflowX: 'auto' }}>
<code>{`// Minimal implementation boilerplate
<CodeBlock code={`// Minimal implementation boilerplate
export default function RobustGrid() {
return (
<DataGrid
Expand All @@ -100,8 +96,7 @@ export default function RobustGrid() {
// Insert your grid props here...
/>
);
}`}</code>
</pre>
}`} />
</div>
</div>
</div>
Expand Down
5 changes: 3 additions & 2 deletions demo/examples/AdvancedFilteringDemo/AdvancedFilteringDemo.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@

import { useState } from 'react';
import { DataGrid, GridToolbar, GridColDef, GridRowModel, GridFilterModel, GridTooltip, exportToCsv, exportToExcel, exportToJson, printGrid } from '@opencorestack/opengridx';
import type { GridApi, GridToolbarSlotProps } from '@opencorestack/opengridx';
import { DocsLayout } from '../../components/DocsLayout';
import './AdvancedFilteringDemo.css';

Expand All @@ -17,7 +18,7 @@ interface Employee extends GridRowModel {
}

interface ExportToolbarProps {
apiRef?: any;
apiRef?: React.RefObject<GridApi | null>;
fallbackRows: Employee[];
fallbackColumns: GridColDef<Employee>[];
}
Expand Down Expand Up @@ -161,7 +162,7 @@ const columns: GridColDef<Employee>[] = [
{ field: 'active', headerName: 'Active', width: 90, type: 'boolean' },
];

function ToolbarWithExport(props: any) {
function ToolbarWithExport(props: GridToolbarSlotProps) {
return (
<GridToolbar
{...props}
Expand Down
2 changes: 1 addition & 1 deletion demo/examples/LoadingStatesDemo/LoadingStatesDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ export default function LoadingStatesDemo() {
loading: false,
error: new Error('Failed to fetch data from the server. Please check your network connection.')
}
} as any}
}}
/>
</div>
</section>
Expand Down
14 changes: 9 additions & 5 deletions demo/examples/ThemingDemo/ThemingDemo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import {
import type { GridTheme } from '@opencorestack/opengridx';
import { generateEmployees } from '../../data/mockData';
import { allColumns } from '../../data/columns';
import type { GridApi, GridRowModel, GridToolbarSlotProps } from '@opencorestack/opengridx';
import { DocsLayout } from '../../components/DocsLayout';
import './ThemingDemo.css';

Expand Down Expand Up @@ -84,9 +85,9 @@ const Icons = {
};

interface ExportToolbarProps {
apiRef?: any;
fallbackRows: any[];
fallbackColumns: GridColDef<any>[];
apiRef?: React.RefObject<GridApi | null>;
fallbackRows: GridRowModel[];
fallbackColumns: GridColDef[];
}

function ExportToolbar({ apiRef, fallbackRows, fallbackColumns }: ExportToolbarProps) {
Expand All @@ -105,7 +106,7 @@ function ExportToolbar({ apiRef, fallbackRows, fallbackColumns }: ExportToolbarP
const selectedIds: (string | number)[] = apiRef?.current?.getSelectedRows?.() || [];
if (selectedIds.length > 0) {
const allRows = getAllRows();
return allRows.filter((r: any) => selectedIds.includes(r.id));
return allRows.filter((r: GridRowModel) => selectedIds.includes(r.id));
}
return getAllRows();
};
Expand Down Expand Up @@ -192,7 +193,10 @@ function ExportToolbar({ apiRef, fallbackRows, fallbackColumns }: ExportToolbarP
// If this were defined inside the component body, every render would create a
// new function reference, causing React to unmount/remount the entire toolbar
// and losing all state (search expansion, filter panel open, etc.).
function ToolbarWithExport(props: any) {
/** The toolbar slot plus the demo's own fallback data, passed through `slotProps.toolbar`. */
type ExportToolbarSlotProps = GridToolbarSlotProps & { _fallbackRows?: GridRowModel[]; _fallbackColumns?: GridColDef[] };

function ToolbarWithExport(props: ExportToolbarSlotProps) {
return (
<GridToolbar
{...props}
Expand Down
4 changes: 2 additions & 2 deletions demo/mockData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ export const mockRows = [
age: 22 + (i % 40),
email: `pro.${i + 13}@opengridx.io`,
phone: `+1-555-${2000 + i}`,
status: i % 3 === 0 ? 'Active' : (i % 3 === 1 ? 'Inactive' : 'Pending') as any,
role: i % 4 === 0 ? 'Admin' : (i % 4 === 1 ? 'User' : (i % 4 === 2 ? 'Moderator' : 'Support')) as any,
status: i % 3 === 0 ? 'Active' : (i % 3 === 1 ? 'Inactive' : 'Pending'),
role: i % 4 === 0 ? 'Admin' : (i % 4 === 1 ? 'User' : (i % 4 === 2 ? 'Moderator' : 'Support')),
salary: 60000 + (Math.random() * 120000),
department: ['Engineering', 'Marketing', 'Sales', 'HR', 'Legal', 'Executives'][i % 6],
country: ['USA', 'UK', 'Germany', 'Japan', 'India', 'Canada'][i % 6],
Expand Down
4 changes: 3 additions & 1 deletion demo/public/llms.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# OpenGridX — AI Agent Context
# Package: @opencorestack/opengridx
# Version: 3.9.1
# Version: 3.10.0
# Docs: https://opencorestack.github.io/OpenGridX/
# This file is machine-readable. AI developers: start here.

Expand Down Expand Up @@ -733,3 +733,5 @@ Arrows move between cells (skipping hidden columns); Home/End, Ctrl+Home/End, Pa
25. **Quick filter text** (3.7.1+): besides the raw value and any `valueFormatter` text, `singleSelect` columns match their option label, `boolean` columns Yes / No, and `date` columns the locale date shown. Exports read values through the same `valueGetter` handling as the grid (a throw exports an empty cell), and CSV / HTML-Excel escape formula text from a `valueFormatter` unless it reads as a plain number.
26. **localeText covers every UI string (3.9+)**: ~200 optional keys (column menu, toolbar, filter / columns / pivot panels, AI panel, pagination, row and header checkboxes, overlays, announcements), English by default. An inline object of strings is fine; define function entries (`rowSelectLabel: id => …`) outside the render so rows stay memoised. Components used outside a grid show English.
27. **Theme keys added in 3.9**: `grid.checkboxCheckedBackground`, `checkboxCheckmark`, `expandIconColor`, `expandIconHoverColor`; `overlays.itemIndicatorColor` (menu selection dot), `itemSelectedIcon`, `iconColor`, `sectionLabelText`, `dividerColor`. The demo's Theme Builder edits all of them.
28. **3.10**: the AI assistant's change chips are `localeText` keys too (`aiAssistantChipSort(field, direction)`, `aiAssistantChipSummary(fn, field)`, …). Select-all at 1M rows takes ~130 ms. The library forbids `as unknown as` outside documented boundaries and `any` everywhere (ESLint), and CI checks that `llms.txt`, CLAUDE.md and the CHANGELOG name the `package.json` version.
29. **Large client-side datasets**: sorting or filtering 1M rows blocks for ~0.4–0.5 s (one native sort). There is no time-sliced mode (measured and rejected: 1.5–2× slower overall). For interactive sorting and filtering at that size use a `dataSource`.
Loading
Loading