@signalsafe/data-table
v0.1.1
Published
Shared strict AG Grid tables with host-owned transports and React Query lifecycle
Readme
SignalSafe data-table
@signalsafe/data-table 0.1.1 is the shared-table release approved on 2026-10-21. One AG Grid Community component for server pages and complete client datasets. Hosts own their React Query client, validated transport, authorization, mutations and regional settings. No root barrel, API client, expression evaluator or product-specific routes are included.
Use
Import DataTable from @signalsafe/data-table/DataTable and @signalsafe/data-table/theme.css once. Import types and constants directly from configuration, query/invalidation helpers from query, and theme types from theme. React 18, React DOM 18, React Query 5 and matching AG Grid Community/React 35 or 36 instances must resolve from the host. The package never constructs a QueryClient.
import { DataTable } from '@signalsafe/data-table/DataTable';
import { DATA_MODE, FILTER_OPERATOR } from '@signalsafe/data-table/configuration';
import type { ServerAdapter, TableConfig } from '@signalsafe/data-table/configuration';
import '@signalsafe/data-table/theme.css';
interface Person {
id: string;
name: string;
}
const config: TableConfig<Person> = {
id: 'people',
caption: 'People',
getRowId: (row) => row.id,
columns: [
{
id: 'name',
label: 'Name',
kind: 'text',
value: (row) => row.name,
operators: [FILTER_OPERATOR.contains, FILTER_OPERATOR.equals],
},
],
defaultSort: [{ column: 'name', direction: 'asc' }],
stableSortColumn: 'id',
pageSize: 20,
pageSizes: [20, 50, 100],
selection: 'none',
};
// These functions belong to the host's validated API adapter.
const adapter: ServerAdapter<Person> = {
kind: DATA_MODE.server,
key: 'people-v1',
read: (query, signal) => personApi.readTable(query, signal),
decode: (cached) => personApi.decodeTableCache(cached),
};
<DataTable
config={config}
adapter={adapter}
queryClient={hostQueryClient}
scope={{ application: 'my-app', user: user.id, company: company.id }}
/>;For fully loaded data use { kind: DATA_MODE.client, key: 'all-people-revision-1', rows: people }. This is a completeness assertion by the host: do not pass one server page or a growing pagination buffer as a client dataset. Client filtering and sorting run before slicing. Server adapters receive the full query and must implement it before pagination; the AG Grid view cannot locally sort/filter the current server page.
Configuration and behavior
Columns have stable IDs, typed value access, optional formatting/rendering, explicitly supported operators and optional integer constraints. Row/toolbar actions carry labels, icons and host permission decisions. Action callbacks remain in the host; mutates: true invalidates only that application/user/company/table after successful completion. The package rechecks the latest row and permission at click time.
Filters debounce for 350ms and reset pagination on commit; numeric zero, false, empty text and null have distinct operators. Sorting appends the declared stable tie-breaker. Query caches include application, user, company, table, adapter context and canonical query; inactive cached entries are bounded and expire. Signals reach adapter requests; inactive/unmounted consumers cancel reads, and obsolete responses do not replace a newer query.
Known totals drive exact ranges/pages; unknown totals display Total unknown and use the adapter's explicit hasNext. A deleted final row moves to the surviving final page. Refresh retains filters, sorting and page size. Loading, refreshing, empty, no-match and retryable error states remain distinct.
Selection and export distinguish current page, explicit IDs across pages and the complete matching query with exclusions. ExportRequest.pageRows is only the loaded page. Hosts resolve selected IDs or all matching records through their own validated export capability; never relabel page rows as all matching. Export requests receive an AbortSignal.
All visible strings and operator labels can be replaced with typed messages. Regional number/date callbacks belong to the host; the client date evaluator uses the supplied timezone. Theme tokens cover font, color, spacing, density, icon tones, borders and focus. Header/row/action dimensions respond to text sizing. Native controls provide labels, titles and visible focus; table content may scroll horizontally at narrow widths.
Preferences are opt-in through an injected store. Reads validate version, table/scope, allowed page sizes, sortable column IDs and optional query fields. Defaults persist only page size and sorting. persistQuery: true explicitly enables validated filters and page; no row payload is persisted. The host chooses storage; the package falls back safely when optional storage reads or writes fail.
See adoption contract and pilot evidence. Local verification is not registry publication or independent review approval.
Verification
npm run test:coverage measures the existing and added control, query-lifecycle, permission and persisted-state suites. The reporter includes all runtime TypeScript and authored scripts; scripts executed by separate package/build gates remain unmeasured in this Vitest report. Sonar also analyzes the workflow and Vitest configuration, including uncovered code rather than excluding it. The measured baseline is not a new coverage threshold.
CI tests the matching AG Grid Community/React 35.1.0 and 36.1.0 peer pairs on Node 24, then checks strict types, lint, architecture, formatting, build and packed imports. The scanner waits for the existing quality gate on trusted repository branches; fork pull requests run the package checks without receiving analysis credentials. CI analysis requires Automatic Analysis to be disabled for this Sonar project. These checks do not adopt the separate canonical pilot or replace its host/browser verification.
Release preparation
Release 0.1.1 combines the installed Step 5 features and verified audit fixes. Public distribution was approved on 2026-10-21 and the package remains UNLICENSED; see RELEASING.md for approval, peer-matrix, archive, and host verification requirements.
Selection and detail workflows
Set config.selection: 'multiple' to render accessible row checkboxes in the grid. initialSelection hydrates uncontrolled selection once. selection controls the current TableSelection; onSelection proposes changes and the host supplies the next value. Hosts own pending/applied state, URL persistence, authorization and confirmations. Explicit IDs survive page navigation. Uncontrolled selection resets when sorting or filters change; controlled explicit IDs remain host-owned. Matching selection applies only to the filter/sort identity in its query; exclusions survive page changes and cannot leak into a different query. Select current page adds its IDs to the existing explicit set. Clear selection removes both IDs and exclusions.
selectedRowId marks a domain-selected row independently of bulk selection. onActivate responds to Enter and double click; choose activateOn: 'click' for single click. Buttons, links and form controls never trigger row activation, and inactive tables do not activate. refreshKey triggers a read of the current server query when its value changes; changes while inactive refresh on reactivation. onRefresh optionally replaces the shared Refresh action with a host-owned reload workflow (including complete client arrays); failures and asynchronous busy state use the shared action handling. Hosts notify a server table afterward with refreshKey or scoped invalidation. Without this callback, Refresh rereads the current server query. Host mutation callbacks can continue using action mutates: true and scoped invalidateTable.
detail: { expandedIds, render } adds full-width rows after their parents. Content height is measured by the package, updates on resizing, and observers disconnect on removal. nestedRows(parent) supplies already validated child rows displayed after that parent and its detail; child rows use the same typed columns/actions and may have details. Parent pagination, totals, filtering and sorting remain adapter-owned; child rows do not increase the page count. Child IDs must identify the domain records uniquely for selection/expansion. Hosts own expansion state and child fetching/cancellation. Nested DataTable instances inside detail content retain their own typed adapters and scopes. Columns may declare group: {id,label}; consecutive columns in the same group share an accessible heading while keeping column filters aligned.
Pagination provides first/previous/next/last and numbered page buttons. Unknown totals disable Last; Next follows the adapter’s hasNext result, and numbered buttons cover pages already reached. Clear filters is always accessible in the top-right toolbar, including nonempty results.
Domain content and focus
emptyContent supplies a genuine product action for an unfiltered empty dataset; it is hidden during loading, failures, nonempty results and filtered empty results. Column description supplies shared header help. The package names the actual grid and connects keyboard instructions through aria-describedby; hosts may localize messages.instructions.
Stable cell renderer components preserve domain opener focus during host rerenders. Cached pages are decoded again when their observed cache revision changes, including updates that retain the same object reference. Host-owned client tables may use onRefresh to reload their complete validated dataset; server tables retain the default refetch when no callback is supplied.
toolbarContent places genuine domain controls (for example an About dialog opener) alongside shared toolbar actions. Hosts retain their product callback/content; the package owns the header, action alignment and shared button styling.
Row actions wrap within their column when necessary. Bootstrap dark body colors keep neutral header text readable; explicit header text can be set with theme.headerForeground. Saved action colors remain controlled through the shared theme tokens.
Saved appearance preferences
Import TableThemeProvider from @signalsafe/data-table/TableThemeProvider and
appearancePreferences from @signalsafe/data-table/theme. Hosts decode saved
{ preset, colorScheme, iconSet } strings with appearancePreferences, then pass
the resulting typed theme to the provider around their application. Supported
presets are Quartz, Alpine, Balham and Material. Supported color schemes are
light, dark, variable, light-warm, light-cold, dark-warm and dark-blue; icon sets
are Bootstrap, Quartz, Material, Alpine, Quartz Bold, Quartz Light and Quartz
Regular. Unknown saved values use Quartz Light with Bootstrap icons. Explicit
table-local theme values override inherited values; appearance changes preserve
query, page and selection state. No engine configuration escapes this API.
Row and toolbar action icons remain shared Bootstrap SVGs. The selected engine icon set controls sort indicators; Bootstrap is the default. Light uses the shared neutral palette and Bootstrap semantic colors; other selected schemes use their named palette, with explicit color overrides taking precedence.
The footer shows only the centered page indicator (Page 2/6). Page-size
selection and navigation share the right group. Narrow screens stack these
groups while keeping the page indicator centered.
All table captions, including Lessons and Scenarios, use the same package-owned 1.5rem heading with regular weight and shared top-left alignment.
See the current release documentation for release-specific compatibility and verification boundaries.
Static semantic tables
For a small, complete host-rendered summary, StaticTable shares the table appearance without creating an AG Grid or query lifecycle. It accepts normal table attributes and children; the host supplies semantic headings and rows. Import each declaring subpath explicitly:
import { StaticTable } from '@signalsafe/data-table/StaticTable';
import { TableThemeProvider } from '@signalsafe/data-table/TableThemeProvider';
import { appearancePreferences } from '@signalsafe/data-table/theme';
export function Summary() {
return (
<TableThemeProvider
theme={appearancePreferences({
preset: 'quartz',
colorScheme: 'light',
iconSet: 'bootstrap',
})}
>
<StaticTable aria-label='Summary'>
<thead>
<tr>
<th scope='col'>Item</th>
<th scope='col'>Count</th>
</tr>
</thead>
<tbody>
<tr>
<th scope='row'>Complete</th>
<td>3</td>
</tr>
</tbody>
</StaticTable>
</TableThemeProvider>
);
}Use DataTable for validated complete datasets or server pages requiring shared sorting, filters, selection or pagination. StaticTable does not infer those capabilities from its children.
