Skip to content

Let a page be read live as a frame and element slices - #349

Merged
SunkenInTime merged 3 commits into
mainfrom
t3/page-slices
Oct 19, 2026
Merged

SunkenInTime merged 3 commits into
mainfrom
t3/page-slices

Conversation

@SunkenInTime

@SunkenInTime SunkenInTime commented Oct 19, 2026 •

Copy link
Copy Markdown
Owner

Bytes re-read after one edit on an 87-element page, by how the live page is read (local deployment loaded with dev's data, same session script):

live page read as re-read per edit live queries at page open function calls at open
page:getSnapshot (today) 58,494 B 80,879 B 5
frame + 8 slices (this PR's queries) 10,735 B 98,625 B 13
frame + 16 slices 7,303 B 113,065 B 21

Server half of proposal v2 (read costs). Convex re-runs a live query in full whenever any row it read is written. The editor's page:getSnapshot subscription reads every row on the page, so after every edit it re-reads the whole page. In a heavy day that's 83% of everything a user reads from Convex, and it counts against our database I/O cap.

What this adds. Additive only, no client reads it yet:

  • page:getFrame: the page, its settings, its lineups and the images they show.
  • page:getElementSlice(fromPublicId, toPublicId?): the page's elements whose public id is in [from, to), with the images they show.
  • Both always send picture ids in payloads and send no picture aliases. Aliases list a copy's picture under the copy's own id, for builds from before pictures had their own id. Every reader of these queries looks pictures up by pictureId (assetId ?? id), so they'd only be dead weight. Rebuilding them across slices would also have meant following alias chains in page order, which independent slices can't do (Astra round 1 found that case).
  • Index elements.by_pageId_and_publicId.

Each part is an ordinary complete query over a fixed range. A row that moves to another page, or is deleted, restored or purged, just leaves or enters its slice, and Convex re-runs that slice. There are no change numbers and nothing to keep in step across write paths. That's why this replaced the per-row change-number design: Astra's review of that one found six ways it could show stale work.

page:getSnapshot answers exactly as before for every shipped desktop build and the live web build. It now shares readablePage / pageContentOf / viewerAssets with the new queries, and still reads its assets before sorting elements and lineups, so even the order of ids that compare equal is unchanged.

Order. Rebuilt from parts, elements with the same sortIndex are ordered by public id, which is how the offline copy already orders them (cloud_strategy_copy_box.dart). getSnapshot orders those ties by creation time. The app change (next PR) will use the copy's order.

Checked

  • New pageSlices.test.ts: a page rebuilt from frame + slices equals getSnapshot(acceptsPictureIds: true) less its aliases (in place order, ties by code unit as Dart orders them) for 1, 2, 4 and 16 slices, on both pages, before and after a move to the other page, a delete and a restore onto the other page. The page has images, copies sharing the original's picture inside and outside the original's slice, a lineup with an image, ids outside the hex range and an id exactly on a slice boundary. Mutation-checked: an inclusive upper bound fails it.
  • Cost harness (E:\w\convex-io\harness, session-slices.mjs --slices 8|16) against this branch's code: the table above. Byte counts on the local backend match Convex cloud exactly.
  • npx tsc --noEmit, full npm run test:convex (329 tests), contract snapshot refreshed against a local deployment of this branch, npm run audit:convex-contract (60 functions), Dart client regenerated (only the two new queries change; the generator now shares one class for the page and its content between getSnapshot and getFrame, so convex_strategy_repository.dart follows the new names); flutter analyze lib test has no errors.

Not in this PR: the app reading pages this way (it touches the sync path, so you look first), and the offline-copy skip (separate design).

ci: test convex contract

🤖 Generated with Claude Code

Convex re-runs a live query in full whenever a row it read is written, so
the editor's page:getSnapshot subscription re-reads every row of the page
after every edit: 58.5 KB on an 87-element page, measured on a local
deployment with dev's data. Add page:getFrame (the page, its settings, its
lineups and their images) and page:getElementSlice (the page's elements in
one public-id range, with their images, and picture aliases apart), backed
by a new elements.by_pageId_and_publicId index. Read as a frame plus slices
covering every id, a page holds what page:getSnapshot returns, and an edit
re-runs only the slice holding its row: 10.7 KB with 8 slices.

Additive only: getSnapshot answers as before (now sharing its helpers), and
no client reads the new queries yet.

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

coderabbitai Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Warning

Review limit reached

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Next included review available in 54 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6e55547f-7b9f-4fd4-a4c5-6f4174d1c9ae
📥 Commits

Reviewing files that changed from the base of the PR and between 0776c17 and 8685859.

⛔ Files ignored due to path filters: 2
  • lib/collab/generated/convex_models.dart is excluded by !**/generated/**
  • lib/collab/generated/icarus_convex_api.dart is excluded by !**/generated/**
📒 Files selected for processing: 4
  • convex/function_spec.json
  • convex/lib/publicValidators.ts
  • convex/page.ts
  • convex/pageSlices.test.ts

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 18b50ad0-5d78-49f6-b1d7-19438756a066
📥 Commits

Reviewing files that changed from the base of the PR and between 5c7b867 and 0776c17.

⛔ Files ignored due to path filters: 2
  • lib/collab/generated/convex_models.dart is excluded by !**/generated/**
  • lib/collab/generated/icarus_convex_api.dart is excluded by !**/generated/**
📒 Files selected for processing: 8
  • convex/cloudProtocol.test.ts
  • convex/function_spec.json
  • convex/lib/imageAssets.ts
  • convex/lib/publicValidators.ts
  • convex/page.ts
  • convex/pageSlices.test.ts
  • convex/schema.ts
  • lib/collab/convex_strategy_repository.dart

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

📝 Walkthrough

Page reads now support separate frame and public-ID-bounded element-slice queries. Shared helpers handle readability checks, page content, and viewer-visible assets. Tests compare assembled reads with whole-page snapshots.

Changes: Page Reads

Layer / File(s) Summary
Shared page-read contracts and helpers
convex/lib/publicValidators.ts, convex/lib/imageAssets.ts, convex/page.ts
Adds validators for frame and slice responses, extracts picture-alias creation, and uses shared helpers for page readability, content retrieval, and viewer-visible assets.
Frame and element-slice queries
convex/page.ts, convex/schema.ts, lib/collab/convex_strategy_repository.dart, convex/cloudProtocol.test.ts
Adds frame and half-open public-ID element-slice queries, an index for page and public ID, updated Dart conversion helper types, and protocol-version test coverage for both queries.
Frame and slice equivalence tests
convex/pageSlices.test.ts
Compares assembled frame and slice reads with whole-page snapshots across pages and element changes. Tests also check slice boundaries and asset-alias precedence.

Priority: ➖ Normal

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

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant GetFrame as page.getFrame
  participant GetSlice as page.getElementSlice
  participant ConvexDB as Convex database
  Client->>GetFrame: Request page frame
  GetFrame->>ConvexDB: Read page data, lineups, and assets
  ConvexDB-->>GetFrame: Return frame data
  GetFrame-->>Client: Return frame payload
  Client->>GetSlice: Request elements in a public-ID range
  GetSlice->>ConvexDB: Read matching elements and assets
  ConvexDB-->>GetSlice: Return matching data
  GetSlice-->>Client: Return element slice and aliases
Loading
Merge Risk: ⚪ Minimal · up to 0776c

This change adds two new read queries and an index while leaving the existing snapshot behavior unchanged for current clients. No concrete merge-blocking risk was found in the supplied context.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 6 files. (1 skipped: … Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed 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 and concisely describes the main changes: adding live page frame and element-slice reads.
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 53.85% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 13 functions across 6 files. (1 skipped: 1 unsupported.)

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

The client codegen now shares one class between page:getSnapshot and
page:getFrame for the page and its content, named after the first.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Aliases list a copy's picture under the copy's own id for builds from
before pictures had their own id. Every reader of page:getFrame and
page:getElementSlice keeps picture ids (and looks pictures up by them), so
the read in parts now always sends them and sends no aliases. Rebuilding
aliases across slices would also have had to follow chains of them in page
order, which independent slices can't.

getSnapshot reads its assets before sorting elements and lineups again, as
on main, so its answer keeps its order for every input.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SunkenInTime added a commit that referenced this pull request Oct 19, 2026
The read in parts always carries picture ids and sends no aliases (#349).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@SunkenInTime
SunkenInTime merged commit 2e48627 into main Oct 19, 2026
14 checks passed
@SunkenInTime
SunkenInTime deleted the t3/page-slices branch October 19, 2026 20:54
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