ADR-0010: AG Grid Community + an in-house enterprise-grid subsystem
Status: Accepted (retroactive) Date: 2026-07-24
Context
The product is table-heavy — dozens of pages are essentially a data grid with server-side pagination, filtering, tabs, saved views, bulk actions, and import/export. AG Grid Community gives the rendering engine and the infinite row model for free, but the "enterprise app" features live in AG Grid Enterprise, a paid per-developer license.
Constraints that argued against buying it:
- The needed features (toolbar, tab counts, saved views, bulk status changes, CSV/PDF export) are app-specific workflows, not raw grid features — even Enterprise would need substantial wrapping.
- A license cost per developer sits badly with a portfolio/bootstrap project and with per-client stack economics (ADR-0007).
- Server-side filtering/sorting was going to be custom anyway, because the
backend speaks Spring Data
Specifications, not AG Grid's Enterprise server-side row model protocol.
Decision
Stay on ag-grid-community + ag-grid-react (v33) — see
frontend/package.json; no Enterprise modules —
and build the missing layer in-house under
frontend/src/ui/ag-grid/ (28 files):
ServerDataGrid.tsx— the core wrapper: Community's infinite row model driven by the backend's page/filter/sort API.useEnterpriseGrid.tsx— the one hook a page calls to get toolbar (search, view options, page size), tabs with counts (TabbedServerGrid.tsx), saved views, bulk action bar, import modal, and CSV/PDF export (csv.ts,pdf.ts).- Support modules:
filterUtils.ts(AG Grid filter-model → API params, unit tested),columnTypes.ts/EnterpriseGridColumn.ts(typed column defs),theme.ts, card-view alternative (CardGrid.tsx/ViewToggle.tsx).
All 30+ table pages across HR, payroll, and recruitment sit on this subsystem rather than on raw AG Grid.
Consequences
Positive
- Zero license cost, and features are exactly shaped to the app (saved views persist to the backend, bulk actions call domain endpoints, tab counts come from the same filtered queries the grid uses).
- One idiom for every table page — a retrofit took JobRequisitions from 641 to 383 lines — and one place to fix grid bugs for all pages at once.
- No vendor protocol between frontend and backend: the grid speaks the same pageable/filter API as everything else.
Negative
- A ~28-file subsystem is now permanent in-house infrastructure with one team behind it — every grid bug, a11y gap, or feature request lands on us, with no vendor support channel.
- Enterprise-only features stay off the table: no row grouping, no pivoting, no native Excel export (we export CSV and tabular PDF instead), no clipboard ranges. If a client demands pivot tables, this ADR gets revisited.
- AG Grid upgrade risk concentrates in the wrapper: v33 internals (infinite row model callbacks, filter model shapes, theming API) are load- bearing across the subsystem, so a major AG Grid upgrade is a subsystem migration, not a version bump.
References
../../frontend/src/ui/ag-grid/— the subsystem../../frontend/src/ui/ag-grid/ServerDataGrid.tsx,../../frontend/src/ui/ag-grid/useEnterpriseGrid.tsx../../frontend/package.json— Community packages only../architecture.md— §2