Data table fed with columns and rows; values are always rendered as text.
Built-in loading, empty and error states; sorting; row click; row actions; pagination; CSV export.
Data flow: set rows (and total when paginating on the server) every time new data arrives;
the table re-renders from scratch, so updating many times or replacing with [] is safe. Setting
properties before the component has loaded is safe too. null / undefined cells render empty.
Pages are 1-based. Server-side tables keep page, page size, sort and filters in one state object,
reload on ab-page-change / ab-sort-change / ab-filter-change, and load with
abLoad(table, () => get_rows(state)) (loading, error, retry and stale responses handled;
a Python function may return { rows, total }).