Separate anywidget Model from widget binding lifecycle#8156
Merged
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
The Model class previously owned three independent concerns: widget definition loading (ESM import), model state/events/comm, and the binding between a widget definition and a model instance. This coupling meant every widget instance ran its own ESM import with no cross-instance deduplication, and Model had to manage AbortControllers and widget definition caching that weren't really its responsibility. This refactor extracts the widget lifecycle into a new `widget-binding.ts` module with three focused abstractions: - `WidgetDefRegistry` deduplicates ESM imports by content hash so multiple instances of the same widget share a single import. - `WidgetBinding` owns the initialize/render lifecycle per model, handling hot-reload teardown and view cleanup via AbortSignal. - `BindingManager` maps model IDs to bindings and ensures cleanup on close messages. Model is now purely about state, events, and comm. it no longer imports AnyWidget types or manages AbortControllers.
f367768 to
df24514
Compare
mscolnick
reviewed
Feb 6, 2026
When a model is closed, its lifecycle signal is aborted. However, the sendUpdate and sendCustomMessage closures could still fire if a widget called save_changes or send after the model was torn down. This adds early returns in both comm methods when signal.aborted is true, preventing unnecessary network requests to the backend for defunct models.
Collaborator
Author
|
Should be good to go |
mscolnick
approved these changes
Feb 6, 2026
|
🚀 Development release published. You may be able to view the changes at https://marimo.app?v=0.19.9-dev9 |
manzt
added a commit
that referenced
this pull request
Feb 6, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model lifecycle from widget binding and moved to a dedicated ModelCommand for frontend-to-backend communication. However, unlike UpdateUIElementCommand which goes through SetUIElementRequestManager's drain-and-merge path, ModelCommand was processed immediately — each rapid model update (e.g. dragging a map widget) triggered an individual cell re-execution. These changes puts `ModelCommand` on the same shared queue as `UpdateUIElementCommand` so both go through the same batching pipeline. When multiple model updates arrive in quick succession, they are now drained and merged (last-write-wins per model ID on state keys), matching the existing UI element behavior. The handler for model messages also now enqueues the resulting `UpdateUIElementCommand` back through the control queue instead of calling `set_ui_element_value` directly, so the downstream cell re-execution also benefits from batching.
manzt
added a commit
that referenced
this pull request
Feb 8, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model lifecycle from widget binding and moved to a dedicated ModelCommand for frontend-to-backend communication. However, unlike UpdateUIElementCommand which goes through SetUIElementRequestManager's drain-and-merge path, ModelCommand was processed immediately — each rapid model update (e.g. dragging a map widget) triggered an individual cell re-execution. These changes puts `ModelCommand` on the same shared queue as `UpdateUIElementCommand` so both go through the same batching pipeline. When multiple model updates arrive in quick succession, they are now drained and merged (last-write-wins per model ID on state keys), matching the existing UI element behavior. The handler for model messages also now enqueues the resulting `UpdateUIElementCommand` back through the control queue instead of calling `set_ui_element_value` directly, so the downstream cell re-execution also benefits from batching.
manzt
added a commit
that referenced
this pull request
Feb 9, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model lifecycle from widget binding and moved to a dedicated ModelCommand for frontend-to-backend communication. However, unlike UpdateUIElementCommand which goes through SetUIElementRequestManager's drain-and-merge path, ModelCommand was processed immediately — each rapid model update (e.g. dragging a map widget) triggered an individual cell re-execution. These changes puts `ModelCommand` on the same shared queue as `UpdateUIElementCommand` so both go through the same batching pipeline. When multiple model updates arrive in quick succession, they are now drained and merged (last-write-wins per model ID on state keys), matching the existing UI element behavior. The handler for model messages also now enqueues the resulting `UpdateUIElementCommand` back through the control queue instead of calling `set_ui_element_value` directly, so the downstream cell re-execution also benefits from batching.
manzt
added a commit
that referenced
this pull request
Feb 10, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model lifecycle from widget binding and moved to a dedicated ModelCommand for frontend-to-backend communication. However, unlike UpdateUIElementCommand which goes through SetUIElementRequestManager's drain-and-merge path, ModelCommand was processed immediately — each rapid model update (e.g. dragging a map widget) triggered an individual cell re-execution. These changes puts `ModelCommand` on the same shared queue as `UpdateUIElementCommand` so both go through the same batching pipeline. When multiple model updates arrive in quick succession, they are now drained and merged (last-write-wins per model ID on state keys), matching the existing UI element behavior. The handler for model messages also now enqueues the resulting `UpdateUIElementCommand` back through the control queue instead of calling `set_ui_element_value` directly, so the downstream cell re-execution also benefits from batching.
manzt
added a commit
that referenced
this pull request
Feb 10, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model lifecycle from widget binding and moved to a dedicated ModelCommand for frontend-to-backend communication. However, unlike UpdateUIElementCommand which goes through SetUIElementRequestManager's drain-and-merge path, ModelCommand was processed immediately — each rapid model update (e.g. dragging a map widget) triggered an individual cell re-execution. These changes puts `ModelCommand` on the same shared queue as `UpdateUIElementCommand` so both go through the same batching pipeline. When multiple model updates arrive in quick succession, they are now drained and merged (last-write-wins per model ID on state keys), matching the existing UI element behavior. The handler for model messages also now enqueues the resulting `UpdateUIElementCommand` back through the control queue instead of calling `set_ui_element_value` directly, so the downstream cell re-execution also benefits from batching.
manzt
added a commit
that referenced
this pull request
Feb 10, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model lifecycle from widget binding and moved to a dedicated ModelCommand for frontend-to-backend communication. However, unlike UpdateUIElementCommand which goes through SetUIElementRequestManager's drain-and-merge path, ModelCommand was processed immediately — each rapid model update (e.g. dragging a map widget) triggered an individual cell re-execution. These changes puts `ModelCommand` on the same shared queue as `UpdateUIElementCommand` so both go through the same batching pipeline. When multiple model updates arrive in quick succession, they are now drained and merged (last-write-wins per model ID on state keys), matching the existing UI element behavior. The handler for model messages also now enqueues the resulting `UpdateUIElementCommand` back through the control queue instead of calling `set_ui_element_value` directly, so the downstream cell re-execution also benefits from batching.
manzt
added a commit
that referenced
this pull request
Feb 10, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model lifecycle from widget binding and moved to a dedicated ModelCommand for frontend-to-backend communication. However, unlike UpdateUIElementCommand which goes through SetUIElementRequestManager's drain-and-merge path, ModelCommand was processed immediately — each rapid model update (e.g. dragging a map widget) triggered an individual cell re-execution. These changes puts `ModelCommand` on the same shared queue as `UpdateUIElementCommand` so both go through the same batching pipeline. When multiple model updates arrive in quick succession, they are now drained and merged (last-write-wins per model ID on state keys), matching the existing UI element behavior. The handler for model messages also now enqueues the resulting `UpdateUIElementCommand` back through the control queue instead of calling `set_ui_element_value` directly, so the downstream cell re-execution also benefits from batching.
manzt
added a commit
that referenced
this pull request
Feb 10, 2026
The anywidget refactors in #8156, #8159, and #8163 separated model lifecycle from widget binding and moved to a dedicated ModelCommand for frontend-to-backend communication. However, unlike UpdateUIElementCommand which goes through SetUIElementRequestManager's drain-and-merge path, ModelCommand was processed immediately — each rapid model update (e.g. dragging a map widget) triggered an individual cell re-execution. These changes puts `ModelCommand` on the same shared queue as `UpdateUIElementCommand` so both go through the same batching pipeline. When multiple model updates arrive in quick succession, they are now drained and merged (last-write-wins per model ID on state keys), matching the existing UI element behavior. The handler for model messages also now enqueues the resulting `UpdateUIElementCommand` back through the control queue instead of calling `set_ui_element_value` directly, so the downstream cell re-execution also benefits from batching.
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.
The Model class previously owned three independent concerns: widget definition loading (ESM import), model state/events/comm, and the binding between a widget definition and a model instance.
This coupling meant every widget instance ran its own ESM import with no cross-instance deduplication, and Model had to manage AbortControllers and widget definition caching that weren't really its responsibility.
This refactor extracts the widget lifecycle into a new
widget-binding.tsmodule with three focused abstractions:WidgetDefRegistrydeduplicates ESM imports by content hash so multiple instances of the same widget share a single import.WidgetBindingowns the initialize/render lifecycle per model, handling hot-reload teardown and view cleanup via AbortSignal.BindingManagermaps model IDs to bindings and ensures cleanup on close messages.Model is now purely about state, events, and comm. it no longer imports AnyWidget types or manages AbortControllers.