Skip to content

perf: sort interactions at TanStack-beating latency, end to end (#457) - #479

Merged
blove merged 27 commits into
mainfrom
blove/spec-457-cbb90d
Aug 19, 2026
Merged

perf: sort interactions at TanStack-beating latency, end to end (#457)#479
blove merged 27 commits into
mainfrom
blove/spec-457-cbb90d

Conversation

@blove

@blove blove commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Closes #457. Three design cycles on one branch, each specced and measured (specs and plans under docs/superpowers/).

What this delivers, measured (S2 bench, like-for-like, same-run TanStack control)

metric before after
50k sort status partial ×3 (never settles) completed ×3
50k sort interaction latency unreportable (~390–409ms once completing) 14.8–16.7ms (TanStack same-run: 35–49ms)
3k sort latency 50–59ms 8.4–23ms
3k filter-metadata (collateral) 42–58ms 15.8ms
50k mount first paint ~450ms blank painted in the activation pass

How, per cycle

Cycle 1 — synchronous sort fast path (row-model). A conservative query-delta classifier routes a sort-only setQuery on ungrouped data to a synchronous rebuild: no cooperative slices (browser-measured: scheduler hops cost frames), O(n) balanced bulk tree construction from sorted entries. Error/supersede semantics mirror the cooperative path observable-for-observable.

Cycle 2 — sort keys move to the plan; the journal learns "order-only". CompiledRowMetadata lost sortKeys; each compiled plan owns a WeakMap sort-key store, so a sort-only change carries records and the rows HAMT by identity (rebuilding them was 86% of the transition cost). The change journal gained reset reason "reorder" (fail-closed for unaware consumers), layout-core gained a synchronous RowHeightIndex.reorder() over existing measurements, and the row-layout controller takes a permutation path instead of re-ingesting 50k rows.

Cycle 3 — decorated entries + bulk mount. Tree entries carry their resolved sort keys ({record, keys}) so comparisons are property reads — this recovered the grouped gate regression that per-comparison WeakMap resolution had introduced, and improved every comparator-hot path (hence the filter gains). Replacements over unmeasured bases (true mounts) build synchronously in one O(n) pass, eliminating the blank-mount window and the sort-during-ingest race; a reorder arriving mid-replacement now composes at finish instead of forcing a full re-ingest.

Verification

  • Every task TDD'd and mutation-hardened (each load-bearing assertion demonstrated to fail under a seeded defect); equivalence oracles against cold-built models throughout.
  • Suites: row-model 412, layout-core 118, renderer-dom 144, react 1246, grid-core 125 (incl. a fail-closed reorder-reset pin), website 557 — all green; pnpm api drift-free (public surface change: the reset-reason union gains "reorder", core+react reports regenerated).
  • Browser traces confirm the benched 50k sort takes the reorder path (reorderFallbackCount 0, no re-ingest frames) and the mount publishes in the activation pass.
  • Full measurement records: docs/superpowers/specs/*2026-08-1{7,8}* reference the scratchpad protocol; cycle-by-cycle numbers in the PR conversation on request.

Merge checklist

  • CI green
  • Grouped gate quiet-machine certification (pnpm bench:row-model:gate ×3 ≤ 8ms — strong evidence already: min 7.8ms under load 12–16, better than the pre-branch best; the machine had no quiet window during final verification)

🤖 Generated with Claude Code

blove and others added 25 commits August 17, 2026 14:00
…build)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…nt for #467 sortAuthority)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… changes

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ueries

A sort-only query change on an ungrouped model now bypasses the
cooperative transition entirely: setQuery cancels any in-flight
transition as superseded, rebuilds the root synchronously via
rebuildRootForSortOnlyChange, and publishes with the same recipe as
runTransitionSlice's completion block. Errors mirror failTransition's
observable semantics (error status carrying the transition id, rejected
finished, committed root untouched), including the reentrant-mutation
mapping.

Pre-existing tests updated for the deliberate behavior change (each
asserted that a sort-only setQuery schedules cooperative work, but its
subject is the cooperative machinery, so the query now changes a filter
too — or, for the properties oracle, accounts the synchronous commit):
- properties.test.ts "matches a fresh final model after superseded
  query catch-up": the revision oracle now accounts a sort-only
  first/second query committing synchronously before the concurrent
  mutations run.
- retention.test.ts (3 tests): scheduler-ownership subjects; queries
  gain a filter change to stay on the cooperative path.
- transitions.test.ts delta-journal + hostile-cancellation + 3 catch-up
  tests: same treatment; the supersede-between-removal-and-insertion
  test's replacement stays sort-only and now pins fast-path
  supersession of a mid-catch-up transition.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…s are synchronous now (#457)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…identity

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…fail-closed pin

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…re-ingesting

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ild synchronously

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@vercel

vercel Bot commented Aug 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
pretable Ignored Ignored Aug 19, 2026 5:43am

Request Review

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Aug 19, 2026

Copy link
Copy Markdown
Contributor

Vercel preview ready

Preview: https://pretable-a8ib39q95-cacheplane.vercel.app
Commit: 396ec3b78b51f92738bb82f1b531232fcc52fea4

Updated automatically by the deploy-preview job.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@blove
blove merged commit a438efb into main Aug 19, 2026
20 checks passed
@blove
blove deleted the blove/spec-457-cbb90d branch August 19, 2026 05:58
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.

S2 sort at target scale (50k rows) never settles: 515ms of transition work against a 400ms window

1 participant