Skip to content

feat(realtime): useRecordChanges — hear that records moved, holding none; useChannel().error() - #857

Merged
sebyx07 merged 2 commits into
mainfrom
feat/realtime-record-changes
Oct 22, 2026
Merged

sebyx07 merged 2 commits into
mainfrom
feat/realtime-record-changes

Conversation

@sebyx07

@sebyx07 sebyx07 commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

What

  • useRecordChanges(types, listener): one call per store update with the moved keys grouped by type. Holds no row and no type, so a page that re-renders on a channel's records pins nothing. Added to FOLLOW_ONLY_EXPORTS: it renders nothing from the store, so its island is never held behind the restore.
  • useChannel(...).error(): the node's refusal of a failed membership, read reactively.

Why

tesote/marketing's header island held 11 types via useRecords(type, {}) only to learn that something changed, so every row stayed for the tab's life. It also couldn't tell a signed-out X_TOPIC_FORBIDDEN from other failures, so it probed with an HTTP call.

Not done (deliberately)

I did not add LRU eviction of never-held synced rows. Query adoption retains after adopting, disk-restored rows are unheld until islands mount, and mutator replays read whole types, so an inline cap could evict a row a render is about to retain. That needs its own design.

Tests

use-record-changes.test.ts (new): grouping, removal, holds nothing (eviction still happens), release. use-channel.test.ts: error() on refusal. bun test packages/realtime + island-realtime suites: 0 fail. verify --only typecheck,lint,boundaries,filesize,errors,manifest green.

🤖 Generated with Claude Code

sebyx07 and others added 2 commits October 22, 2026 16:33
…one; useChannel().error()

A whole-type useRecords was the only way to react to a channel's records,
and it holds the type: every row kept for the reader's life. The new hook
gets the moved keys per type per store update and holds nothing; it is
follow-only, so its island is not held behind the restore. useChannel's
accessor now exposes the node's refusal of a failed membership.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ts ceiling

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@developerz-ai

developerz-ai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

🟡 Reviewed

1 actionable comment(s) · grounded on your code

⏱ 1m 03s wall clock · gpt-6.1-sol via router · 3 model call(s) · 504 output token(s)

🤖 developerz.ai — automated review, running on your model and your box.

@developerz-ai developerz-ai 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.

Review summary — 9 file(s), 1 finding(s).

Critical 0 · Major 0 · Minor 1 · Nit 0

Completed [bot]: the refusal test does not protect the new reactive error contract.

1 non-blocking finding (1 minor; not posted inline)
  • packages/realtime/src/use-channel.test.ts:103 test [bot] These imperative reads still pass if error() stops reading version(), leaving components that render only the refusal unable to update. Add a reactive-effect test that observes error() before delivery and asserts it reruns with X_TOPIC_FORBIDDEN afterward.

🤖 developerz.ai review — automated, running on your model and your box. What is this? · Reviewed by router/gpt-6.1-sol on box hv-dedicated-reviewer-1 — 187k in / 504 out over 3 calls; cost unknown: no price on record for router/gpt-6.1-sol.

@sebyx07
sebyx07 merged commit 1b75b31 into main Oct 22, 2026
16 checks passed
@sebyx07
sebyx07 deleted the feat/realtime-record-changes branch October 22, 2026 21:46
@sebyx07 sebyx07 mentioned this pull request Oct 22, 2026
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.

1 participant