@tanstack/angular-table 9.0.0-alpha.9 → 9.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/README.md +127 -0
  2. package/dist/README.md +127 -0
  3. package/dist/fesm2022/tanstack-angular-table-static-functions.mjs +6 -0
  4. package/dist/fesm2022/tanstack-angular-table-static-functions.mjs.map +1 -0
  5. package/dist/fesm2022/tanstack-angular-table.mjs +1336 -239
  6. package/dist/fesm2022/tanstack-angular-table.mjs.map +1 -1
  7. package/dist/types/tanstack-angular-table-static-functions.d.ts +1 -0
  8. package/dist/types/tanstack-angular-table.d.ts +793 -0
  9. package/package.json +39 -19
  10. package/skills/angular/angular-rendering-directives/SKILL.md +415 -0
  11. package/skills/angular/angular-rendering-directives/references/content-shapes.md +142 -0
  12. package/skills/angular/angular-rendering-directives/references/create-table-hook-registries.md +89 -0
  13. package/skills/angular/angular-rendering-directives/references/di-tokens.md +171 -0
  14. package/skills/angular/angular-rendering-directives/references/flex-render-component-options.md +64 -0
  15. package/skills/angular/client-to-server/SKILL.md +467 -0
  16. package/skills/angular/compose-with-tanstack-query/SKILL.md +482 -0
  17. package/skills/angular/compose-with-tanstack-store/SKILL.md +397 -0
  18. package/skills/angular/compose-with-tanstack-virtual/SKILL.md +400 -0
  19. package/skills/angular/getting-started/SKILL.md +496 -0
  20. package/skills/angular/getting-started/references/feature-row-model-mapping.md +48 -0
  21. package/skills/angular/migrate-v8-to-v9/SKILL.md +419 -0
  22. package/skills/angular/migrate-v8-to-v9/references/v8-to-v9-mapping.md +261 -0
  23. package/skills/angular/production-readiness/SKILL.md +469 -0
  24. package/skills/angular/table-state/SKILL.md +429 -0
  25. package/skills/angular/table-state/references/external-atoms-and-app-hook.md +152 -0
  26. package/src/flex-render/context.ts +14 -0
  27. package/src/flex-render/flags.ts +34 -0
  28. package/src/flex-render/flexRenderComponent.ts +288 -0
  29. package/src/flex-render/flexRenderComponentFactory.ts +251 -0
  30. package/src/flex-render/renderer.ts +393 -0
  31. package/src/flex-render/view.ts +226 -0
  32. package/src/flexRender.ts +124 -0
  33. package/src/helpers/cell.ts +104 -0
  34. package/src/helpers/createTableHook.ts +499 -0
  35. package/src/helpers/flexRenderCell.ts +136 -0
  36. package/src/helpers/header.ts +99 -0
  37. package/src/helpers/table.ts +85 -0
  38. package/src/index.ts +23 -70
  39. package/src/injectTable.ts +212 -0
  40. package/src/{lazy-signal-initializer.ts → lazySignalInitializer.ts} +2 -2
  41. package/src/reactivity.ts +105 -0
  42. package/static-functions/index.ts +1 -0
  43. package/static-functions/ng-package.json +6 -0
  44. package/dist/esm2022/flex-render.mjs +0 -148
  45. package/dist/esm2022/index.mjs +0 -48
  46. package/dist/esm2022/lazy-signal-initializer.mjs +0 -43
  47. package/dist/esm2022/proxy.mjs +0 -83
  48. package/dist/esm2022/tanstack-angular-table.mjs +0 -5
  49. package/dist/flex-render.d.ts +0 -30
  50. package/dist/index.d.ts +0 -5
  51. package/dist/lazy-signal-initializer.d.ts +0 -5
  52. package/dist/proxy.d.ts +0 -3
  53. package/src/flex-render.ts +0 -184
  54. package/src/proxy.ts +0 -97
@@ -0,0 +1,496 @@
1
+ ---
2
+ name: angular/getting-started
3
+ description: >
4
+ End-to-end first-table journey for TanStack Table v9 in Angular: install
5
+ `@tanstack/angular-table`, declare `features` with `tableFeatures()`, register row-model
6
+ factories under `rowModels` with explicit `*Fns` parameters, build columns with the
7
+ `TFeatures, TData` generic order, call `injectTable(() => ({...}))` from an injection context,
8
+ and render with `FlexRender` / `*flexRenderHeader` / `*flexRenderCell` / `*flexRenderFooter`.
9
+ Covers the minimum-viable signal-backed table plus the upgrade path to sorting + filtering +
10
+ pagination.
11
+ type: lifecycle
12
+ library: tanstack-table
13
+ framework: angular
14
+ library_version: '9.0.0-alpha.48'
15
+ requires:
16
+ - angular/table-state
17
+ - angular/angular-rendering-directives
18
+ - setup
19
+ - column-definitions
20
+ sources:
21
+ - TanStack/table:docs/framework/angular/angular-table.md
22
+ - TanStack/table:docs/framework/angular/guide/table-state.md
23
+ - TanStack/table:docs/framework/angular/guide/rendering.md
24
+ - TanStack/table:packages/angular-table/src/injectTable.ts
25
+ - TanStack/table:examples/angular/basic-inject-table/
26
+ - TanStack/table:examples/angular/basic-app-table/
27
+ ---
28
+
29
+ # Getting Started — Angular Table v9
30
+
31
+ > Goal: from zero to a working signal-backed, sorted + paginated, type-safe
32
+ > table in Angular ≥19.
33
+ >
34
+ > v9 is **explicit**: tell the table which features you want with `features`,
35
+ > tell it which row models you want with `rowModels`. That explicitness is what
36
+ > makes the v9 bundle tree-shakeable.
37
+
38
+ ---
39
+
40
+ ## 1. Install
41
+
42
+ ```bash
43
+ pnpm add @tanstack/angular-table
44
+ # or npm / yarn / bun
45
+ ```
46
+
47
+ Requires Angular ≥19 (signal APIs, `input()`, structural directive metadata).
48
+ Standalone components are assumed.
49
+
50
+ ---
51
+
52
+ ## 2. The simplest possible table (core only)
53
+
54
+ ```ts
55
+ // app.ts
56
+ import { ChangeDetectionStrategy, Component, signal } from '@angular/core'
57
+ import {
58
+ FlexRender,
59
+ injectTable,
60
+ tableFeatures,
61
+ type ColumnDef,
62
+ } from '@tanstack/angular-table'
63
+
64
+ type Person = {
65
+ id: string
66
+ firstName: string
67
+ lastName: string
68
+ age: number
69
+ }
70
+
71
+ // 1. features OUTSIDE the component class (stable reference)
72
+ const features = tableFeatures({}) // empty = core row model only
73
+
74
+ // 2. columns OUTSIDE the component class (stable reference)
75
+ const columns: Array<ColumnDef<typeof features, Person>> = [
76
+ {
77
+ accessorKey: 'firstName',
78
+ header: 'First name',
79
+ cell: (info) => info.getValue(),
80
+ },
81
+ {
82
+ accessorKey: 'lastName',
83
+ header: 'Last name',
84
+ cell: (info) => info.getValue(),
85
+ },
86
+ {
87
+ accessorKey: 'age',
88
+ header: () => 'Age',
89
+ cell: (info) => info.getValue(),
90
+ },
91
+ ]
92
+
93
+ @Component({
94
+ selector: 'app-root',
95
+ imports: [FlexRender], // tuple imports BOTH directives
96
+ templateUrl: './app.html',
97
+ changeDetection: ChangeDetectionStrategy.OnPush,
98
+ })
99
+ export class App {
100
+ readonly data = signal<Array<Person>>([
101
+ { id: '1', firstName: 'Ada', lastName: 'Lovelace', age: 36 },
102
+ { id: '2', firstName: 'Alan', lastName: 'Turing', age: 41 },
103
+ ])
104
+
105
+ // 3. injectTable in an injection context (a class field qualifies)
106
+ readonly table = injectTable(() => ({
107
+ features, // required in v9
108
+ rowModels: {}, // {} = core only; that's fine
109
+ columns, // stable ref
110
+ data: this.data(), // signal read → re-syncs the table on change
111
+ }))
112
+ }
113
+ ```
114
+
115
+ ```html
116
+ <!-- app.html -->
117
+ <table>
118
+ <thead>
119
+ @for (headerGroup of table.getHeaderGroups(); track headerGroup.id) {
120
+ <tr>
121
+ @for (header of headerGroup.headers; track header.id) {
122
+ <th>
123
+ @if (!header.isPlaceholder) {
124
+ <ng-container *flexRenderHeader="header; let value">
125
+ {{ value }}
126
+ </ng-container>
127
+ }
128
+ </th>
129
+ }
130
+ </tr>
131
+ }
132
+ </thead>
133
+
134
+ <tbody>
135
+ @for (row of table.getRowModel().rows; track row.id) {
136
+ <tr>
137
+ @for (cell of row.getVisibleCells(); track cell.id) {
138
+ <td>
139
+ <ng-container *flexRenderCell="cell; let value">
140
+ {{ value }}
141
+ </ng-container>
142
+ </td>
143
+ }
144
+ </tr>
145
+ }
146
+ </tbody>
147
+ </table>
148
+ ```
149
+
150
+ That's a complete v9 table. No sorting, no pagination — just `<table>` markup
151
+ driven by the row model.
152
+
153
+ ### What the boilerplate is doing
154
+
155
+ - `tableFeatures({})` registers no opt-in features. The core row model
156
+ (`getRowModel()`) is always available. With `features: tableFeatures({})`,
157
+ `table.atoms.*` only contains the slices core ships with — no `pagination`,
158
+ no `sorting`, no `rowSelection` until you add the matching features.
159
+ - `rowModels: {}` does not register any feature-specific row models. Core
160
+ row model is included automatically.
161
+ - `injectTable(() => ({...}))` runs the initializer, builds the table, and
162
+ re-runs the initializer whenever any signal read inside changes. Stable
163
+ references outside the initializer keep `columns` / `features` / `rowModels`
164
+ from getting recreated on every data update.
165
+
166
+ ---
167
+
168
+ ## 3. Add a feature — sorting
169
+
170
+ Each opt-in feature has two pieces in v9:
171
+
172
+ 1. The **feature** itself (`rowSortingFeature`) in `features` — adds APIs
173
+ like `column.toggleSorting()` and the `sorting` state slice.
174
+ 2. The **row-model factory** (`createSortedRowModel(sortFns)`) in `rowModels`
175
+ — produces the sorted output. Without it, `table.getRowModel().rows` is
176
+ unsorted regardless of sort state.
177
+
178
+ ```ts
179
+ import {
180
+ injectTable,
181
+ tableFeatures,
182
+ rowSortingFeature,
183
+ createSortedRowModel,
184
+ sortFns,
185
+ type ColumnDef,
186
+ } from '@tanstack/angular-table'
187
+
188
+ const features = tableFeatures({
189
+ rowSortingFeature,
190
+ })
191
+
192
+ readonly table = injectTable(() => ({
193
+ features,
194
+ rowModels: {
195
+ sortedRowModel: createSortedRowModel(sortFns), // <-- enables sorting output
196
+ },
197
+ columns,
198
+ data: this.data(),
199
+ }))
200
+ ```
201
+
202
+ In the template, drive sorting from the header:
203
+
204
+ ```html
205
+ @if (!header.isPlaceholder) {
206
+ <th
207
+ (click)="header.column.toggleSorting()"
208
+ [style.cursor]="header.column.getCanSort() ? 'pointer' : ''"
209
+ >
210
+ <ng-container *flexRenderHeader="header; let value">{{ value }}</ng-container>
211
+ @switch (header.column.getIsSorted()) { @case ('asc') { ▲ } @case ('desc') { ▼
212
+ } }
213
+ </th>
214
+ }
215
+ ```
216
+
217
+ > **Use `column.toggleSorting()`, not your own sort handler.** It correctly
218
+ > handles the asc → desc → unsorted cycle. Same applies for every other
219
+ > feature.
220
+
221
+ `sortFns` is the registry of built-in sort functions
222
+ (`alphanumeric`, `basic`, `datetime`, etc.). Pass only the ones you use to
223
+ tree-shake (`createSortedRowModel({ basic: sortFns.basic })`), or pass `sortFns`
224
+ in its entirety for all of them.
225
+
226
+ ---
227
+
228
+ ## 4. Add filtering + pagination
229
+
230
+ ```ts
231
+ import {
232
+ injectTable,
233
+ tableFeatures,
234
+ rowSortingFeature,
235
+ columnFilteringFeature,
236
+ rowPaginationFeature,
237
+ createSortedRowModel,
238
+ createFilteredRowModel,
239
+ createPaginatedRowModel,
240
+ sortFns,
241
+ filterFns,
242
+ type ColumnDef,
243
+ } from '@tanstack/angular-table'
244
+
245
+ const features = tableFeatures({
246
+ rowSortingFeature,
247
+ columnFilteringFeature,
248
+ rowPaginationFeature,
249
+ })
250
+
251
+ readonly table = injectTable(() => ({
252
+ features,
253
+ rowModels: {
254
+ sortedRowModel: createSortedRowModel(sortFns),
255
+ filteredRowModel: createFilteredRowModel(filterFns),
256
+ paginatedRowModel: createPaginatedRowModel(),
257
+ },
258
+ columns,
259
+ data: this.data(),
260
+ initialState: {
261
+ pagination: { pageIndex: 0, pageSize: 10 },
262
+ },
263
+ }))
264
+ ```
265
+
266
+ Pagination controls — again, prefer the table APIs:
267
+
268
+ ```html
269
+ <button (click)="table.previousPage()" [disabled]="!table.getCanPreviousPage()">
270
+
271
+ </button>
272
+ <span>
273
+ Page {{ table.atoms.pagination.get().pageIndex + 1 }} of {{
274
+ table.getPageCount() }}
275
+ </span>
276
+ <button (click)="table.nextPage()" [disabled]="!table.getCanNextPage()">
277
+
278
+ </button>
279
+
280
+ <select
281
+ [value]="table.atoms.pagination.get().pageSize"
282
+ (change)="table.setPageSize(Number($any($event.target).value))"
283
+ >
284
+ @for (size of [10, 20, 50]; track size) {
285
+ <option [value]="size">{{ size }}</option>
286
+ }
287
+ </select>
288
+ ```
289
+
290
+ Reading state in the template via `table.atoms.<slice>.get()` is signal-backed
291
+ — Angular tracks it and re-renders on change.
292
+
293
+ ---
294
+
295
+ ## 5. Use the column helper for safer types
296
+
297
+ `createColumnHelper<TFeatures, TData>()` (generic order: features first!) gives
298
+ type-safe accessor / display / group definitions, plus a `columns(...)` method
299
+ for better inference across heterogeneous columns:
300
+
301
+ ```ts
302
+ import { createColumnHelper } from '@tanstack/angular-table'
303
+
304
+ const columnHelper = createColumnHelper<typeof features, Person>()
305
+
306
+ const columns = columnHelper.columns([
307
+ columnHelper.accessor('firstName', {
308
+ header: 'First name',
309
+ cell: (info) => info.getValue(),
310
+ }),
311
+ columnHelper.accessor((row) => `${row.firstName} ${row.lastName}`, {
312
+ id: 'fullName',
313
+ header: 'Full name',
314
+ }),
315
+ columnHelper.display({
316
+ id: 'actions',
317
+ header: 'Actions',
318
+ cell: ({ row }) => `Edit #${row.original.id}`,
319
+ }),
320
+ ])
321
+ ```
322
+
323
+ > v9 changed the generic order: `createColumnHelper<typeof features, Person>()`,
324
+ > **not** `createColumnHelper<Person>()`. Same for `ColumnDef<typeof features, Person>`.
325
+
326
+ If multiple components share the same `features` / `rowModels`, factor them
327
+ into a `createTableHook(...)` call — see
328
+ `tanstack-table/angular/angular-rendering-directives` §10 and the
329
+ `composable-tables` example.
330
+
331
+ ---
332
+
333
+ ## 6. Stable row identity — set `getRowId`
334
+
335
+ If your rows have a primary key, set `getRowId`. This makes row selection,
336
+ row pinning, and refetch-based updates correct.
337
+
338
+ ```ts
339
+ readonly table = injectTable(() => ({
340
+ features,
341
+ rowModels: { /* … */ },
342
+ columns,
343
+ data: this.data(),
344
+ getRowId: (row) => row.id, // ← stable ID across re-fetches
345
+ }))
346
+ ```
347
+
348
+ Without `getRowId`, the row index becomes the ID — selection state
349
+ ("rows 0–4 selected") survives sorting but breaks across server refetches that
350
+ return rows in a new order.
351
+
352
+ ---
353
+
354
+ ## 7. State ownership — start with internal, hoist when you need to
355
+
356
+ The simplest table lets TanStack Table own all state internally. You set
357
+ starting values with `initialState`, and you use APIs like `table.nextPage()`
358
+ and `table.setSorting(...)` to drive updates.
359
+
360
+ ```ts
361
+ readonly table = injectTable(() => ({
362
+ features,
363
+ rowModels: { /* … */ },
364
+ columns,
365
+ data: this.data(),
366
+ initialState: {
367
+ pagination: { pageIndex: 0, pageSize: 25 },
368
+ sorting: [{ id: 'age', desc: true }],
369
+ },
370
+ }))
371
+ ```
372
+
373
+ Hoist a slice into an Angular signal only when something outside the table
374
+ needs to read or react to it (URL sync, debounced server fetch, persistence,
375
+ cross-component coordination). The pattern is `state` + `on[State]Change` →
376
+ see `tanstack-table/angular/table-state` §6.
377
+
378
+ For full server-driven tables, see `tanstack-table/angular/client-to-server`.
379
+
380
+ ---
381
+
382
+ ## Failure modes
383
+
384
+ ### 1. (CRITICAL) Calling `injectTable` outside an injection context
385
+
386
+ `injectTable` calls `assertInInjectionContext`. It must be invoked from a
387
+ class-field initializer, constructor, or factory inside a DI scope. Calling it
388
+ from a service method or a `setTimeout` callback throws:
389
+
390
+ > `NG0203: inject() must be called from an injection context...`
391
+
392
+ If you need to construct a table from a service method, capture the injector
393
+ and use `runInInjectionContext(injector, () => injectTable(...))`.
394
+
395
+ ### 2. (CRITICAL) Hallucinating v8 `createAngularTable` or `getCoreRowModel()`
396
+
397
+ ```ts
398
+ // ❌ v8
399
+ import { createAngularTable, getCoreRowModel } from '@tanstack/angular-table'
400
+
401
+ // ✅ v9
402
+ import { injectTable, tableFeatures } from '@tanstack/angular-table'
403
+ ```
404
+
405
+ There is no `getCoreRowModel()` / `getSortedRowModel()` / `getFilteredRowModel()`
406
+ in v9. Core row model is automatic; the rest are
407
+ `createSortedRowModel(sortFns)` / `createFilteredRowModel(filterFns)` / etc.
408
+ registered under `rowModels`.
409
+
410
+ ### 3. (CRITICAL) Reimplementing what the table API already does
411
+
412
+ Telltale AI signs in a getting-started snippet:
413
+
414
+ - Custom `sortBy()` on the data signal instead of `table.setSorting()` /
415
+ `column.toggleSorting()`.
416
+ - Manual `pageIndex` math instead of `table.nextPage()` / `table.getCanNextPage()`.
417
+ - Computing `getCanNextPage()` as `pageIndex < Math.ceil(rows / pageSize) - 1`
418
+ instead of asking the table.
419
+ - Manual filtering of the data array before passing it to the table when you
420
+ could just register `columnFilteringFeature` + `createFilteredRowModel`.
421
+
422
+ The table already does all of this. Use it.
423
+
424
+ ### 4. (HIGH) Feature without its row model (or vice versa)
425
+
426
+ ```ts
427
+ // ❌ rowSortingFeature without createSortedRowModel → sort state changes, rows don't reorder
428
+ features: tableFeatures({ rowSortingFeature })
429
+ rowModels: {
430
+ }
431
+
432
+ // ✅
433
+ rowModels: {
434
+ sortedRowModel: createSortedRowModel(sortFns)
435
+ }
436
+ ```
437
+
438
+ Full mapping table → [`references/feature-row-model-mapping.md`](references/feature-row-model-mapping.md).
439
+
440
+ ### 5. (HIGH) Declaring `columns` / `features` / `rowModels` inside the initializer
441
+
442
+ ```ts
443
+ // ❌ Recreated on every signal change
444
+ readonly table = injectTable(() => ({
445
+ features: tableFeatures({ rowSortingFeature }),
446
+ rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
447
+ columns: [/* … */],
448
+ data: this.data(),
449
+ }))
450
+
451
+ // ✅ Stable references outside, signal reads inside
452
+ const features = tableFeatures({ rowSortingFeature })
453
+ const columns: Array<ColumnDef<typeof features, Person>> = [/* … */]
454
+
455
+ readonly table = injectTable(() => ({
456
+ features,
457
+ rowModels: { sortedRowModel: createSortedRowModel(sortFns) },
458
+ columns,
459
+ data: this.data(),
460
+ }))
461
+ ```
462
+
463
+ ### 6. (HIGH) Importing a non-existent `flexRender` function
464
+
465
+ In Angular, `FlexRender` is a directive tuple, not a function. There is no
466
+ `flexRender(fn, ctx)` call expression — that's the React/Vue API. Always:
467
+
468
+ ```ts
469
+ import { FlexRender } from '@tanstack/angular-table'
470
+ @Component({ imports: [FlexRender] })
471
+ ```
472
+
473
+ and use `*flexRenderCell` / `*flexRenderHeader` / `*flexRenderFooter` in the
474
+ template.
475
+
476
+ Lower-severity failure modes (MEDIUM: `createColumnHelper` generic-order flip,
477
+ importing only `FlexRenderDirective` without the shorthand) →
478
+ [`references/feature-row-model-mapping.md`](references/feature-row-model-mapping.md#lower-severity-failure-modes-medium).
479
+
480
+ ---
481
+
482
+ ## References
483
+
484
+ - [Feature → row-model mapping table and MEDIUM failure modes](references/feature-row-model-mapping.md)
485
+
486
+ ---
487
+
488
+ ## See also
489
+
490
+ - `tanstack-table/angular/table-state` — state model, ownership, controlled vs internal
491
+ - `tanstack-table/angular/angular-rendering-directives` — full rendering API surface
492
+ - `tanstack-table/angular/migrate-v8-to-v9` — for projects upgrading from v8
493
+ - `tanstack-table/angular/client-to-server` — flipping a working table to a server endpoint
494
+ - `tanstack-table/angular/production-readiness` — tree-shaking, stable refs, selectors
495
+ - Example: `examples/angular/basic-inject-table/`
496
+ - Example: `examples/angular/basic-app-table/` (uses `createTableHook`)
@@ -0,0 +1,48 @@
1
+ # Feature → Row Model Mapping & Optional Patterns
2
+
3
+ Reference material extracted from the getting-started SKILL.md.
4
+
5
+ ---
6
+
7
+ ## Feature → row model mapping
8
+
9
+ Every opt-in v9 feature has two pieces:
10
+
11
+ 1. The **feature** itself in `features` — adds APIs (e.g.
12
+ `column.toggleSorting()`) and the matching state slice.
13
+ 2. A **row-model factory** in `rowModels` — produces the derived row output.
14
+ Without it, sort/filter/paginate UI updates but rows don't reorder.
15
+
16
+ | Feature | Row model needed |
17
+ | ---------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
18
+ | `rowSortingFeature` | `sortedRowModel: createSortedRowModel(sortFns)` |
19
+ | `columnFilteringFeature` / `globalFilteringFeature` | `filteredRowModel: createFilteredRowModel(filterFns)` |
20
+ | `rowPaginationFeature` | `paginatedRowModel: createPaginatedRowModel()` |
21
+ | `rowExpandingFeature` | `expandedRowModel: createExpandedRowModel()` |
22
+ | `columnGroupingFeature` | `groupedRowModel: createGroupedRowModel(aggregationFns)` |
23
+ | `columnFacetingFeature` | `facetedRowModel: createFacetedRowModel()` (+ `facetedMinMaxValues` / `facetedUniqueValues`) |
24
+ | `rowSelectionFeature` | (no row model needed) |
25
+ | `columnVisibilityFeature` / `columnOrderingFeature` / `columnPinningFeature` / `columnSizingFeature` / `columnResizingFeature` / `rowPinningFeature` | (no row model needed) |
26
+
27
+ ---
28
+
29
+ ## Lower-severity failure modes (MEDIUM)
30
+
31
+ ### Wrong `createColumnHelper` generic order
32
+
33
+ ```ts
34
+ // ❌ v8 shape
35
+ const columnHelper = createColumnHelper<Person>()
36
+
37
+ // ✅ v9 — features first
38
+ const columnHelper = createColumnHelper<typeof features, Person>()
39
+ ```
40
+
41
+ Or use `createAppColumnHelper<Person>()` from a `createTableHook(...)` factory,
42
+ which pre-binds `TFeatures`.
43
+
44
+ ### Importing only `FlexRenderDirective` and missing the shorthand
45
+
46
+ `FlexRender` is preferred — it imports both `FlexRenderDirective` (the long
47
+ `*flexRender`) and `FlexRenderCell` (the shorthand). If you only import one,
48
+ `*flexRenderCell` won't compile.