GeezerVison is showing github.com — perf(tables): stop a table write refetching every loaded page in the tab that made it by waleedlatif1 · Pull Request #6698 · simstudioai/sim
Conversation
Summary
- Stop a single-row table write refetching every loaded page in the tab that made it
- Give the v1 Query Rows tool an end-of-data signal, and fix pagination docs that were instructing row-skipping
The refetch storm
#5991 added a live-collaboration signal: a row write appends an edit event that every subscriber of the table refetches on. The stream carries no originator, so the writing client refetches its own write — rowsRoot is a prefix over the infinite query, so one cell edit costs N sequential GET /rows (up to 1000 rows each, plus a COUNT(*) on page 0) about 250–750ms after the write.
For the acting tab that work is pure duplication, and it actively undoes existing optimism: useUpdateTableRow merges the server row in onSuccess and deliberately never invalidates rows; useCreateTableRow avoids the rows root specifically to prevent insert flicker. The echo invalidates it anyway, a second later. On delete it's a second full refetch racing the one the hook already issued — and since invalidateQueries defaults to cancelRefetch: true, it can cancel and restart that one.
lib/table/events.ts had carried a standing TODO describing this and naming the fix. This is that fix.
Writes now optionally carry the originating tab, and a client ignores its own echo. Every tab sends an opaque per-tab id (x-sim-client-id, generated once per page load) — matching the existing pattern for cross-cutting headers (execution-deadline-header.ts, call-chain.ts, mcp/constants.ts): a small module owning the constant plus both sides' accessors.
Attribution is deliberately narrow. Only three routes stamp it — single-row create, update, delete — because only their hooks reconcile the server's answer across every cached rows query. Bulk, filter-scoped, upsert, import, copilot, and run-dispatch writes stay unattributed and refetch exactly as before; suppressing those would strand the writer on stale rows. That invariant is a claim about a client hook, which nothing in the type system ties to the call site, so events.attribution.test.ts pins the allowlist — a fourth call site fails the build.
Per-tab rather than per-user: a shared id would make your second tab ignore the first tab's edits.
The nextCursor holes
Fallout from #6582, which turned byte-cut pages on by default. Two in-repo consumers still assumed a full page meant more data:
apps/docs/.../using-in-workflows.mdx told users to advance Offset by the Limit — "0, then 100, then 200". Since a page can now end early at the byte budget, stepping by the requested limit rather than the received count silently skips rows, starting around 5KB average rows. Rewritten to advance by rowCount and stop on nextCursor.
tools/table/query_rows.ts (the v1 Table block) dropped nextCursor in transformResponse, leaving workflow authors with no correct termination signal. Now surfaced, matching query_rows_v2.
What was investigated and deliberately NOT changed
Four other suspected regressions were verified and declined on evidence:
- Per-cell cache walk — benchmarked: 2.86ms per 500 events on 1000 loaded rows. An id-index early-exit would recover ~3ms and add an index to keep in sync with every page mutation.
- Saved-views double page-0 fetch — real, but gating the rows query on views serializes two requests that are currently parallel, and adds latency for tables with no default view. The right fix is resolving the view in the page prefetch.
- Duplicate
useTable mount — real (network deduped, memos and renders not), but a provider refactor of a 1746 + 4616-line pair for unmeasured gain.
#6582 byte cut / firstBatchCap — both bounds are load-bearing. Reverting the byte cut restores a 400MB worst-case page; every route to a single first batch either re-opens unbounded memory or puts a byte-accounting mismatch on the hottest table read.
Type of Change
Testing
9 unit tests: the header is sent in the browser and omitted on the server, an over-long id is dropped rather than broadcast, absent reads as unattributed, and the attributed call-site allowlist is pinned.
Not browser-verified. The check that would confirm it end-to-end: edit a cell on a scrolled table with devtools Network open — today that triggers one GET /rows per loaded page ~250–750ms later; it should now trigger none in the acting tab, while a second tab still refetches.
Checklist
- Code follows project style guidelines
- Self-reviewed my changes
- Tests added/updated and passing
- No new warnings introduced
- I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
greptile-apps[bot] reacted with thumbs up emoji
vercel
Bot
commented
Aug 14, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
cursor
Bot
commented
Aug 14, 2026
•
edited
Loading
Uh oh!
There was an error while loading. Please reload this page.
PR Summary
Medium Risk
Touches real-time table sync and cache invalidation; mis-attribution could leave a tab stale, but unattributed fallback, fingerprinting, and a pinned allowlist limit that surface area.
Overview
Stops the writing browser tab from refetching every loaded rows page after its own single-row create, update, or delete. Live-collab edit events used to make all subscribers (including the actor) debounce-invalidate rowsRoot, which duplicated work the mutation hooks already do and could race deletes on scrolled tables.
Each tab now sends x-sim-client-id on API requests; single-row write routes call signalTableRowsChangedByActor, which publishes a SHA-256 fingerprint (not the raw id) as originatorId on the edit event. The SSE handler skips the rows refetch when that fingerprint matches the tab. Bulk/filter writes stay on the unattributed path so writers that do not reconcile locally still refetch. An allowlist test pins which routes may use attributed signals.
useUpdateTableRow additionally invalidates non-default filtered/sorted row queries on success, since the acting tab no longer relies on the broadcast for those views. Docs for workflow Query Rows pagination now say to advance offset by rowCount and terminate on nextCursor, because pages can end early under the response size budget.
Reviewed by Cursor Bugbot for commit 8e18553. Configure here.
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Greptile Summary
The PR prevents an originating table tab from refetching locally reconciled single-row writes and exposes a reliable end-of-pagination signal for Query Rows.
- Adds per-tab request attribution using a non-replayable SHA-256 fingerprint.
- Suppresses only the originating tab’s redundant edit-event invalidation while preserving collaborator refreshes.
- Refetches non-default filtered or sorted caches after row updates.
- Adds nullable
nextCursor output metadata and corrects offset-pagination guidance.
Confidence Score: 5/5
The PR appears safe to merge.
No blocking failure remains.
Important Files Changed
| Filename |
Overview |
| apps/sim/lib/api/client-id.ts |
Generates a per-page client identifier and hashes it before attribution is exposed to table subscribers. |
| apps/sim/lib/table/events.ts |
Adds an attributed edit signal while retaining unattributed signaling for mutation paths that require refetching. |
| apps/sim/app/workspace/[workspaceId]/tables/[tableId]/hooks/use-table-event-stream.ts |
Skips edit invalidation only when the event fingerprint matches the current tab. |
| apps/sim/hooks/queries/tables.ts |
Invalidates non-default row queries whose filtering, ordering, or counts cannot be repaired by an in-place row patch. |
| apps/sim/tools/table/query_rows.ts |
Exposes a nullable pagination cursor in both transformed output and tool metadata. |
| apps/docs/content/docs/en/tables/using-in-workflows.mdx |
Corrects pagination instructions to advance by received row count and terminate using nextCursor. |
Reviews (3): Last reviewed commit: "perf(tables): stop a table write refetch..." | Re-trigger Greptile
|
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
greptile-apps[bot] reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
greptile-apps[bot] reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
cursor[bot] reacted with thumbs up emoji
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 8e18553. Configure here.
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
This branch was successfully deployed
1 active
deployment
Uh oh!
There was an error while loading. Please reload this page.
Assignees
No one assigned
Milestone
No milestone
Development
Successfully merging this pull request may close these issues.
Uh oh!
There was an error while loading. Please reload this page.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
|