feat(react): public rejected-writes API (rejectedWrites + onRejectedWriteChange) - #564
Merged
Conversation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Generalizes usePretable's notifying rows-write store to carry a fault record per write kind (rows, derivations, query), wires all three rejection guards to publish through it, and derives a fault-only-keyed PretableRejectedWrites record exposed as model.rejectedWrites. The shared EMPTY_REJECTED_WRITES identity is returned whenever every slot is null, and slot normalization keeps record identity stable across no-op republishes and ordinary windowed paging. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The corrected-query recovery test could not see the main publish's controlledQueryChanged clear branch: its new query lands synchronously in the same effect, so the landed-transition publish masks it. Pairing the corrected query with a simultaneous derivations change defers applyQuery behind the derivations transition, leaving a window where only the main publish can have cleared the slot — asserted before any microtask runs, and again after the deferred re-apply lands. Verified by mutation: always-previous on that branch fails exactly this test. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two pins the review asked for: one commit rejecting rows AND derivations must surface both faults from the single consolidated publish (the reason the publish sits below the derivations block), and a query rejected on the deferred .then path must publish its fault via the read-modify-write over the settled snapshot, after the interim main-publish clear. Comment-only fixes alongside: RejectedWriteSlot.refused documented as replacement semantics (it never drives a clear), the slotOrPrevious rows-vs-slots normalization asymmetry stated as intended, and the plan-artifact localSlots sentence deleted from the memo comment. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A consumer doing useLocalRowModel({rows}) + <PretableSurface model={m}>
never runs usePretable's own guards, so the public rejectedWrites record
answered "in sync" for a grid that had diverged. A Symbol-keyed
notifying store attached to the model instance now carries the local
hook's rows/derivations faults into usePretable's merged record; query
stays null by construction for this entry point.
The merge is a disjoint union via ??, not precedence: in model mode the
own slots are always null (the effect returns early), in rows mode the
channel slots are always null (the owned model carries no channel).
Pinned by mutation: replacing readLocalRejectedWrites(rowModel) with
undefined fails both model-mode tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two coverage pins on the useLocalRowModel rejection channel: a commit that recovers only rows must carry the standing derivations fault forward (the previousSlots seeding — verified by mutation: seeding the slots to null fails the test), and a fault must follow its model instance across a model-prop swap rather than leak to a clean model. Plus one doc line: attachLocalRejectedWrites is at-most-once per model. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
PretableSurface (and LabeledGridSurface, byte-parallel to onTelemetryChange) now accepts onRejectedWriteChange, notified whenever the PretableRejectedWrites record changes identity: every rejection of any kind, and every recovery — nothing latches, unlike the console warning. A layout effect compares the record against the last DELIVERED one, seeded with the mount value, so the callback stays silent at mount, on valid updates, on paging, and when only the callback prop changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A derivations fault must drive onRejectedWriteChange too — the callback is not rows-only — and a callback swapped in with no data change gets no catch-up call for the standing record, only the transitions after it arrives. The prop's JSDoc now states the mechanism (seeded compare, transition-only) rather than the mount-time coincidence. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The comment contrasted the channel's effect-publish clear with "usePretable's own rows slot" — true only of the internal rejectedRows identity; the public rowsFault clears via the same effect publish as the channel. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
blove
enabled auto-merge (squash)
September 2, 2026 00:27
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
GHSA-73wf-gq98-2v4g Two new HIGH advisories on browserslist <=4.28.6 (transitive via eslint-plugin-react-hooks -> @babel/core) fail the required security-audit check on every branch. Lockfile-only in-range bump; pnpm audit is clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Contributor
Vercel preview readyPreview: https://pretable-d8l4lj5xd-cacheplane.vercel.app Updated automatically by the |
This was referenced Sep 2, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
What
Since #550/#553/#557/#559, an invalid
rows/derivations/queryupdate is a rejected write: the grid keeps its last-good value and stays alive. The only signal was a console warning that latches per fault kind — and there was no API to ask whether the rendered rows match the ones you passed. Now there is:model.rejectedWrites: PretableRejectedWritesonusePretable's return — a per-kind record{ rows, derivations, query }, eachnullwhen in sync or{ kind, code, message, columnId? }describing the most recent rejection. Nothing latches; every rejection replaces the record; a slot clears on its own when a valid value lands (published pre-paint, so no painted frame is ever stale).onRejectedWriteChangeonPretableSurfaceandLabeledGridSurface— fires on every transition including recovery; silent at mount, on unrelated renders, and on callback-prop swaps (transition events, not state sync).useLocalRowModelrejections (rows and derivations) surface through the same record via a react-internal Symbol channel on the model instance — model-mode consumers get the same answer, no false "in sync".Console warnings are unchanged. Fatal faults (
disposed-model,reentrant-mutation, foreign errors) still throw.How
Generalizes #561's notifying
RowsWriteStatestore to per-kind{refused, fault}slots with identity-preserving publish normalization; the public record is a fault-only-deps memo returning a shared frozen empty identity, so record identity moves exactly on fault transitions (pinned by paging/no-change tests). The async query path publishes read-modify-write fromapplyQuery(generation-gated against stale rejections).Design spec:
docs/superpowers/specs/2026-09-01-rejected-writes-public-api-design.md(includes two as-built notes: the local channel carries derivations too, and invalid initial rows still throw at model creation — a creation fault, not a write).Verification
rejected-writes-public-api.test.tsx, every load-bearing assertion mutation-verified (memo deps, both query clears, channel merge, slot carry-forward, ref-update, fresh-literal empty)pnpm build && pnpm api:checkgreen;react.api.mddiff is exactly the four new symbolsgrid/pretable-surface.mdx@pretable/reactminor🤖 Generated with Claude Code