Add offscreen block skeletons - #37
Conversation
Native browser selection stops at contentEditable boundaries, so skeletonized editors could select only the current rendered block. Build the full-editor selection in Draft while leaving custom key bindings in control.
designlooking at this PR and https://github.com/descriptinc/descript/pull/38103, my understanding is that this approach exposes
I don't love that the script feature builder has to know when to opt in to declare a parallel one alternative is to remove the need to match skeleton height against full rendering height:
wdyt? verificationI was going to try some scrolling in the https://github.com/descriptinc/descript/pull/38103 preview build but I suppose that depends on merging and publishing this, so I'll leave the verification to you! the concern is that if the skeleton height and full render height diverge, since further notesmore claude-written notes in https://app.notion.com/p/descript/Script-skeleton-height-parity-vs-scroll-compensation-design-notes-3b2abe2e1a50819c969fcd2843131a86 |
I agree with the principle that this API is too broad!
This sounds more complex and error-prone than requiring a little bit of upfront developer effort and then relying on the browser to handle scrolling entirely on its own. But I'll noodle on it.
Agreed! There's no reason to expose a full suite of attributes when all we need is an id.
I can always publish interim versions of draft-js for testing in the descript PRs. Will do once I take another pass.
We might not even need the content visibility stuff anymore if we turn on skeletons, since the offscreen nodes will be very simple. We should profile that.
|
yeah fair, I'm probably oversimplifying it, maybe worth a quick prototype to see if it's feasible? also, my claude keeps suggesting that the native |
For sure, will try it out! |
Do not demote a visible block when the same block key is removed and added in one DOM mutation batch. This prevents an unnecessary full-to-skeleton transition during keyed DOM moves.
|
oops totally forgot about this!! just saw it in my review list in linear 🫠 did another review pass w/ cursor, I think it found a real bug: if the last paragraph is split with a span with id (e.g. card boundary), when that last paragraph is rendered as skeleton, select all puts end cursor in the wrong place and doesn't actually select everything: Screen.Recording.2026-08-28.at.11.47.53.AM.movthe simplest fix I can think of (without diving too deep) is to not split paragraph with id spans - put the id spans at the beginning of paragraphs, and keep the entire paragraph as one text node. this only doesn't work if the paragraph is really long, where scrolling to the id would be inaccurate, but long paragraphs should be rare given we split them by default in transcription? |
scottcheng
left a comment
There was a problem hiding this comment.
approving anyway since it's already in prod 🙂
Persistent marker and card anchors split skeleton block text into multiple DOM nodes. Convert selection endpoints inside skeletons to cumulative block offsets so native selections, including Select All, cover the complete Draft content.
Automated fixes applied by autofix-pr: - install the pinned pnpm toolchain in CI - run the build on Node 24 with current setup actions Agent-Harness: Codex
Summary
This adds an Editor-owned skeleton mode for large documents. Offscreen blocks keep a lightweight, editable text subtree while skipping block renderers, decorator components, and leaf components; selected and near-viewport blocks use the normal renderer.
Visibility uses one
IntersectionObserverwith 500 px overscan, while structural edits are registered without a render-all measurement frame. Native scroll anchoring preserves position during promotion. Decorators can expose stable DOM IDs for addressable cards and markers, and skeleton text remains content-editable so native selection and editing continue to work without pinned blocks or keyboard special cases.Draft now reports skeleton-state DOM commits through
onBlockSkeletonsRendered. Consumers can refresh application-owned geometry after promotion without maintaining a parallel visibility signal. Draft does not inspect or rewrite applicationcontent-visibilitystyles, keeping those optimizations independent.Decisions
IntersectionObserveror native scroll anchoring falls back to full rendering.Test Plan
Published as
@descript/draft-js@0.11.6-descript.38for descriptinc/descript#38103.