Skip to content

Intern shared styles and skip unchanged updates - #14

Merged
remorses merged 3 commits into
mainfrom
intern-shared-styles
Aug 24, 2026
Merged

remorses merged 3 commits into
mainfrom
intern-shared-styles

Conversation

@remorses

Copy link
Copy Markdown
Owner

Took the intern/skip-style work off main so it can land after a real profile.

What this does

  • Shared JS style objects go to Rust once (internStyle) and attach with setStyleId
  • Nodes store Arc<StyleDesc> instead of a cloned 1272-byte struct
  • Later commits skip setStyle when the object or fields did not change
  • Intern ids live for the renderer life. A bad setStyleId fails before any batch mutation

Why it is a PR
No profile yet. Skip-unchanged should cut chrome-update FFI. Intern should cut first-batch parse and retained copies when many nodes share one object. Chat already windows rows, so intern is not a 10k-row RAM win there.

Check

  • packages/react style-updates + styles + events + virtual-list
  • examples/chat.test.tsx 9/9
  • Live launch() of chat.tsx, sidebar open/collapse screenshots

Do not mutate a style object in place. Pass a new object.

StyleDesc is 1272 bytes even when most fields are unset. Long lists that
reuse the same JS style object used to clone that struct onto every node.

Rust now keeps a renderer-lifetime intern table. JS can send internStyle
once, then attach many nodes with setStyleId. Nodes store Arc<StyleDesc>,
so 20 row wrappers share one allocation.

Intern ids are not reused while the renderer is live. A conflicting
internStyle or an unknown setStyleId fails before any batch mutation
applies. Hide, destroy, and later reuse of the same style object still
resolve.

Session: ses_fcd734a97ffeCh4hPdoJNAd9qV
commitUpdate always sent setStyle, including empty objects on every
text node. That made chrome updates rewrite styles that did not change.

The reconciler now skips a style when the object is the same reference
or the fields match. Shared style constants are interned once and
attached with setStyleId.

Instance no longer keeps a copy of props. Refs expose id and type.
Mutating a style object in place is not supported; pass a new object.

Session: ses_fcd734a97ffeCh4hPdoJNAd9qV
Shared style objects now cross FFI once. Later commits skip styles that
did not change. This cuts batch size and retained copies for long lists
that reuse style constants.

Session: ses_fcd734a97ffeCh4hPdoJNAd9qV

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad1635bc13

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".


function resetStyleIntern(): void {
styleIntern = new WeakMap()
nextStyleInternId = 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve intern IDs across hot remounts

When render() remounts on the same native host—the supported bun --hot path—createRoot() creates a new batching proxy, so this reset starts IDs again at 1 even though the Rust RetainedTree retains its previous intern table. If a style changes across the save, internStyle(1, newStyle) conflicts with the old entry and aborts the remount batch; associate the cache/counter with the underlying renderer or clear its native intern table instead.

AGENTS.md reference: AGENTS.md:L581-L581

Useful? React with 👍 / 👎.

Comment on lines +150 to +152
const styleId = nextStyleInternId++
styleIntern.set(style, styleId)
getRenderer().internStyle(styleId, style)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid permanently interning one-off dynamic styles

When a component drives a changing inline style, such as style={{ width: dragX }}, every update whose fields differ allocates a fresh ID here. The corresponding Rust map never removes entries—even after the element or JS object is collected—so a continuously updated element grows native memory without bound for the renderer's lifetime; only genuinely shared styles should be interned, or unused IDs need reclamation.

Useful? React with 👍 / 👎.

@remorses
remorses merged commit ad1635b into main Aug 24, 2026
9 checks passed
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