Skip to content

Commit cc703fe

Browse files
authored
Merge pull request #6 from OpenCoreStack/release/3.10.0
Release 3.10.0
2 parents 4862db7 + 8283d64 commit cc703fe

72 files changed

Lines changed: 2873 additions & 1962 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/workflows/ci.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ jobs:
3838
- name: Lint
3939
run: npm run lint
4040

41+
- name: Versions agree (llms.txt, CLAUDE.md, CHANGELOG vs package.json)
42+
run: npm run check:versions
43+
44+
- name: React Compiler baseline (no new CompileError or skip)
45+
run: npm run check:compiler
46+
4147
- name: Type-check (lib, tests, demo, e2e)
4248
run: npm run typecheck
4349

‎.github/workflows/npm-publish.yml‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ jobs:
5050
- name: Package contents and size budget
5151
run: npm run check:package
5252

53+
- name: Versions agree (llms.txt, CLAUDE.md, CHANGELOG vs package.json)
54+
run: npm run check:versions
55+
5356
- name: Package smoke suite (packed tarball in React 18 and 19 apps)
5457
run: npm run test:smoke -- --skip-lib
5558

‎CHANGELOG.md‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,26 @@
55

66
---
77

8+
## [3.10.0] — 2026-10-13
9+
10+
### Added
11+
12+
- **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.
13+
14+
### Performance
15+
16+
- **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.
17+
18+
### Internal
19+
20+
- **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.
21+
22+
- **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).
23+
24+
- 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).
25+
26+
---
27+
828
## [3.9.1] — 2026-10-13
929

1030
### Fixed

‎CLAUDE.md‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ This file is auto-loaded by Claude Code and other AI coding assistants. It provi
1414
- **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**.
1515
- **Published:** yes — on npm as `@opencorestack/opengridx` with external consumers. Treat public types and behaviour changes as semver-relevant.
1616
- **Lint:** `npm run lint` (ESLint). Must pass before every commit.
17-
- **Current version:** 3.9.1
17+
- **Current version:** 3.10.0
1818

1919
---
2020

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

158158
---
159159

160+
## v3.10.0 — significant changes
161+
162+
- **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`
163+
- **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
164+
- 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`)
165+
- Demo code samples go through `demo/components/CodeBlock.tsx` (prism-react-renderer, demo-only devDependency); live output panels (JSON state, logs) stay plain `<pre>`
166+
160167
## v3.9.1 — significant changes
161168

162169
- **`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
@@ -193,7 +200,7 @@ The GitHub wiki is generated from `docs/` + `wiki/` by `scripts/build-wiki.mjs`
193200
- **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
194201
- 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
195202
- Header filter strings are in `localeText` (`headerFilter*`, resolved in `lib/utils/headerFilterLocale.ts`)
196-
- 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 %
203+
- 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
197204

198205
## v3.5.1 — significant changes
199206

‎demo/components/CodeBlock.css‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.docs-code-block--highlighted {
2+
padding: 16px;
3+
border-radius: 8px;
4+
overflow-x: auto;
5+
white-space: pre;
6+
font-family: ui-monospace, 'JetBrains Mono', Menlo, monospace;
7+
font-size: 13px;
8+
line-height: 1.55;
9+
tab-size: 4;
10+
}

‎demo/components/CodeBlock.tsx‎

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Highlight, themes } from 'prism-react-renderer';
2+
import './CodeBlock.css';
3+
4+
interface CodeBlockProps {
5+
code: string;
6+
language?: 'tsx' | 'typescript' | 'css' | 'json' | 'bash';
7+
}
8+
9+
/** Syntax-highlighted source (prism-react-renderer, VS Code dark theme) for the demo's code samples. */
10+
export function CodeBlock({ code, language = 'tsx' }: CodeBlockProps) {
11+
return (
12+
<Highlight code={code.trimEnd()} language={language} theme={themes.vsDark}>
13+
{({ className, style, tokens, getLineProps, getTokenProps }) => (
14+
// Prism hands out its theme colours as style objects per line and token.
15+
<pre className={`docs-code-block docs-code-block--highlighted ${className}`} style={style}>
16+
<code>
17+
{tokens.map((line, i) => (
18+
<div key={i} {...getLineProps({ line })}>
19+
{line.map((token, key) => <span key={key} {...getTokenProps({ token })} />)}
20+
</div>
21+
))}
22+
</code>
23+
</pre>
24+
)}
25+
</Highlight>
26+
);
27+
}

‎demo/components/DocsLayout.tsx‎

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React, { useState } from 'react';
22
import './DocsLayout.css';
3+
import { CodeBlock } from './CodeBlock';
34

45
interface DocsLayoutProps {
56
title: string;
@@ -58,9 +59,7 @@ export function DocsLayout({ title, description, children, sourceCode }: DocsLay
5859
</div>
5960
{showSource && (
6061
<div className="docs-source-content">
61-
<pre className="docs-code-block">
62-
<code>{sourceCode}</code>
63-
</pre>
62+
<CodeBlock code={sourceCode} />
6463
</div>
6564
)}
6665
</div>
@@ -75,23 +74,20 @@ export function DocsLayout({ title, description, children, sourceCode }: DocsLay
7574

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

8582
<h3 id="best-practices">Integration Best Practices</h3>
8683
<ul style={{ lineHeight: 1.6, color: '#4b5563', paddingLeft: 20 }}>
87-
<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>
88-
<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>
89-
<li><strong style={{ color: '#111827' }}>Performance:</strong> Try to declare <code>columns</code> completely outside the component if it lacks interactive state.</li>
84+
<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>
85+
<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>
86+
<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>
9087
</ul>
9188

9289
<div className="docs-source-content" style={{ marginTop: 16 }}>
93-
<pre className="docs-code-block" style={{ background: '#1e1e1e', padding: '16px', borderRadius: '8px', color: '#d4d4d4', overflowX: 'auto' }}>
94-
<code>{`// Minimal implementation boilerplate
90+
<CodeBlock code={`// Minimal implementation boilerplate
9591
export default function RobustGrid() {
9692
return (
9793
<DataGrid
@@ -100,8 +96,7 @@ export default function RobustGrid() {
10096
// Insert your grid props here...
10197
/>
10298
);
103-
}`}</code>
104-
</pre>
99+
}`} />
105100
</div>
106101
</div>
107102
</div>

‎demo/examples/AdvancedFilteringDemo/AdvancedFilteringDemo.tsx‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

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

@@ -17,7 +18,7 @@ interface Employee extends GridRowModel {
1718
}
1819

1920
interface ExportToolbarProps {
20-
apiRef?: any;
21+
apiRef?: React.RefObject<GridApi | null>;
2122
fallbackRows: Employee[];
2223
fallbackColumns: GridColDef<Employee>[];
2324
}
@@ -161,7 +162,7 @@ const columns: GridColDef<Employee>[] = [
161162
{ field: 'active', headerName: 'Active', width: 90, type: 'boolean' },
162163
];
163164

164-
function ToolbarWithExport(props: any) {
165+
function ToolbarWithExport(props: GridToolbarSlotProps) {
165166
return (
166167
<GridToolbar
167168
{...props}

‎demo/examples/LoadingStatesDemo/LoadingStatesDemo.tsx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ export default function LoadingStatesDemo() {
9797
loading: false,
9898
error: new Error('Failed to fetch data from the server. Please check your network connection.')
9999
}
100-
} as any}
100+
}}
101101
/>
102102
</div>
103103
</section>

‎demo/examples/ThemingDemo/ThemingDemo.tsx‎

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import type { GridTheme } from '@opencorestack/opengridx';
2626
import { generateEmployees } from '../../data/mockData';
2727
import { allColumns } from '../../data/columns';
28+
import type { GridApi, GridRowModel, GridToolbarSlotProps } from '@opencorestack/opengridx';
2829
import { DocsLayout } from '../../components/DocsLayout';
2930
import './ThemingDemo.css';
3031

@@ -84,9 +85,9 @@ const Icons = {
8485
};
8586

8687
interface ExportToolbarProps {
87-
apiRef?: any;
88-
fallbackRows: any[];
89-
fallbackColumns: GridColDef<any>[];
88+
apiRef?: React.RefObject<GridApi | null>;
89+
fallbackRows: GridRowModel[];
90+
fallbackColumns: GridColDef[];
9091
}
9192

9293
function ExportToolbar({ apiRef, fallbackRows, fallbackColumns }: ExportToolbarProps) {
@@ -105,7 +106,7 @@ function ExportToolbar({ apiRef, fallbackRows, fallbackColumns }: ExportToolbarP
105106
const selectedIds: (string | number)[] = apiRef?.current?.getSelectedRows?.() || [];
106107
if (selectedIds.length > 0) {
107108
const allRows = getAllRows();
108-
return allRows.filter((r: any) => selectedIds.includes(r.id));
109+
return allRows.filter((r: GridRowModel) => selectedIds.includes(r.id));
109110
}
110111
return getAllRows();
111112
};
@@ -192,7 +193,10 @@ function ExportToolbar({ apiRef, fallbackRows, fallbackColumns }: ExportToolbarP
192193
// If this were defined inside the component body, every render would create a
193194
// new function reference, causing React to unmount/remount the entire toolbar
194195
// and losing all state (search expansion, filter panel open, etc.).
195-
function ToolbarWithExport(props: any) {
196+
/** The toolbar slot plus the demo's own fallback data, passed through `slotProps.toolbar`. */
197+
type ExportToolbarSlotProps = GridToolbarSlotProps & { _fallbackRows?: GridRowModel[]; _fallbackColumns?: GridColDef[] };
198+
199+
function ToolbarWithExport(props: ExportToolbarSlotProps) {
196200
return (
197201
<GridToolbar
198202
{...props}

0 commit comments

Comments
 (0)