Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 9 additions & 5 deletions _artifacts/domain_map.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ library:

meta:
generated_by: '@tanstack/intent scaffold domain discovery'
date: '2026-07-10'
date: '2026-07-29'
status: reviewed
maintainer_review_pending: false
phase_4_date: '2026-07-10'
Expand Down Expand Up @@ -892,7 +892,7 @@ skills:
domain: framework-adapters
type: framework
framework: svelte
purpose: 'Create a Svelte 5 table with createTable, rune-backed options, snippets, and headless markup.'
purpose: 'Create a Svelte 5 table with createTable, rune-backed option getters, FlexRender, and headless markup.'
sources:
[
'TanStack/table:docs/framework/svelte/guide/migrating.md',
Expand All @@ -909,16 +909,19 @@ skills:
domain: framework-adapters
type: framework
framework: svelte
purpose: 'Use rune-backed atoms, selected table.state, controlled slices, and external atoms without reactivity mismatches.'
purpose: 'Use rune-aware table atoms and stores, native $derived projections, controlled $state or createTableState slices, and external atoms without broad invalidation or snapshot mismatches.'
sources:
[
'TanStack/table:docs/framework/svelte/guide/table-state.md',
'TanStack/table:docs/framework/svelte/guide/pagination.md',
'TanStack/table:examples/svelte/basic-external-state',
'TanStack/table:packages/svelte-table/src/createTable.svelte.ts',
'TanStack/table:packages/svelte-table/src/createTableState.svelte.ts',
]
failure_modes:
- 'Reading or passing non-reactive snapshots where createTable options need getters over $state or $derived values.'
- 'Keeping the removed beta.58 createTable/createAppTable selector argument, selected table.state property, subscribeTable helper, or SubscribeSource type after beta.59.'
- 'Reading atom or store snapshots outside a tracked Svelte scope and expecting the read itself to keep a consumer reactive.'
- 'Reading table.store.get() in an effect that only needs one atom slice, causing unrelated state changes to rerun the effect.'
- 'Pairing onSliceChange with a controlled state slice that is not actually written back with value-or-updater semantics.'
- 'Blaming pagination reactivity when autoResetPageIndex immediately overwrites an externally requested page.'

Expand All @@ -937,6 +940,7 @@ skills:
failure_modes:
- 'Attempting the v9 adapter migration without first adopting Svelte 5-compatible component and reactivity syntax.'
- 'Keeping readable-store assumptions or v8 row-model table options after switching to createTable.'
- 'Keeping beta.58 creation selectors, table.state, subscribeTable, or their removed selected-state generic parameters after beta.59.'
- 'Missing shared v9 prototype, feature, helper, sorting, and logical-pinning changes.'

- slug: create-table-hook
Expand All @@ -952,7 +956,7 @@ skills:
'TanStack/table:packages/svelte-table/src/createTableHook.svelte.ts',
]
failure_modes:
- 'Defining the hook in a plain module that cannot carry the required Svelte rune semantics.'
- 'Reimplementing createAppTable around the framework-agnostic core instead of using the shipped rune-capable createTableHook implementation.'
- 'Passing $state snapshots instead of getters to createAppTable, freezing data or controlled state.'
- 'Prop-drilling contexts through registered components instead of consuming the typed hooks under matching App wrappers.'

Expand Down
13 changes: 8 additions & 5 deletions _artifacts/skill_spec.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# TanStack Table v9 skill specification

Status: reviewed
Date: 2026-07-10
Library target: TanStack Table v9, authored in stable-release voice
Status: reviewed<br>
Date: 2026-07-29<br>
Library target: TanStack Table v9, authored in stable-release voice<br>
Package metadata target: exact workspace package versions; release automation keeps every shipped skill synchronized

This specification is the generation contract for a deliberately smaller, foot-gun-first TanStack Intent skill set. It is not a documentation outline. The complete evidence and failure-mode inventory is in domain_map.yaml.
Expand Down Expand Up @@ -305,8 +305,11 @@ All Devtools skills must emphasize the required non-empty table options.key, lif
### Svelte

- V9 targets Svelte 5 and runes.
- Read a slice with `table.atoms.<slice>.get()` and the complete state with `table.store.get()` inside templates, `$derived`, `$derived.by`, or `$effect`; reads outside tracked scopes are current snapshots.
- Starting in beta.59, Svelte has no table-creation selector, selected `table.state`, `subscribeTable`, or `SubscribeSource`; use native `$derived` projections instead.
- Prefer `$state` plus getter-backed controlled slices, or `createTableState` for an updater-compatible getter/setter pair. Keep `useSelector` only for raw external atoms consumed outside the table.
- Reactive data and controlled values commonly need getters; avoid passing snapshots.
- createTableHook implementation belongs in a rune-capable module.
- The shipped `createTableHook` implementation supplies rune semantics, so an app hook that calls it may live in a normal `.ts` module.

### Vue

Expand Down Expand Up @@ -342,7 +345,7 @@ All Devtools skills must emphasize the required non-empty table options.key, lif

## Cross-cutting placement rules

- Performance: stable inputs in getting-started/core; selectors in table-state; CSS variables in resizing; measurement/overscan in Virtual; row ownership in client-vs-server.
- Performance: stable inputs in getting-started/core; adapter-specific fine-grained state reads or selectors in table-state; CSS variables in resizing; measurement/overscan in Virtual; row ownership in client-vs-server.
- CSS: pinning, sizing, resizing, and Virtual skills only. Core may state that CSS is user-owned.
- Accessibility: core/getting-started may remind that headless rendering leaves semantics and interaction accessibility with the renderer; do not create a component-library integration skill.
- Query: data source and manual processing boundaries, not Table rendering.
Expand Down
15 changes: 8 additions & 7 deletions _artifacts/skill_tree.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ library:
generated_from:
domain_map: '_artifacts/domain_map.yaml'
skill_spec: '_artifacts/skill_spec.md'
generated_at: '2026-07-10'
generated_at: '2026-07-29'
status: reviewed
reviewed_at: '2026-07-10'
reviewed_at: '2026-07-29'
batch_review: true
structure: 'flat-per-package'
monorepo_layout: true
Expand Down Expand Up @@ -607,7 +607,7 @@ skills:
domain: framework-adapters
path: 'packages/svelte-table/skills/getting-started/SKILL.md'
package: 'packages/svelte-table'
description: 'Create a Svelte 5 TanStack Table v9 table with createTable, explicit tableFeatures, rune-backed data getters, stable static inputs, snippets, and headless markup. Load when replacing createSvelteTable or pre-rune patterns.'
description: 'Create a Svelte 5 TanStack Table v9 table with createTable, explicit tableFeatures, rune-backed data getters, stable static inputs, FlexRender, and headless markup. Load when replacing createSvelteTable or pre-rune patterns.'
requires:
['@tanstack/table-core#core', '@tanstack/table-core#table-features']
sources:
Expand All @@ -621,21 +621,22 @@ skills:
domain: framework-adapters
path: 'packages/svelte-table/skills/table-state/SKILL.md'
package: 'packages/svelte-table'
description: 'Use Svelte 5 rune-backed table.atoms/store and selected table.state, reactive option getters, controlled $state slices, value-or-updater callbacks, external atoms, and auto-reset behavior without snapshot mismatches.'
description: 'Use Svelte 5 rune-aware table atoms and stores, $derived projections, reactive option getters, controlled $state or createTableState slices, external atoms, and auto-reset behavior without broad invalidation or snapshot mismatches.'
requires: ['@tanstack/table-core#core', 'getting-started']
sources:
- 'TanStack/table:docs/framework/svelte/guide/table-state.md'
- 'TanStack/table:docs/framework/svelte/guide/pagination.md'
- 'TanStack/table:examples/svelte/basic-external-state'
- 'TanStack/table:packages/svelte-table/src/createTable.svelte.ts'
- 'TanStack/table:docs/framework/svelte/guide/pagination.md'
- 'TanStack/table:packages/svelte-table/src/createTableState.svelte.ts'

- name: 'Svelte Migrate v8 to v9'
slug: migrate-v8-to-v9
type: lifecycle
domain: framework-adapters
path: 'packages/svelte-table/skills/migrate-v8-to-v9/SKILL.md'
package: 'packages/svelte-table'
description: 'Migrate Svelte v8/store-oriented tables to the Svelte 5 v9 createTable adapter, runes, explicit feature and row-model slots, current rendering, prototype methods, sortFn names, helpers, and logical pinning.'
description: 'Complete Svelte v8-to-v9 migration reference: Svelte 5, createTable, beta.59 selector removal, explicit features and row-model slots, atom/rune state, rendering helpers, prototype methods, type generics, sorting, sizing, selection, and logical pinning.'
requires:
[
'@tanstack/table-core#migrate-v8-to-v9',
Expand All @@ -653,7 +654,7 @@ skills:
domain: framework-adapters
path: 'packages/svelte-table/skills/create-table-hook/SKILL.md'
package: 'packages/svelte-table'
description: 'Define a Svelte createAppTable/createAppColumnHelper in a rune-capable module with shared features/defaults, reactive per-table getters, optional App component registries, and typed table/cell/header context hooks.'
description: "Define a Svelte createAppTable/createAppColumnHelper using the adapter's rune-capable createTableHook implementation, shared features/defaults, reactive per-table getters, optional App component registries, and typed table/cell/header context hooks."
requires: ['@tanstack/table-core#core', 'getting-started', 'table-state']
sources:
- 'TanStack/table:docs/framework/svelte/guide/composable-tables.md'
Expand Down
1 change: 0 additions & 1 deletion docs/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,6 @@
{ "label": "createTableState", "to": "framework/svelte/reference/functions/createTableState" },
{ "label": "SvelteTable", "to": "framework/svelte/reference/type-aliases/SvelteTable" },
{ "label": "AppSvelteTable", "to": "framework/svelte/reference/type-aliases/AppSvelteTable" },
{ "label": "subscribeTable", "to": "framework/svelte/reference/functions/subscribeTable" },
{ "label": "renderComponent", "to": "framework/svelte/reference/functions/renderComponent" },
{ "label": "renderSnippet", "to": "framework/svelte/reference/functions/renderSnippet" },
{ "label": "FlexRender", "to": "framework/svelte/reference/variables/FlexRender" }
Expand Down
36 changes: 15 additions & 21 deletions docs/framework/svelte/guide/cell-selection.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,20 +38,27 @@ The cell selection feature keeps track of spreadsheet-style rectangular selectio

The table instance already manages the cell selection state for you. You can access the selection or values derived from it through a few APIs.

- `table.state.cellSelection` - returns the cell selection state reactively (selected by the `createTable` selector)
- `table.atoms.cellSelection.get()` - returns the current cell selection state and participates in Svelte tracking when read in a template or rune
- `getSelectedCellCount()` - returns how many cells are selected
- `getSelectedCellIds()` - returns the ids of every selected cell
- `getCellSelectionRowIds()` / `getCellSelectionColumnIds()` - returns the rows and columns the selection touches
- `getSelectedCellRangesData()` - returns each selected rectangle's values as a row-major grid

```ts
console.log(table.state.cellSelection) //get the cell selection state
console.log(table.atoms.cellSelection.get()) //get the cell selection state
console.log(table.getSelectedCellCount()) //3
console.log(table.getSelectedCellIds()) //['0_firstName', '0_lastName', '1_firstName']
console.log(table.getSelectedCellRangesData()) //[[['Tanner', 'Linsley'], ['Kevin', 'Vandy']]]
```

In event handlers or other non-render code, you can also read the current snapshot with `table.atoms.cellSelection.get()`. This read does not subscribe a component to future changes, so prefer `table.state.cellSelection` in render positions.
Use the same atom read in markup or a native derived value:

```ts
const cellSelection = $derived(table.atoms.cellSelection.get())
const selectedRangeCount = $derived(cellSelection.length)
```

Outside a tracked context, `table.atoms.cellSelection.get()` is simply the current snapshot.

The expansion APIs (`getSelectedCellIds`, `getSelectedCellRangesData`) are memoized and pull-based. They cost nothing unless you actually call them, so a table that only highlights cells never pays to enumerate a large selection.

Expand Down Expand Up @@ -313,8 +320,8 @@ Because a reorder can widen a selection onto columns the user never picked, some
let isFirstColumnLayout = true

$effect(() => {
// read the atoms, not table.state: the selector rebuilds that object on every
// selected slice change, so reading it here would re-fire on every selection
// Read only the layout atoms. A full table.store.get() read would re-run for
// every table state change, including selection changes.
void table.atoms.columnOrder.get()
void table.atoms.columnPinning.get()
void table.atoms.columnVisibility.get()
Expand Down Expand Up @@ -343,21 +350,8 @@ const table = createTable({

### Performance

Svelte's runes track these reads and the compiler updates only the DOM nodes
that actually changed, so the example renders its cells plainly.
Svelte's runes track atom reads and the compiler updates only the DOM nodes that actually changed, so the example renders its cells plainly.

Measured on a table with a thousand rows and twelve columns, a drag updates in
roughly 16ms per move with plain reads.

`subscribeTable` is available if you want an explicit fine-grained subscription:

```svelte
const selected = subscribeTable( table.atoms.cellSelection, (ranges) =>
ranges.length, ) // read it as selected.current
```
Measured on a table with a thousand rows and twelve columns, a drag updates in roughly 16ms per move with plain reads.

One trap worth knowing: do not read `table.state` inside an `$effect` that also
writes selection state. The selector rebuilds that object whenever any selected
slice changes, so an effect meant to watch the column layout will re-fire on
every selection change and clear the selection you just made. Read
`table.atoms.<slice>.get()` instead.
One trap worth knowing: do not read `table.store.get()` inside an `$effect` that also writes selection state unless the effect truly depends on every state slice. An effect meant to watch column layout would otherwise re-run on every selection change and clear the selection you just made. Read only the required `table.atoms.<slice>.get()` values instead.
9 changes: 6 additions & 3 deletions docs/framework/svelte/guide/column-filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ Since the column filter state is an array of objects, you can have multiple colu

#### Accessing Column Filter State

For reactive reads that should update your UI, use `table.state.columnFilters` (selected by the `createTable` selector) or `subscribeTable`. In event handlers or other non-reactive code, you can read the current snapshot with `table.atoms.columnFilters.get()`, but this read only participates in Svelte dependency tracking when called in a rune-tracked context.
Read column filters from `table.atoms.columnFilters.get()`. The call returns the current value in event handlers and becomes reactive when it runs in a template, `$derived`, `$derived.by`, or `$effect`.

```ts
const table = createTable({
Expand All @@ -148,8 +148,11 @@ const table = createTable({
//...
})

table.state.columnFilters // reactive read
table.atoms.columnFilters.get() // snapshot read in event handlers
const columnFilters = $derived(table.atoms.columnFilters.get())

const logCurrentColumnFilters = () => {
console.log(table.atoms.columnFilters.get())
}
```

However, if you need access to the column filter state outside of the table, you can "control" the column filter state like down below.
Expand Down
4 changes: 2 additions & 2 deletions docs/framework/svelte/guide/column-resizing.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ TanStack Table keeps track of a `columnResizing` state object that you can use t
<div
class="resize-indicator"
style:transform={header.column.getIsResizing()
? `translateX(${table.state.columnResizing.deltaOffset ?? 0}px)`
? `translateX(${table.atoms.columnResizing.get().deltaOffset ?? 0}px)`
: ''}
></div>
```
Expand Down Expand Up @@ -253,4 +253,4 @@ Svelte 5's fine-grained reactivity means a resize does not re-render whole compo
1. **Derive all column widths in one `$derived` value.** Build a single style string that maps each header and column id to a CSS variable, then bind it on the `<table>` element (`<table style={tableStyle}>`). `header.getSize()` reads the rune-backed `columnSizing` atom, so a drag re-runs only this derived and its single attribute effect.
2. **Reference the variables in cell styles** (`width: calc(var(--col-firstName-size) * 1px)`) so the browser applies new widths without Svelte re-evaluating each cell.

Because nothing in the table body reads resize state, no body memoization is needed, and you do not need a `createTable` selector for this pattern; the resizer's highlight is its own inline binding.
Because nothing in the table body reads resize state, no body memoization is needed; the resizer's highlight is its own narrow inline atom binding.
10 changes: 3 additions & 7 deletions docs/framework/svelte/guide/composable-tables.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,13 +276,9 @@ Create each table with `createAppTable`. In Svelte 5, pass reactive data through
debugTable: true,
})
let sorting = $derived(table.state.sorting)
let columnFilters = $derived(table.state.columnFilters)
const rows = $derived.by(() => {
JSON.stringify(table.state)
return table.getRowModel().rows
})
const sorting = $derived(table.atoms.sorting.get())
const columnFilters = $derived(table.atoms.columnFilters.get())
const rows = $derived(table.getRowModel().rows)
</script>
```

Expand Down
4 changes: 2 additions & 2 deletions docs/framework/svelte/guide/custom-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -330,10 +330,10 @@ const table = createTable({

### Step 5: Use the Feature in Your Application

Now that the feature is added to the table instance, you can use the new instance APIs, options, and state in your application. `table.state.density` reads the new state slice reactively in your markup, and `table.setDensity` / `table.toggleDensity` update it.
Now that the feature is added to the table instance, you can use the new instance APIs, options, and state in your application. `table.atoms.density.get()` reads the new state slice reactively in your markup, and `table.setDensity` / `table.toggleDensity` update it.

```svelte
{@const density = table.state.density}
{@const density = table.atoms.density.get()}
<td
style:padding={density === 'sm' ? '4px' : density === 'md' ? '8px' : '16px'}
style:transition="padding 0.2s"
Expand Down
Loading
Loading