fix(messaging): stop reusable hint flicker on cell updates#9782
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
There was a problem hiding this comment.
No issues found across 7 files
Architecture diagram
sequenceDiagram
participant FE as Frontend Cell Store
participant WS as WebSocket Stream
participant BE as Backend Notification
participant Runner as Post-Exec Hook
participant Session as Session View
Note over FE,Session: Cell serialization hint three-state contract
Runner->>BE: NEW: broadcast_serialization_cleared(cell_id)
Note right of BE: Cell no longer a top-level def
alt Cell IS a top-level definition
Runner->>BE: broadcast_serialization(cell_id, str(serialization))
BE->>WS: CellNotification(serialization="reusable")
else Cell is NOT a top-level definition (NEW branch)
Runner->>BE: broadcast_serialization_cleared(cell_id)
BE->>WS: CellNotification(serialization=None)
end
WS-->>FE: CellNotification arrives
alt serialization is undefined (msgspec.UNSET/omitted)
FE->>FE: CHANGED: Skip update, hint unchanged
else serialization is null
FE->>FE: CHANGED: Clear hint (set to null)
else serialization is string
FE->>FE: Set hint to value
end
Note over FE,Session: Reconnect/replay path
Session->>Session: CHANGED: merge_cell_notification()
alt current.serialization is UNSET
Session->>Session: CHANGED: Inherit previous.serialization
else serialization is null or string
Session->>Session: Pass through as-is
end
Session-->>FE: Snapshot with correct hint state
Note over FE,Session: Key behavior changes
Note over WS,BE: Before: serialization=None sent on every status update
Note over WS,BE: After: serialization omitted (UNSET) unless changed
Contributor
There was a problem hiding this comment.
Pull request overview
This PR fixes flickering and lost “reusable definition” (serialization) hints by making CellNotification.serialization follow the project’s partial-update semantics: omitted/UNSET means “unchanged”, explicit null clears, and a string sets the hint. This stabilizes the hint across routine status updates, lazy execution, and reconnect/replay merges.
Changes:
- Backend: make
CellNotification.serializationtri-state viamsgspec.UNSET, preserve prior hints during reconnect merge, and explicitly clear hints when a cell stops being a top-level definition. - Frontend: treat
serializationas a partial-update field (ignore when omitted/undefined, apply whennullor string). - OpenAPI: update schema/docs to remove the implicit
default: nulland describe the new contract.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/openapi/src/api.ts | Updates generated TS OpenAPI typings/docs for the tri-state serialization field. |
| packages/openapi/api.yaml | Updates OpenAPI schema/docs to reflect omission vs null vs string for serialization. |
| marimo/_session/state/session_view.py | Preserves the previous serialization hint when the incoming notification leaves it UNSET (reconnect/replay). |
| marimo/_runtime/runner/hooks_post_execution.py | Explicitly clears the hint when a cell stops being a top-level definition. |
| marimo/_messaging/notification.py | Changes CellNotification.serialization to tri-state (msgspec.UNSET default) and documents the contract. |
| marimo/_messaging/notification_utils.py | Adds a helper to broadcast an explicit clear (serialization=None). |
| frontend/src/core/cells/cell.ts | Applies tri-state partial-update semantics for serialization (undefined no-op, null clear, string set). |
a372ebb to
c9b9e73
Compare
c9b9e73 to
0b658ef
Compare
0b658ef to
65e3a6a
Compare
Treat the serialization hint as a tri-state partial update: msgspec.UNSET leaves it unchanged, None explicitly clears it, a string sets it. Previously every queued/status message carried serialization=None and wiped the hint (permanently under lazy execution); the reconnect merge also dropped it.
65e3a6a to
5bdf12e
Compare
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.
Problem
The reusable-definition hint (
?/reusablebadge on a top-leveldef/classcell) flickered whenever the cell received any update, and vanished permanently under lazy execution.Root cause:
serializationwas the oneCellNotificationfield that did not follow the partial-update convention. The frontend assigned it unconditionally, and the backend sentserialization=Noneon every queued/running/idle message. So each status update wiped the hint; under eager execution the post-execution broadcast re-set it (the flicker), and under lazy execution the cell never re-ran so the hint never came back. The reconnect/replay merge dropped it for the same reason.Fix
Adopt a three-state contract for the hint, with
msgspec.UNSETas the "unchanged" sentinel soNoneis free to mean "clear":msgspec.UNSET(omitted on the wire) -> leave the hint unchangedNone(explicitnull) -> clear the hint (cell is no longer a top-level def)The frontend mirrors this as
undefined(unchanged) vsnull(clear) vs value. The post-execution hook now explicitly clears the hint when a cell stops being a top-level definition, and the session-view merge inherits the prior hint onUNSET, fixing the reconnect path too.To clear the hint when a cell stops being a top-level def, we need to know if it was showing one before, otherwise every normal cell run would send a redundant clear. We track this on the
DirectedGraphby cell id rather thanCellImpl, sinceCellImplis replaced when a cell's code changes, which is the exact transition we need to catch.Tests
cell.test.ts:transitionCellkeeps the hint whenserializationis omitted, clears it onnull, sets it on a string.test_session_view.py: the reconnect merge keeps the old hint onUNSETand applies aNoneclear.test_runtime.py: editing a top-level def into a plain assignment sends one clear; normal cell runs send none.Follow-up tracked in MO-6367 to migrate remaining partial-update fields to the same convention.
Closes MO-6336