Skip to content

fix(table-core): wire expansion auto-reset into the core row model and guard first runs - #6499

Merged
KevinVandy merged 1 commit into
betafrom
fix/auto-reset-first-run-guard
Aug 2, 2026
Merged

fix(table-core): wire expansion auto-reset into the core row model and guard first runs#6499
KevinVandy merged 1 commit into
betafrom
fix/auto-reset-first-run-guard

Conversation

@KevinVandy

@KevinVandy KevinVandy commented Aug 2, 2026

Copy link
Copy Markdown
Member

🎯 Changes

Auto-resets are onAfterUpdate hooks on the row model stage memos. Two related defects made them fire in the wrong places.

Expansion auto-reset was wired to the wrong stage (#5801)

table_autoResetExpanded was only called from createGroupedRowModel, so autoResetExpanded silently did nothing for tables that use expansion without grouping. It is now wired from createCoreRowModel alongside the existing pageIndex, sorting, and cell-selection resets, so a data reference change resets expansion regardless of which features are installed.

table_autoResetExpanded gained the same if (!table.atoms.expanded) return guard that the sorting and cell-selection resets already use, since the core row model also runs on tables without the expanding feature.

Auto-resets fired on first render (#5968)

memo seeds its dependency list to [], so the first computation of every stage always compares as changed. Merely reading a row model on mount scheduled resets for pageIndex, sorting, cell selection, and (for grouped tables) expansion.

For uncontrolled tables this was self-cancelling, since the reset targets initialState — which is why it was hard to reproduce and why the examples all looked fine. It was visible in two cases:

  • a seeded initialState.pagination.pageIndex was wiped to 0 on mount
  • controlled consumers received unsolicited onExpandedChange / onPaginationChange calls on mount, clobbering state restored from a URL or storage

v7 and v8 suppressed the first run with a registered flag inside each feature's createTable closure. The v9 rewrite dropped it. A new skipFirstRun util restores that suppression:

export function skipFirstRun(fn: () => void): () => void {
  let hasRun = false
  return () => {
    if (!hasRun) {
      hasRun = true
      return
    }
    fn()
  }
}

It is applied in the row model factories, which already run once per table, so each table instance gets its own flag. The grouped row model already tracked its previous inputs and only needed its first-run condition inverted. The worker bridge reports every stage as changed in its first response, so it carries an equivalent hasAppliedResults flag.

Landing order

These ship together deliberately. Wiring expansion into the core row model without the first-run guard would extend the mount-time wipe from grouped tables to every table using expansion — which is why #6443 was held back. This PR supersedes that one and includes an equivalent regression test.

Changes

table_autoResetPageIndex continues to reset to page 0 rather than initialState.pageIndex; that semantic is unchanged and now pinned by test.

Examples

Bundled from parallel work on the same tree:

  • migrate the basic examples to createColumnHelper across all 10 framework adapters
  • document more table options inline as commented-out defaults across the expanding, row-pinning, row-selection, aggregation, and column-sizing examples
  • replace the filterFns.between workaround in the React expanding example with the filterFn_between individual export, which already existed

Verification

  • table-core: 62 files, 1,236 tests passed
  • pnpm test:pr: eslint, sherif, knip, test:lib, test:types, test:build, build across 405 projects passed
  • rebased onto latest beta and re-verified

✅ Checklist

  • I have followed the steps in the Contributing guide.
  • I have tested this code locally with pnpm test:pr.

Closes #5801
Closes #5968
Supersedes #6443

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Prevented automatic pagination and expanded-row resets during a table’s initial load.
    • Preserved initial state and avoided triggering controlled callbacks on mount, while retaining resets after subsequent data or feature changes.
    • Improved behavior for worker-driven table updates and tables without expanded-row state.
  • Documentation

    • Expanded examples across supported frameworks with guidance for filtering, aggregation, row selection, row pinning, column sizing, visibility, resizing, and grouping.
    • Updated basic table examples to demonstrate typed column helpers.

…d guard first runs

Auto-resets are `onAfterUpdate` hooks on the row model stage memos. Two
related defects made them fire in the wrong places.

Expansion auto-reset was only wired from the grouped row model, so
`autoResetExpanded` silently did nothing for tables that use expansion
without grouping (#5801). It is now wired from `createCoreRowModel`
alongside the existing pageIndex, sorting, and cell-selection resets, so
a data reference change resets expansion regardless of which features are
installed. `table_autoResetExpanded` gained the same feature guard the
sorting and cell-selection resets already use, since the core row model
runs on tables without the expanding feature.

Auto-resets also fired on the very first computation of every stage
(#5968). `memo` seeds its dependency list to `[]`, so the initial run
always compares as changed, and merely reading a row model on mount
scheduled resets. For uncontrolled tables this was self-cancelling (the
reset targets `initialState`), which is why it went unnoticed, but it
wiped a seeded `initialState.pagination.pageIndex` and pushed unsolicited
`onExpandedChange` / `onPaginationChange` calls at controlled consumers on
mount. v7 and v8 suppressed the first run with a `registered` flag; the
v9 rewrite dropped it.

A new `skipFirstRun` util restores that suppression. It is applied in the
row model factories, which already run once per table, so each table gets
its own flag. The grouped row model tracked previous inputs already and
only needed its first-run condition inverted. The worker bridge reports
every stage as changed in its first response, so it carries an equivalent
`hasAppliedResults` flag.

Landing order matters: wiring expansion into the core row model without
the first-run guard would have extended the mount-time wipe from grouped
tables to every table using expansion, so both halves ship together.

- fix: wire `table_autoResetExpanded` into `createCoreRowModel` (#5801)
- fix: skip auto-resets on the first computation of each stage (#5968)
- add `skipFirstRun` util; apply to core, filtered, and sorted row models
- invert the grouped row model's first-run condition
- guard `table_autoResetExpanded` when the expanding feature is absent
- skip auto-resets on the worker bridge's first applied result
- tests: expansion reset without the grouping feature; first-run guard
  suite covering seeded initial state and controlled-state consumers

Examples: migrate the basic examples to `createColumnHelper` across all
frameworks, document more table options inline as commented-out defaults,
and replace the `filterFns.between` workaround in the React expanding
example with the `filterFn_between` individual export that already
existed.

Closes #5801
Closes #5968
Supersedes #6443

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@nx-cloud

nx-cloud Bot commented Aug 2, 2026

Copy link
Copy Markdown

View your CI Pipeline Execution ↗ for commit 1189c33

Command Status Duration Result
nx affected --targets=test:eslint,test:sherif,t... ✅ Succeeded 7m 48s View ↗
nx run-many --targets=build --exclude=examples/** ✅ Succeeded 56s View ↗

☁️ Nx Cloud last updated this comment at 2026-08-02 22:10:05 UTC

@coderabbitai

coderabbitai Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

The change updates table-core auto-reset behavior, prevents first-result worker resets, adds Angular expanding-example filters, and refreshes table configuration examples across supported frameworks.

Changes

Table auto-reset behavior

Layer / File(s) Summary
Initial auto-reset guards
packages/table-core/src/utils.ts, packages/table-core/src/core/..., packages/table-core/src/features/..., packages/table-core/tests/...
Initial row-model computation no longer triggers automatic resets. Later data, grouping, sorting, and filtering changes still reset the applicable state. Tests cover expanded-state and pagination behavior.
Worker result handling
packages/table-core/src/worker/createTableWorker.ts
The worker bridge tracks applied results and skips expanded-row and page-index resets for the first result.

Angular expanding filters

Layer / File(s) Summary
Filtering controls and handlers
examples/angular/expanding/src/app/app.ts, examples/angular/expanding/src/app/app.html
The example registers string and numeric filtering, renders matching controls, and updates text or numeric filter bounds.

Framework examples

Layer / File(s) Summary
Typed column-helper examples
examples/*/basic-*/...
Alpine, Angular, Octane, Preact, React, Solid, Svelte, and Vue examples replace manual ColumnDef arrays with typed column helpers while preserving columns and rendering.
Configuration documentation examples
examples/*/aggregation/..., examples/*/column-sizing/..., examples/*/expanding/..., examples/*/row-pinning/..., examples/*/row-selection/..., examples/react/column-*/..., examples/react/filters/..., examples/react/grouping/...
Examples document manual aggregation, sizing limits, filtering, row selection, row pinning, column pinning, resizing, visibility, and grouping options. Existing runtime settings remain unchanged unless noted.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the core auto-reset wiring and first-run guard changes.
Linked Issues check ✅ Passed The changes address expansion resets after data changes and prevent unintended initial resets described in [#5801] and [#5968].
Out of Scope Changes check ✅ Passed The example migrations, documentation updates, core changes, and regression tests align with the stated pull request objectives.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/auto-reset-first-run-guard

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@pkg-pr-new

pkg-pr-new Bot commented Aug 2, 2026

Copy link
Copy Markdown
More templates

@tanstack/alpine-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/alpine-table@6499

@tanstack/angular-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/angular-table@6499

@tanstack/angular-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/angular-table-devtools@6499

@tanstack/ember-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/ember-table@6499

@tanstack/lit-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/lit-table@6499

@tanstack/match-sorter-utils

npm i https://pkg.pr.new/TanStack/table/@tanstack/match-sorter-utils@6499

@tanstack/octane-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/octane-table@6499

@tanstack/preact-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/preact-table@6499

@tanstack/preact-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/preact-table-devtools@6499

@tanstack/react-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/react-table@6499

@tanstack/react-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/react-table-devtools@6499

@tanstack/solid-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/solid-table@6499

@tanstack/solid-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/solid-table-devtools@6499

@tanstack/svelte-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/svelte-table@6499

@tanstack/table-core

npm i https://pkg.pr.new/TanStack/table/@tanstack/table-core@6499

@tanstack/table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/table-devtools@6499

@tanstack/vue-table

npm i https://pkg.pr.new/TanStack/table/@tanstack/vue-table@6499

@tanstack/vue-table-devtools

npm i https://pkg.pr.new/TanStack/table/@tanstack/vue-table-devtools@6499

commit: 1189c33

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/angular/row-pinning/src/app/app.ts`:
- Around line 76-79: Align the row-pinning example’s initial state with its
controlled state: update the row-pinning source used by this.rowPinning() to
initialize with the documented row IDs, or remove the controlled rowPinning
state so initialState.rowPinning takes effect. Preserve the intended
first-render pinning behavior and avoid leaving conflicting controlled and
initial row-pinning configurations.

In `@examples/ember/row-pinning/app/templates/application.gts`:
- Around line 117-120: Update the initial row-pinning example in the application
template so it is executable: either remove the controlled empty rowPinning
state and enable initialState.rowPinning, or initialize the controlled state
with the row IDs generated by getRowId. Ensure the pinned IDs match the actual
getRowId output rather than hard-coded '0' and '1'.

In `@examples/octane/row-pinning/src/main.tsrx`:
- Around line 164-165: Align the commented initialState.rowPinning example with
the controlled state by initializing controlled state.rowPinning with top ID "0"
and bottom ID "1", so uncommenting the option preserves the demonstrated pinned
rows on first render.

In `@examples/react/row-pinning/src/main.tsx`:
- Around line 163-166: Update the initialState configuration in
examples/react/row-pinning/src/main.tsx lines 163-166,
examples/preact/row-pinning/src/main.tsx lines 162-165, and
examples/solid/row-pinning/src/App.tsx lines 141-144 to seed rowPinning with top
row "0" and bottom row "1", while preserving the existing pagination settings
and documentation.

In `@examples/svelte/row-pinning/src/App.svelte`:
- Around line 92-95: Update the controlled row-pinning state in
createTableState<RowPinningState> to initialize top with ['0'] and bottom with
['1']; do not rely on initialState.rowPinning, since
table.options.state.rowPinning controls the first render.

In `@packages/table-core/src/core/row-models/createCoreRowModel.ts`:
- Around line 31-36: Update the onAfterUpdate callback in createCoreRowModel so
data updates no longer request table_autoResetExpanded when the
grouped-row-model path will also reset it. Preserve table_autoResetExpanded in
createGroupedRowModel for grouping or filtering changes that are not already
reset by the core stage, while keeping the other core reset calls unchanged.

In `@packages/table-core/src/worker/createTableWorker.ts`:
- Around line 75-76: Update the worker termination loop in terminate() to reset
hasAppliedResults to false for each retained bridge before or alongside
terminating it, ensuring the recreated worker treats its first result as the
initial result.
- Around line 256-260: Update the change tracking in the worker result
application flow around isFirstAppliedResult so core-stage changes are tracked
separately from grouped-stage changes. Ensure expanded state is reset when
either the core stage or grouped stage changes, while preserving the
first-applied-result guard and existing auto-reset behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: ad769a99-aa14-4d3c-84a0-6ac29e01464b

📥 Commits

Reviewing files that changed from the base of the PR and between bd622ee and 1189c33.

📒 Files selected for processing (74)
  • examples/alpine/aggregation/src/main.ts
  • examples/alpine/basic-create-table/index.html
  • examples/alpine/basic-create-table/src/main.ts
  • examples/alpine/column-sizing/src/main.ts
  • examples/alpine/expanding/src/main.ts
  • examples/alpine/row-pinning/src/main.ts
  • examples/alpine/row-selection/src/main.ts
  • examples/angular/aggregation/src/app/app.ts
  • examples/angular/basic-inject-table/src/app/app.ts
  • examples/angular/column-sizing/src/app/app.ts
  • examples/angular/expanding/src/app/app.html
  • examples/angular/expanding/src/app/app.ts
  • examples/angular/row-pinning/src/app/app.ts
  • examples/angular/row-selection/src/app/app.ts
  • examples/ember/aggregation/app/templates/application.gts
  • examples/ember/column-sizing/app/templates/application.gts
  • examples/ember/expanding/app/templates/application.gts
  • examples/ember/row-pinning/app/templates/application.gts
  • examples/ember/row-selection/app/templates/application.gts
  • examples/lit/aggregation/src/main.ts
  • examples/lit/column-sizing/src/main.ts
  • examples/lit/expanding/src/main.ts
  • examples/lit/row-pinning/src/main.ts
  • examples/lit/row-selection/src/main.ts
  • examples/octane/aggregation/src/main.tsrx
  • examples/octane/basic-use-table/src/main.tsrx
  • examples/octane/column-sizing/src/main.tsrx
  • examples/octane/expanding/src/main.tsrx
  • examples/octane/row-pinning/src/main.tsrx
  • examples/octane/row-selection/src/main.tsrx
  • examples/preact/aggregation/src/main.tsx
  • examples/preact/basic-use-table/src/main.tsx
  • examples/preact/column-sizing/src/main.tsx
  • examples/preact/expanding/src/main.tsx
  • examples/preact/row-pinning/src/main.tsx
  • examples/preact/row-selection/src/main.tsx
  • examples/react/aggregation/src/main.tsx
  • examples/react/basic-use-table/src/main.tsx
  • examples/react/column-pinning/src/main.tsx
  • examples/react/column-resizing/src/main.tsx
  • examples/react/column-sizing/src/main.tsx
  • examples/react/column-visibility/src/main.tsx
  • examples/react/expanding/src/main.tsx
  • examples/react/filters/src/main.tsx
  • examples/react/grouping/src/main.tsx
  • examples/react/row-pinning/src/main.tsx
  • examples/react/row-selection/src/main.tsx
  • examples/solid/aggregation/src/App.tsx
  • examples/solid/basic-use-table/src/App.tsx
  • examples/solid/column-sizing/src/App.tsx
  • examples/solid/expanding/src/App.tsx
  • examples/solid/row-pinning/src/App.tsx
  • examples/solid/row-selection/src/App.tsx
  • examples/svelte/aggregation/src/App.svelte
  • examples/svelte/basic-create-table/src/App.svelte
  • examples/svelte/column-sizing/src/App.svelte
  • examples/svelte/expanding/src/App.svelte
  • examples/svelte/row-pinning/src/App.svelte
  • examples/svelte/row-selection/src/App.svelte
  • examples/vanilla/aggregation/src/main.ts
  • examples/vue/aggregation/src/App.vue
  • examples/vue/basic-use-table/src/App.tsx
  • examples/vue/column-sizing/src/App.vue
  • examples/vue/expanding/src/App.tsx
  • examples/vue/row-pinning/src/App.tsx
  • examples/vue/row-selection/src/App.vue
  • packages/table-core/src/core/row-models/createCoreRowModel.ts
  • packages/table-core/src/features/column-filtering/createFilteredRowModel.ts
  • packages/table-core/src/features/column-grouping/createGroupedRowModel.ts
  • packages/table-core/src/features/row-expanding/rowExpandingFeature.utils.ts
  • packages/table-core/src/features/row-sorting/createSortedRowModel.ts
  • packages/table-core/src/utils.ts
  • packages/table-core/src/worker/createTableWorker.ts
  • packages/table-core/tests/implementation/core/autoReset.test.ts

Comment on lines +76 to +79
initialState: {
pagination: { pageSize: 20, pageIndex: 0 },
// rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file info =="
fd -a 'app\.ts$' examples/angular/row-pinning/src/app 2>/dev/null || true

echo "== git status/stat =="
git diff --stat HEAD~1..HEAD 2>/dev/null || git diff --stat || true

echo "== relevant lines =="
for f in $(fd 'app\.ts$' examples/angular/row-pinning/src/app 2>/dev/null); do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,140p' "$f" | nl -ba | sed -n '50,110p'
done

echo "== rowPinning occurrences =="
rg -n "rowPinning|row-pinning|pin rows|initialState|state:" examples/angular/row-pinning/src/app examples/angular/row-pinning 2>/dev/null || true

Repository: TanStack/table

Length of output: 5005


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "== file info =="
fd -a 'app\.ts$' examples/angular/row-pinning/src/app 2>/dev/null || true

echo "== git status/stat =="
git diff --stat HEAD~1.. is-available 2>/dev/null || git diff --stat || true

echo "== relevant lines =="
for f in $(fd 'app\.ts$' examples/angular/row-pinning/src/app 2>/dev/null); do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,140p' "$f" | nl -ba | sed -n '50,110p'
done

echo "== rowPinning occurrences =="
rg -n "rowPinning|row-pinning|pin rows|initialState|state:" examples/angular/row-pinning/src/app examples/angular/row-pinning 2>/dev/null || true

Repository: TanStack/table

Length of output: 411


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f="examples/angular/row-pinning/src/app/app.ts"

echo "== app.ts lines 50-110 =="
awk 'NR>=50 && NR<=110 { printf "%4d  %s\n", NR, $0 }' "$f"

echo "== all rowPinning/initialState state occurrences =="
rg -n "rowPinning|row-pinning|pin rows|initialState|state:" "$f" . 2>/dev/null || true

echo "== package/source references =="
rg -n "rowPinning|initialState|rowModels|pin" examples/angular packages angular 2>/dev/null | head -200 || true

Repository: TanStack/table

Length of output: 50371


🏁 Script executed:

#!/bin/bash
set -euo pipefail

f="examples/angular/row-pinning/src/app/app.ts"

echo "== app.ts relevant sections =="
awk 'NR>=1 && NR<=125 { printf "%4d  %s\n", NR, $0 }' "$f"

echo "== focused rowPinning occurrences only in target example =="
rg -n "rowPinning|row-pinning|pin rows|initialState|state:" "$f" || true

echo "== source control precedence definitions =="
rg -n "initialState|external atom|state|atoms|rowPinningFeature|rowPinning:" packages/framework docs/framework/angular/guide/table-state.md docs/framework/angular/quick-start.md | head -120 || true

Repository: TanStack/table

Length of output: 17447


Make the row-pinning initial state match the controlled row-pinning source.

state.rowPinning controls row pinning from this.rowPinning(), which starts as { top: [], bottom: [] }. Uncommenting initialState.rowPinning will not pin rows because controlled state takes precedence. Remove controlled rowPinning for this example, or initialize this.rowPinning with the documented row IDs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/angular/row-pinning/src/app/app.ts` around lines 76 - 79, Align the
row-pinning example’s initial state with its controlled state: update the
row-pinning source used by this.rowPinning() to initialize with the documented
row IDs, or remove the controlled rowPinning state so initialState.rowPinning
takes effect. Preserve the intended first-render pinning behavior and avoid
leaving conflicting controlled and initial row-pinning configurations.

Comment on lines +117 to +120
initialState: {
pagination: { pageSize: 20, pageIndex: 0 },
// rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the initial row-pinning example executable.

If Line 119 is uncommented, state.rowPinning still supplies the empty this.rowPinning value. The initialState.rowPinning value is therefore not applied. The IDs '0' and '1' also do not match the IDs returned by getRowId at Lines 114-115.

Show this as an uncontrolled example, or initialize the controlled state with IDs produced by getRowId.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/ember/row-pinning/app/templates/application.gts` around lines 117 -
120, Update the initial row-pinning example in the application template so it is
executable: either remove the controlled empty rowPinning state and enable
initialState.rowPinning, or initialize the controlled state with the row IDs
generated by getRowId. Ensure the pinned IDs match the actual getRowId output
rather than hard-coded '0' and '1'.

Comment on lines +164 to +165
// rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n -C 3 'initialState|rowPinning|onRowPinningChange' examples/octane/row-pinning/src/main.tsrx

Repository: TanStack/table

Length of output: 1476


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n 'function useTable|const useTable|rowPinningFeature|state: \\{|initialState' packages -g '*.ts' -g '*.tsx' | head -200

Repository: TanStack/table

Length of output: 381


🏁 Script executed:

#!/bin/bash
set -euo pipefail
rg -n 'function useTable|const useTable|rowPinningFeature|initialState|state.pinning|state.rowPinning' packages -g '*.ts' -g '*.tsx' -g '*.js' -g '*.jsx' | head -300

Repository: TanStack/table

Length of output: 37838


🏁 Script executed:

#!/bin/bash
set -euo pipefail
fd -a 'createTableHook.tsx|useTable.ts|rowPinningFeature.utils.ts|rowPinningFeature.ts' packages/octane-table packages/table-core src packages 2>/dev/null | sed 's#^\./##' | head -50

Repository: TanStack/table

Length of output: 1110


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '220,340p' packages/octane-table/src/createTableHook.tsx
sed -n '1,130p' packages/octane-table/src/useTable.tsrx
sed -n '1,80p' packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts
sed -n '1,70p' packages/table-core/src/features/row-pinning/rowPinningFeature.ts

Repository: TanStack/table

Length of output: 241


🏁 Script executed:

#!/bin/bash
set -euo pipefail
git ls-files packages/octane-table packages/table-core | rg 'createTableHook|useTable|rowPinningFeature'

Repository: TanStack/table

Length of output: 771


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '220,345p' packages/octane-table/src/createTableHook.tsrx
printf '\n--- oct useTable ---\n'
sed -n '1,140p' packages/octane-table/src/useTable.tsrx
printf '\n--- row pinning utils ---\n'
sed -n '1,90p' packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts
printf '\n--- row pinning feature ---\n'
sed -n '1,75p' packages/table-core/src/features/row-pinning/rowPinningFeature.ts
printf '\n--- tests ---\n'
sed -n '200,280p' packages/table-core/tests/unit/features/row-pinning/rowPinningFeature.utils.test.ts

Repository: TanStack/table

Length of output: 14524


Align the commented pinning example with the controlled state.

Controlled state.rowPinning is initialized with top: [] and bottom: []. If users uncomment the initialState.rowPinning option, those rows are still unpinned on first render because the controlled value wins.

Update the example to initialize the controlled state with these IDs, or make the comment say the option should replace the controlled rowPinning state.

Suggested documentation fix
-        // rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render
+        // rowPinning: { top: ['0'], bottom: ['1'] }, // use instead of controlled state.rowPinning above
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render
},
// rowPinning: { top: ['0'], bottom: ['1'] }, // use instead of controlled state.rowPinning above
},
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/octane/row-pinning/src/main.tsrx` around lines 164 - 165, Align the
commented initialState.rowPinning example with the controlled state by
initializing controlled state.rowPinning with top ID "0" and bottom ID "1", so
uncommenting the option preserves the demonstrated pinned rows on first render.

Comment on lines +163 to +166
initialState: {
pagination: { pageSize: 20, pageIndex: 0 },
// rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n -C 5 'initialState|options\.state|state\(' packages/table-core/src

for file in \
  examples/react/row-pinning/src/main.tsx \
  examples/preact/row-pinning/src/main.tsx \
  examples/solid/row-pinning/src/App.tsx
do
  rg -n -C 5 'initialState|rowPinning|onRowPinningChange|state' "$file"
done

Repository: TanStack/table

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the table implementation files and inspect table creation/state merge paths.
fd -a 'createTable.* | Table.* | Table.*\.ts$' packages/table-core/src | sed 's#^\./##' | head -80

printf '\n--- candidate createTable/getInitialState references ---\n'
rg -n -C 4 'getInitialState|initialState|createTable|initial.*state|state.*initial' packages/table-core/src/core packages/table-core/src/types packages/table-core/src/queries packages/table-core/src/features/row-pinning packages/table-core/src/features/column-ordering packages/table-core/src/features/column-resizing packages/table-core/src/features/row-selection packages/table-core/src/features/global-filtering packages/table-core/src/table | head -240

printf '\n--- rowPinning example relevant snippets ---\n'
for file in \
  examples/react/row-pinning/src/main.tsx \
  examples/preact/row-pinning/src/main.tsx \
  examples/solid/row-pinning/src/App.tsx
do
  echo "### $file"
  sed -n '130,200p' "$file"
done

Repository: TanStack/table

Length of output: 24075


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- table-core files ---'
git ls-files packages/table-core/src | rg 'createTable|Table\.ts|Atoms|Store|Atoms|utils|create.*Table|table.*' | head -120

printf '%s\n' '--- focused getInitialState state merging ---'
rg -n -C 5 'getInitialState|initialState|state\?' packages/table-core/src --glob '*.ts' | head -260

printf '%s\n' '--- rowPinningFeature details ---'
sed -n '1,90p' packages/table-core/src/features/row-pinning/rowPinningFeature.ts
sed -n '1,110p' packages/table-core/src/features/row-pinning/rowPinningFeature.utils.ts

printf '%s\n' '--- row-pinning examples ---'
for file in \
  examples/react/row-pinning/src/main.tsx \
  examples/preact/row-pinning/src/main.tsx \
  examples/solid/row-pinning/src/App.tsx
do
  echo "### $file"
  rg -n -C 6 'initialState|rowPinning|onRowPinningChange|rowPinning' "$file"
done

Repository: TanStack/table

Length of output: 43828


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- core table construction ---'
sed -n '1,260p' packages/table-core/src/core/table/constructTable.ts

printf '%s\n' '--- table options normalization ---'
fd -a '.*' packages/table-core/src | rg 'TableOptions|options' | sed 's#^\./##'
sed -n '1,220p' packages/table-core/src/types/TableOptions.ts
sed -n '1,220p' packages/table-core/src/types/TableState.ts

printf '%s\n' '--- table option merge searches ---'
rg -n -C 5 'merge|tableOptions|getDeep`|createTable|initialState|state.*' packages/table-core/src/core packages/table-core/src/flex-render.ts packages/table-core/src/static-functions.ts packages/table-core/src/helpers/tableOptions.ts --glob '*.ts' | head -260

Repository: TanStack/table

Length of output: 42345


Seed controlled row-pinning state for initial pins.

state.rowPinning overrides initialState.rowPinning on the first render, so these examples seed empty pinning state while documenting initial pinned rows. Seed rowPinning with { top: ['0'], bottom: ['1'] } in all three examples if initial pins should be shown.

  • examples/react/row-pinning/src/main.tsx
  • examples/preact/row-pinning/src/main.tsx
  • examples/solid/row-pinning/src/App.tsx
📍 Affects 3 files
  • examples/react/row-pinning/src/main.tsx#L163-L166 (this comment)
  • examples/preact/row-pinning/src/main.tsx#L162-L165
  • examples/solid/row-pinning/src/App.tsx#L141-L144
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/react/row-pinning/src/main.tsx` around lines 163 - 166, Update the
initialState configuration in examples/react/row-pinning/src/main.tsx lines
163-166, examples/preact/row-pinning/src/main.tsx lines 162-165, and
examples/solid/row-pinning/src/App.tsx lines 141-144 to seed rowPinning with top
row "0" and bottom row "1", while preserving the existing pagination settings
and documentation.

Comment on lines +92 to +95
initialState: {
pagination: { pageSize: 20, pageIndex: 0 },
// rowPinning: { top: ['0'], bottom: ['1'] }, // pin rows on first render
},

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Files matching App.svelte:"
git ls-files | grep 'examples/svelte/row-pinning/src/App.svelte' || true

echo
echo "Relevant sections:"
if [ -f examples/svelte/row-pinning/src/App.svelte ]; then
  nl -ba examples/svelte/row-pinning/src/App.svelte | sed -n '1,140p'
fi

echo
echo "Search for createTableState / rowPinning usage:"
rg -n "createTableState|rowPinning|initialState" examples/svelte/row-pinning/src/App.svelte examples . --glob '!node_modules' --glob '!dist' --glob '!build' | head -200

Repository: TanStack/table

Length of output: 283


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Top 140 lines of examples/svelte/row-pinning/src/App.svelte:"
awk '{printf "%6d\t%s\n", NR, $0}' examples/svelte/row-pinning/src/App.svelte | sed -n '1,140p'

echo
echo "Search for createTableState / rowPinning / initialState:"
rg -n "createTableState|rowPinning|initialState" examples/svelte/row-pinning/src/App.svelte examples . --glob '!node_modules' --glob '!dist' --glob '!build' | head -200

Repository: TanStack/table

Length of output: 32863


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Package files and references:"
git ls-files | rg '(^|/)(package\.json|pnpm-lock\.yaml|yarn\.lock|package-lock\.json)$' | head -50
rg -n "packages/table-core/src/createTable.ts|createTable\\(|initialState|atoms\\.rowPinning|getDefaultRowPinningState|rowPinning\\.initialState" packages tables svelte-table --glob '!node_modules' --glob '!dist' --glob '!build' | head -250

echo
echo "Candidate createTable source snippets:"
if [ -f packages/table-core/src/createTable.ts ]; then
  wc -l packages/table-core/src/createTable.ts
  rg -n -C 6 "initialState|setDefault|rowPinning|createTableState|onRowPinningChange|setRowPinning" packages/table-core/src/createTable.ts packages/table-core/src --glob '!node_modules' --glob '!dist' --glob '!build' | head -300
fi

echo
echo "Relevant Svelte store/state creation:"
if [ -f packages/svelte-table/src/createTableState.svelte.ts ]; then
  wc -l packages/svelte-table/src/createTableState.svelte.ts
  sed -n '1,240p' packages/svelte-table/src/createTableState.svelte.ts
fi

Repository: TanStack/table

Length of output: 2478


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Repo root contents:"
git ls-files | sed 's#/.*##' | sort | uniq | head -100

echo
echo "Locate createTable.ts:"
git ls-files | rg 'createTable\.ts$|createTableState\.svelte\.ts$|rowPinningFeature\.utils\.ts$' | sed -n '1,120p'

echo
echo "Search createTable/rowPinning in tracked files:"
rg -n "function createTable|const createTable|initialState:|rowPinning:|atoms\\.rowPinning|getDefaultRowPinningState|onRowPinningChange|setRowPinning" packages examples packages --glob '!node_modules' --glob '!dist' --glob '!build' | head -300

Repository: TanStack/table

Length of output: 40071


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "SvelteTable createTable.ts:"
git ls-files | rg 'packages/[^/]*/src/createTable\.ts$' | rg -i 'svelte'
for f in $(git ls-files | rg 'packages/[^/]*/src/createTable\.ts$' | rg -i 'svelte'); do
  echo "--- $f"
  wc -l "$f"
  sed -n '1,180p' "$f"
done

echo
echo "Focus row pinning tests for controlled initialState precedence:"
awk '{printf "%6d\t%s\n", NR, $0}' packages/table-core/tests/implementation/features/row-pinning/rowPinningFeature.test.ts | sed -n '1,200p'

echo
echo "Inspect default row pinning atom source:"
rg -n -C 8 "DEFAULT|EMPTY_PINNING_STATE|rowPinning:|getDefaultRowPinningState|atoms\\.rowPinning|initialState\\[.*rowPinning|options\\.initialState|onRowPinningChange" packages/table-core/src packages --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,260p'

Repository: TanStack/table

Length of output: 180


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "List tracked createTable.ts files:"
git ls-files 'packages/*/src/createTable.ts' | sed -n '1,80p'

echo
echo "Svelte package src files:"
git ls-files packages/svelte-table/src | sed -n '1,120p'

echo
echo "Search export/createTable/createTableState:"
rg -n -C 5 "export .*createTable|function createTable|setRowPinning|onRowPinningChange|rowPinning|initialState|atoms" packages/table-core/src packages/svelte-table/src packages/preact-table/src packages/react-table/src packages/solid-table/src --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,360p'

Repository: TanStack/table

Length of output: 29098


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Svelte createTable state synchronization:"
awk '{printf "%6d\t%s\n", NR, $0}' packages/svelte-table/src/createTable.svelte.ts | sed -n '1,160p'

echo
echo "Svelte createTableState:"
awk '{printf "%6d\t%s\n", NR, $0}' packages/svelte-table/src/createTableState.svelte.ts | sed -n '1,220p'

echo
echo "Core options store atom initialization and apply options:"
rg -n -C 8 "optionsStore|initialState|apply|state\\(|setOptions|rowPinning|rowPinningFeature|getDefaultRowPinningState|EMPTY_PINNING_STATE" packages/table-core/src packages/table-core/src -g '!node_modules' -g '!dist' -g '!build' | sed -n '1,260p'

echo
echo "Core table store/source atom for state:"
rg -n -C 6 "sourceAtom|source:|atoms\\.state|table\\.state|setOptions|effect.*state|state.\\(" packages/table-core/src --global '!node_modules' '!dist' '!build' | sed -n '1,260p'

Repository: TanStack/table

Length of output: 29989


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "Core state atom/sourceAtom definitions:"
rg -n -C 8 "sourceAtom|source:|sourceAtom|atoms\\.state|setOptions|initialState\\[" packages/table-core/src --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,360p'

echo
echo "Row pinning source definitions:"
rg -n -C 8 "EMPTY_PINNING_STATE|getDefaultRowPinningState|rowPinning\\)|resetRowPinning|setRowPinning|getInitialState: \\(initialState\\).* initialState|onRowPinningChange" packages/table-core/src/features/row-pinning --glob '!node_modules' --glob '!dist' --glob '!build' | sed -n '1,360p'

echo
echo "Focused row pinning tests:"
awk '{printf "%6d\t%s\n", NR, $0}' packages/table-core/tests/implementation/features/row-pinning/rowPinningFeature.test.ts | sed -n '90,200p'

Repository: TanStack/table

Length of output: 40796


Use the controlled pinning state as the initial value here.

This example keeps rowPinning in createTableState, so uncommenting initialState.rowPinning only changes the internal initial atom value; table.options.state.rowPinning still defaults to empty top/bottom arrays and controls the first render. Set top: ['0'], bottom: ['1'] in createTableState<RowPinningState> for this controlled example.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/svelte/row-pinning/src/App.svelte` around lines 92 - 95, Update the
controlled row-pinning state in createTableState<RowPinningState> to initialize
top with ['0'] and bottom with ['1']; do not rely on initialState.rowPinning,
since table.options.state.rowPinning controls the first render.

Comment on lines +31 to +36
onAfterUpdate: skipFirstRun(() => {
table_autoResetExpanded(table)
table_autoResetPageIndex(table)
table_autoResetSorting(table)
table_autoResetCellSelection(table)
},
}),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Avoid duplicate expanded-state reset requests for one data update.

When grouping is enabled, a data replacement updates both the core row model and the pre-grouped row model. This callback and createGroupedRowModel then each call table_autoResetExpanded. A controlled onExpandedChange consumer can receive two reset updaters for one data update.

Ensure that one data update schedules one expanded-state reset. Keep grouped resets for grouping or filtering changes that do not already reset in the core stage.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/table-core/src/core/row-models/createCoreRowModel.ts` around lines
31 - 36, Update the onAfterUpdate callback in createCoreRowModel so data updates
no longer request table_autoResetExpanded when the grouped-row-model path will
also reset it. Preserve table_autoResetExpanded in createGroupedRowModel for
grouping or filtering changes that are not already reset by the core stage,
while keeping the other core reset calls unchanged.

Comment on lines +75 to +76
/** The first applied result is not a change; auto-resets skip it. */
hasAppliedResults: boolean

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Reset first-result tracking when the worker terminates.

terminate() retains the bridge in _bridges, but it does not reset hasAppliedResults. When the same table recreates its worker, the first new result is treated as a later result and can reset pagination and expanded state.

Set bridge.hasAppliedResults = false in the terminate loop.

Proposed fix
         bridge.sentState = null
         bridge.sentStageCount = 0
+        bridge.hasAppliedResults = false
       }

Also applies to: 162-162

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/table-core/src/worker/createTableWorker.ts` around lines 75 - 76,
Update the worker termination loop in terminate() to reset hasAppliedResults to
false for each retained bridge before or alongside terminating it, ensuring the
recreated worker treats its first result as the initial result.

Comment on lines +256 to +260
// reset expansion). Like the sync models, the first applied result is not
// a change and must not fire auto-resets.
const isFirstAppliedResult = !bridge.hasAppliedResults
bridge.hasAppliedResults = true
if (anyChanged && !isFirstAppliedResult) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reset expansion for core-stage data changes.

When a worker table uses expansion without grouping, groupedChanged is always false. A data replacement changes the core stage and passes anyChanged, but Lines 263-265 do not reset expanded state. This differs from createCoreRowModel.

Track core-stage changes. Reset expanded state when either the core stage or grouped stage changes.

Proposed fix
   let anyChanged = false
+  let coreChanged = false
   let groupedChanged = false
   for (const [stage, payload] of Object.entries(message.stages) as Array<
@@
     bridge.stageVersions[stage] = (bridge.stageVersions[stage] ?? 0) + 1
     anyChanged = true
+    if (stage === 'core') coreChanged = true
     if (stage === 'grouped') groupedChanged = true
@@
-        if (groupedChanged) {
+        if (coreChanged || groupedChanged) {
           table_autoResetExpanded(table)
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/table-core/src/worker/createTableWorker.ts` around lines 256 - 260,
Update the change tracking in the worker result application flow around
isFirstAppliedResult so core-stage changes are tracked separately from
grouped-stage changes. Ensure expanded state is reset when either the core stage
or grouped stage changes, while preserving the first-applied-result guard and
existing auto-reset behavior.

@KevinVandy
KevinVandy merged commit 8c9df9c into beta Aug 2, 2026
9 checks passed
@KevinVandy
KevinVandy deleted the fix/auto-reset-first-run-guard branch August 2, 2026 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Invoking table.getRowModel() causes rows to collapse autoResetExpanded not reset the expanded state on data change in V8

1 participant