@perspective-dev/viewer 4.5.2 → 5.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/dist/cdn/perspective-viewer.js +20 -1
  2. package/dist/cdn/perspective-viewer.js.map +4 -4
  3. package/dist/css/botanical.css +1 -1
  4. package/dist/css/dracula.css +1 -1
  5. package/dist/css/gruvbox-dark.css +1 -1
  6. package/dist/css/gruvbox.css +1 -1
  7. package/dist/css/icons.css +1 -1
  8. package/dist/css/intl/de.css +1 -1
  9. package/dist/css/intl/es.css +1 -1
  10. package/dist/css/intl/fr.css +1 -1
  11. package/dist/css/intl/ja.css +1 -1
  12. package/dist/css/intl/pt.css +1 -1
  13. package/dist/css/intl/zh.css +1 -1
  14. package/dist/css/intl.css +1 -1
  15. package/dist/css/monokai.css +1 -1
  16. package/dist/css/phosphor.css +1 -1
  17. package/dist/css/pro-dark.css +1 -1
  18. package/dist/css/pro.css +1 -1
  19. package/dist/css/solarized-dark.css +1 -1
  20. package/dist/css/solarized.css +1 -1
  21. package/dist/css/themes.css +1 -1
  22. package/dist/css/vaporwave.css +1 -1
  23. package/dist/esm/extensions.d.ts +22 -3
  24. package/dist/esm/perspective-viewer.d.ts +9 -0
  25. package/dist/esm/perspective-viewer.inline.js +20 -1
  26. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  27. package/dist/esm/perspective-viewer.js +20 -1
  28. package/dist/esm/perspective-viewer.js.map +4 -4
  29. package/dist/esm/plugin.d.ts +71 -5
  30. package/dist/esm/ts-rs/ClientOptions.d.ts +7 -0
  31. package/dist/esm/ts-rs/ExportMethod.d.ts +1 -0
  32. package/dist/esm/ts-rs/ExportOptions.d.ts +9 -0
  33. package/dist/esm/ts-rs/GetClientOptions.d.ts +8 -0
  34. package/dist/esm/ts-rs/GetTableOptions.d.ts +8 -0
  35. package/dist/esm/ts-rs/Layout.d.ts +11 -0
  36. package/dist/esm/ts-rs/Orientation.d.ts +4 -0
  37. package/dist/esm/ts-rs/PanelOptions.d.ts +7 -0
  38. package/dist/esm/ts-rs/PanelViewerConfig.d.ts +41 -0
  39. package/dist/esm/ts-rs/SplitLayout.d.ts +7 -0
  40. package/dist/esm/ts-rs/TabLayout.d.ts +10 -0
  41. package/dist/esm/ts-rs/ViewerConfig.d.ts +8 -2
  42. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +9 -0
  43. package/dist/esm/ts-rs/WindowAggregate.d.ts +1 -0
  44. package/dist/esm/ts-rs/WindowSort.d.ts +7 -0
  45. package/dist/esm/ts-rs/WindowSortDir.d.ts +6 -0
  46. package/dist/esm/ts-rs/WindowSpec.d.ts +46 -0
  47. package/dist/esm/ts-rs/Windows.d.ts +10 -0
  48. package/dist/esm/ts-rs/WorkspaceConfig.d.ts +40 -0
  49. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +28 -0
  50. package/dist/wasm/perspective-viewer.d.ts +299 -78
  51. package/dist/wasm/perspective-viewer.js +544 -186
  52. package/dist/wasm/perspective-viewer.wasm +0 -0
  53. package/dist/wasm/perspective-viewer.wasm.d.ts +34 -26
  54. package/dist/wasm/snippets/perspective-viewer-8a8c7d5d696b43ae/inline0.js +6 -0
  55. package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline3.js → perspective-viewer-8a8c7d5d696b43ae/inline4.js} +5 -0
  56. package/package.json +3 -2
  57. package/src/css/_context_menu.css +14 -0
  58. package/src/css/column-selector.css +1 -0
  59. package/src/css/column-settings-panel.css +68 -3
  60. package/src/css/column-style.css +4 -3
  61. package/src/css/config-selector.css +22 -1
  62. package/src/css/containers/context-menu.css +107 -0
  63. package/src/css/containers/tabs.css +2 -2
  64. package/src/css/panel-tab.css +193 -0
  65. package/src/css/plugin-selector.css +7 -10
  66. package/src/css/status-bar.css +75 -81
  67. package/src/css/viewer.css +188 -70
  68. package/src/rust/components/column_dropdown.rs +40 -30
  69. package/src/rust/components/column_selector/active_column.rs +28 -45
  70. package/src/rust/components/column_selector/aggregate_selector.rs +4 -7
  71. package/src/rust/components/column_selector/column_selector_column_row.rs +73 -0
  72. package/src/rust/components/column_selector/config_selector.rs +40 -82
  73. package/src/rust/components/column_selector/expr_edit_button.rs +7 -1
  74. package/src/rust/components/column_selector/filter_column.rs +5 -15
  75. package/src/rust/components/column_selector/inactive_column.rs +11 -9
  76. package/src/rust/components/column_selector/sort_column.rs +3 -6
  77. package/src/rust/components/column_selector.rs +25 -14
  78. package/src/rust/components/column_settings_sidebar/style_tab.rs +2 -1
  79. package/src/rust/components/column_settings_sidebar/window_tab.rs +34 -0
  80. package/src/rust/components/column_settings_sidebar.rs +163 -16
  81. package/src/rust/components/containers/dragdrop_list.rs +73 -3
  82. package/src/rust/components/containers/sidebar.rs +99 -22
  83. package/src/rust/components/containers/split_panel.rs +181 -62
  84. package/src/rust/components/context_menu.rs +130 -0
  85. package/src/rust/components/editable_header.rs +2 -0
  86. package/src/rust/components/form/debug.rs +3 -0
  87. package/src/rust/components/global_filter_bar.rs +76 -0
  88. package/src/rust/components/main_panel/frame_theme.rs +186 -0
  89. package/src/rust/components/main_panel/msg.rs +62 -0
  90. package/src/rust/components/main_panel/presize.rs +220 -0
  91. package/src/rust/components/main_panel/reconcile.rs +224 -0
  92. package/src/rust/components/main_panel/render.rs +385 -0
  93. package/src/rust/components/main_panel/update.rs +184 -0
  94. package/src/rust/components/main_panel.rs +316 -71
  95. package/src/rust/components/mod.rs +5 -0
  96. package/src/rust/components/panel_menu.rs +493 -0
  97. package/src/rust/components/panel_tab.rs +510 -0
  98. package/src/rust/components/plugin_tab.rs +1 -1
  99. package/src/rust/components/portal.rs +7 -0
  100. package/src/rust/components/settings_panel.rs +13 -5
  101. package/src/rust/components/status_bar.rs +110 -178
  102. package/src/rust/components/status_indicator.rs +6 -9
  103. package/src/rust/components/style/mod.rs +5 -0
  104. package/src/rust/components/style/style_provider.rs +2 -0
  105. package/src/rust/components/viewer/filters.rs +133 -0
  106. package/src/rust/components/viewer/msg.rs +182 -0
  107. package/src/rust/components/viewer/panels.rs +326 -0
  108. package/src/rust/components/viewer/render.rs +255 -0
  109. package/src/rust/components/viewer/settings.rs +434 -0
  110. package/src/rust/components/viewer/snapshots.rs +134 -0
  111. package/src/rust/components/viewer/wiring.rs +505 -0
  112. package/src/rust/components/viewer.rs +245 -756
  113. package/src/rust/components/window_editor.rs +1013 -0
  114. package/src/rust/config/export_method.rs +1 -1
  115. package/src/rust/config/mod.rs +4 -0
  116. package/src/rust/config/options.rs +71 -0
  117. package/src/rust/config/viewer_config.rs +24 -2
  118. package/src/rust/config/workspace_config.rs +92 -0
  119. package/src/rust/custom_elements/copy_dropdown.rs +3 -1
  120. package/src/rust/custom_elements/debug_plugin.rs +20 -1
  121. package/src/rust/custom_elements/export_dropdown.rs +3 -1
  122. package/src/rust/custom_elements/viewer.rs +1015 -286
  123. package/src/rust/custom_events.rs +212 -61
  124. package/src/rust/js/mod.rs +2 -0
  125. package/src/rust/js/plugin.rs +297 -10
  126. package/src/rust/js/regular_layout.rs +258 -0
  127. package/src/rust/lib.rs +24 -2
  128. package/src/rust/presentation/column_locator.rs +16 -1
  129. package/src/rust/presentation.rs +66 -33
  130. package/src/rust/queries/columns_iter_set.rs +7 -1
  131. package/src/rust/queries/get_viewer_config.rs +29 -11
  132. package/src/rust/queries/plugin_column_styles.rs +2 -2
  133. package/src/rust/renderer/activate.rs +41 -14
  134. package/src/rust/renderer/dispatch.rs +463 -0
  135. package/src/rust/renderer/plugin_config.rs +495 -0
  136. package/src/rust/renderer/registry.rs +18 -6
  137. package/src/rust/renderer/render_timer.rs +29 -10
  138. package/src/rust/renderer.rs +384 -838
  139. package/src/rust/session/column_defaults_update.rs +26 -8
  140. package/src/rust/session/drag_drop_update.rs +18 -0
  141. package/src/rust/session/metadata.rs +66 -9
  142. package/src/rust/session/replace_expression_update.rs +1 -0
  143. package/src/rust/session/view_subscription.rs +30 -15
  144. package/src/rust/session.rs +755 -264
  145. package/src/rust/tasks/apply_global_filters.rs +89 -0
  146. package/src/rust/tasks/auto_pause.rs +171 -0
  147. package/src/rust/tasks/copy_export.rs +29 -19
  148. package/src/rust/tasks/create_panel.rs +243 -0
  149. package/src/rust/tasks/dismiss_render_warning.rs +3 -1
  150. package/src/rust/tasks/edit_expression.rs +4 -4
  151. package/src/rust/tasks/edit_window.rs +155 -0
  152. package/src/rust/tasks/eject.rs +64 -23
  153. package/src/rust/tasks/mod.rs +30 -4
  154. package/src/rust/tasks/pipeline.rs +411 -0
  155. package/src/rust/tasks/presize_panels.rs +324 -0
  156. package/src/rust/tasks/reset_all.rs +22 -12
  157. package/src/rust/tasks/resize_observer.rs +124 -87
  158. package/src/rust/tasks/restore_and_render.rs +80 -86
  159. package/src/rust/tasks/restore_panel.rs +145 -0
  160. package/src/rust/tasks/send_column_config.rs +5 -2
  161. package/src/rust/tasks/send_plugin_config.rs +5 -2
  162. package/src/rust/tasks/set_edit_mode.rs +59 -0
  163. package/src/rust/tasks/sync_update_panels.rs +152 -0
  164. package/src/rust/tasks/table_lifecycle.rs +158 -0
  165. package/src/rust/tasks/update_theme.rs +56 -12
  166. package/src/rust/utils/browser/dragdrop.rs +12 -0
  167. package/src/rust/utils/browser/tests/debounce.rs +49 -0
  168. package/src/rust/utils/completion.rs +56 -0
  169. package/src/rust/utils/custom_element.rs +5 -0
  170. package/src/rust/utils/debounce.rs +189 -17
  171. package/src/rust/utils/mod.rs +6 -0
  172. package/src/rust/utils/pending_effects.rs +62 -0
  173. package/src/rust/utils/spawn.rs +33 -0
  174. package/src/rust/utils/weak_scope.rs +0 -10
  175. package/src/rust/workspace.rs +846 -0
  176. package/src/svg/active-indicator.svg +3 -0
  177. package/src/svg/pin-icon.svg +5 -0
  178. package/src/themes/botanical.css +5 -2
  179. package/src/themes/defaults.css +54 -2
  180. package/src/themes/dracula.css +6 -3
  181. package/src/themes/gruvbox-dark.css +5 -2
  182. package/src/themes/gruvbox.css +5 -2
  183. package/src/themes/icons.css +5 -2
  184. package/src/themes/intl/de.css +7 -0
  185. package/src/themes/intl/es.css +7 -0
  186. package/src/themes/intl/fr.css +7 -0
  187. package/src/themes/intl/ja.css +7 -0
  188. package/src/themes/intl/pt.css +7 -0
  189. package/src/themes/intl/zh.css +7 -0
  190. package/src/themes/intl.css +10 -1
  191. package/src/themes/monokai.css +5 -2
  192. package/src/themes/phosphor.css +5 -2
  193. package/src/themes/pro-dark.css +5 -2
  194. package/src/themes/pro.css +2 -1
  195. package/src/themes/solarized-dark.css +5 -2
  196. package/src/themes/solarized.css +5 -2
  197. package/src/themes/vaporwave.css +5 -2
  198. package/src/ts/bootstrap.ts +8 -7
  199. package/src/ts/extensions.ts +30 -2
  200. package/src/ts/perspective-viewer.ts +10 -0
  201. package/src/ts/plugin.ts +72 -5
  202. package/src/ts/ts-rs/ClientOptions.ts +7 -0
  203. package/src/ts/ts-rs/ExportMethod.ts +3 -0
  204. package/src/ts/ts-rs/ExportOptions.ts +8 -0
  205. package/src/ts/ts-rs/GetClientOptions.ts +7 -0
  206. package/src/ts/ts-rs/GetTableOptions.ts +7 -0
  207. package/src/ts/ts-rs/Layout.ts +9 -0
  208. package/src/ts/ts-rs/Orientation.ts +6 -0
  209. package/src/ts/ts-rs/PanelOptions.ts +7 -0
  210. package/src/ts/ts-rs/PanelViewerConfig.ts +16 -0
  211. package/src/ts/ts-rs/SplitLayout.ts +5 -0
  212. package/src/ts/ts-rs/TabLayout.ts +11 -0
  213. package/src/ts/ts-rs/ViewerConfig.ts +7 -2
  214. package/src/ts/ts-rs/ViewerConfigUpdate.ts +9 -0
  215. package/src/ts/ts-rs/WindowAggregate.ts +3 -0
  216. package/src/ts/ts-rs/WindowSort.ts +9 -0
  217. package/src/ts/ts-rs/WindowSortDir.ts +8 -0
  218. package/src/ts/ts-rs/WindowSpec.ts +46 -0
  219. package/src/ts/ts-rs/Windows.ts +10 -0
  220. package/src/ts/ts-rs/WorkspaceConfig.ts +36 -0
  221. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +24 -0
  222. package/src/rust/tasks/intersection_observer.rs +0 -98
  223. package/src/rust/tasks/update_and_render.rs +0 -110
  224. /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline0.js → perspective-viewer-8a8c7d5d696b43ae/inline1.js} +0 -0
  225. /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline1.js → perspective-viewer-8a8c7d5d696b43ae/inline2.js} +0 -0
  226. /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline2.js → perspective-viewer-8a8c7d5d696b43ae/inline3.js} +0 -0
  227. /package/dist/wasm/snippets/{perspective-viewer-93064d0d79855d97/inline4.js → perspective-viewer-8a8c7d5d696b43ae/inline5.js} +0 -0
@@ -20,16 +20,19 @@ use crate::config::PluginStaticConfig;
20
20
 
21
21
  #[extend::ext]
22
22
  pub impl ViewConfigUpdate {
23
- /// Appends additional columns to the `columns` field of this
24
- /// `ViewConfigUpdate` by picking appropriate new columns from the
25
- /// `SessionMetadata`, given the necessary column requirements of the
26
- /// plugin provided by a `PluginStaticConfig`. For example, an "X/Y
27
- /// Scatter" chart needs a minimum of 2 numeric columns to be
28
- /// drawable.
29
- fn set_update_column_defaults(
23
+ /// Coerce this update's `group_rollup_mode` to one the plugin accepts
24
+ /// (`PluginStaticConfig::group_rollup_modes`, feature-filtered). An
25
+ /// absent mode counts as `Rollup` for the acceptance check, so a plugin
26
+ /// that only renders `flat` (Treemap / Sunburst) gets it stamped even
27
+ /// when the update never mentions rollup at all.
28
+ ///
29
+ /// Split out of [`Self::set_update_column_defaults`] so same-plugin
30
+ /// restores can enforce the mode WITHOUT the column-defaulting below —
31
+ /// that path must not touch `columns` (it would drop non-numeric color
32
+ /// slots from a partial restore).
33
+ fn set_update_rollup_defaults(
30
34
  &mut self,
31
35
  metadata: &SessionMetadata,
32
- columns: &[Option<String>],
33
36
  config_static: &PluginStaticConfig,
34
37
  ) {
35
38
  let rollup_features = metadata
@@ -49,6 +52,21 @@ pub impl ViewConfigUpdate {
49
52
  self.group_rollup_mode
50
53
  );
51
54
  }
55
+ }
56
+
57
+ /// Appends additional columns to the `columns` field of this
58
+ /// `ViewConfigUpdate` by picking appropriate new columns from the
59
+ /// `SessionMetadata`, given the necessary column requirements of the
60
+ /// plugin provided by a `PluginStaticConfig`. For example, an "X/Y
61
+ /// Scatter" chart needs a minimum of 2 numeric columns to be
62
+ /// drawable.
63
+ fn set_update_column_defaults(
64
+ &mut self,
65
+ metadata: &SessionMetadata,
66
+ columns: &[Option<String>],
67
+ config_static: &PluginStaticConfig,
68
+ ) {
69
+ self.set_update_rollup_defaults(metadata, config_static);
52
70
 
53
71
  if let (None, Some(min_cols)) = (&self.columns, config_static.min_config_columns) {
54
72
  let names_len = config_static.config_column_names.len();
@@ -96,9 +96,27 @@ pub impl ViewConfig {
96
96
  config.filter.retain(|x| x.column() != column);
97
97
  update.filter = Some(config.filter.clone());
98
98
  },
99
+ DragEffect::Move(
100
+ DragTarget::WindowSource
101
+ | DragTarget::WindowOrderBy
102
+ | DragTarget::WindowPartitionBy,
103
+ ) => {
104
+ // Staged origins (window-editor pills) have nothing to remove
105
+ // HERE - the editor's draft is not part of the view config
106
+ // this update mutates. The open `WindowEditor` observes the
107
+ // same `drop_received` event and clears the origin slot in
108
+ // its draft, completing the move.
109
+ },
99
110
  }
100
111
 
101
112
  match drop {
113
+ DragTarget::WindowSource
114
+ | DragTarget::WindowOrderBy
115
+ | DragTarget::WindowPartitionBy => {
116
+ // Unreachable: staged drops never build config updates
117
+ // (`DragTarget::is_staged` is excluded by every committing
118
+ // `drop_received` subscriber).
119
+ },
102
120
  DragTarget::Active => {
103
121
  if !is_swap_to_after_last {
104
122
  if is_to_swap || is_from_required {
@@ -69,6 +69,7 @@ pub struct SessionMetadataState {
69
69
  edit_port: f64,
70
70
  view_schema: Option<HashMap<String, ColumnType>>,
71
71
  expr_meta: Option<SessionViewExpressionMetadata>,
72
+ window_schema: HashMap<String, ColumnType>,
72
73
  }
73
74
 
74
75
  impl SessionMetadata {
@@ -95,6 +96,43 @@ impl SessionMetadata {
95
96
  Ok(())
96
97
  }
97
98
 
99
+ /// Records the pre-aggregation types of the config's window columns, so
100
+ /// `get_column_table_type` resolves them like any other derived column.
101
+ /// Types mirror the engine's `t_window_engine::resolve_dtype` rules from
102
+ /// the window's op and its source's table type - independent of any
103
+ /// aggregation the view may also apply.
104
+ pub(super) fn update_windows(
105
+ &mut self,
106
+ windows: &perspective_client::config::Windows,
107
+ ) -> ApiResult<()> {
108
+ use perspective_client::config::WindowAggregate;
109
+ let window_schema = windows
110
+ .iter()
111
+ .filter_map(|(name, w)| {
112
+ let source = self.get_column_table_type(&w.column)?;
113
+ let dtype = match w.aggregate {
114
+ WindowAggregate::Sum
115
+ | WindowAggregate::Avg
116
+ | WindowAggregate::Stddev
117
+ | WindowAggregate::Var
118
+ | WindowAggregate::Rate
119
+ | WindowAggregate::Ema
120
+ | WindowAggregate::Diff => ColumnType::Float,
121
+ WindowAggregate::Count => ColumnType::Integer,
122
+ WindowAggregate::Min
123
+ | WindowAggregate::Max
124
+ | WindowAggregate::First
125
+ | WindowAggregate::Last
126
+ | WindowAggregate::Lag
127
+ | WindowAggregate::Lead => source,
128
+ };
129
+ Some((name.clone(), dtype))
130
+ })
131
+ .collect();
132
+ self.as_mut().unwrap().window_schema = window_schema;
133
+ Ok(())
134
+ }
135
+
98
136
  /// Whether `view_schema` has been populated by a prior successful
99
137
  /// `create_view`. Used by `Session::create_view` to force a full
100
138
  /// build on first run even when `is_clean` happens to be true, so
@@ -212,6 +250,19 @@ impl SessionMetadata {
212
250
  self.as_ref().map(|meta| &meta.column_names)
213
251
  }
214
252
 
253
+ pub fn is_column_window(&self, name: &str) -> bool {
254
+ self.as_ref()
255
+ .map(|meta| meta.window_schema.contains_key(name))
256
+ .unwrap_or_default()
257
+ }
258
+
259
+ pub fn get_window_columns(&self) -> impl Iterator<Item = &'_ String> {
260
+ self.as_ref()
261
+ .map(|meta| meta.window_schema.keys())
262
+ .into_iter()
263
+ .flatten()
264
+ }
265
+
215
266
  pub fn is_column_expression(&self, name: &str) -> bool {
216
267
  let is_expr: Option<bool> = try {
217
268
  self.as_ref()?
@@ -253,14 +304,18 @@ impl SessionMetadata {
253
304
  pub fn get_column_table_type(&self, name: &str) -> Option<ColumnType> {
254
305
  try {
255
306
  let meta = self.as_ref()?;
256
- meta.table_schema.get(name).cloned().or_else(|| {
257
- meta.expr_meta
258
- .as_ref()?
259
- .expressions
260
- .expression_schema
261
- .get(name)
262
- .cloned()
263
- })?
307
+ meta.table_schema
308
+ .get(name)
309
+ .cloned()
310
+ .or_else(|| {
311
+ meta.expr_meta
312
+ .as_ref()?
313
+ .expressions
314
+ .expression_schema
315
+ .get(name)
316
+ .cloned()
317
+ })
318
+ .or_else(|| meta.window_schema.get(name).cloned())?
264
319
  }
265
320
  }
266
321
 
@@ -282,7 +337,9 @@ impl SessionMetadata {
282
337
  /// expressions.
283
338
  pub fn locator_name_or_default(&self, locator: &ColumnLocator) -> String {
284
339
  match locator {
285
- ColumnLocator::Table(s) | ColumnLocator::Expression(s) => s.clone(),
340
+ ColumnLocator::Table(s) | ColumnLocator::Expression(s) | ColumnLocator::Window(s) => {
341
+ s.clone()
342
+ },
286
343
  ColumnLocator::NewExpression => self.make_new_column_name(None),
287
344
  }
288
345
  }
@@ -126,6 +126,7 @@ pub impl ViewConfig {
126
126
  filter_op: None,
127
127
  group_by_depth: None,
128
128
  group_rollup_mode: None,
129
+ windows: None,
129
130
  }
130
131
  }
131
132
  }
@@ -36,6 +36,7 @@ pub struct ViewStats {
36
36
  struct ViewSubscriptionData {
37
37
  view: View,
38
38
  config: Rc<ViewConfig>,
39
+ build_config: Rc<ViewConfig>,
39
40
  callback_id: Rc<Cell<u32>>,
40
41
  on_stats: Callback<ViewStats>,
41
42
  on_update: Option<Callback<()>>,
@@ -59,8 +60,6 @@ impl ViewSubscriptionData {
59
60
  Ok(JsValue::UNDEFINED)
60
61
  }
61
62
 
62
- /// TODO Use serde to serialize the full view config, instead of calculating
63
- /// `is_aggregated` here.
64
63
  async fn update_view_stats(self) -> ApiResult<JsValue> {
65
64
  let dimensions = self.view.dimensions().await?;
66
65
  let num_rows = dimensions.num_table_rows as u32;
@@ -80,13 +79,16 @@ impl ViewSubscriptionData {
80
79
  }
81
80
 
82
81
  async fn internal_delete(&self) -> ApiResult<()> {
82
+ if self.is_deleted.replace(true) {
83
+ return Ok(());
84
+ }
85
+
83
86
  let view = &self.view;
84
87
  if self.on_update.is_some() {
85
88
  view.remove_update(self.callback_id.get()).await?;
86
89
  }
87
90
 
88
91
  view.delete().await?;
89
- self.is_deleted.set(true);
90
92
  Ok(())
91
93
  }
92
94
  }
@@ -104,13 +106,15 @@ impl ViewSubscription {
104
106
  /// `View.on_update()`.
105
107
  pub async fn new(
106
108
  view: perspective_client::View,
107
- config: ViewConfig,
109
+ config: Rc<ViewConfig>,
110
+ build_config: Rc<ViewConfig>,
108
111
  on_stats: Callback<ViewStats>,
109
112
  on_update: Option<Callback<()>>,
110
113
  ) -> Result<Self, ApiError> {
111
114
  let data = ViewSubscriptionData {
112
115
  view,
113
- config: config.into(),
116
+ config,
117
+ build_config,
114
118
  on_stats,
115
119
  callback_id: Rc::default(),
116
120
  on_update,
@@ -142,10 +146,13 @@ impl ViewSubscription {
142
146
  }
143
147
 
144
148
  /// It is possible to re-use a `ViewSubscription` without a costly
145
- /// resubscribe under certain conditions, which still need an updated
146
- /// `ViewConfig`.
147
- pub fn update_view_config(&mut self, config: Rc<ViewConfig>) {
148
- self.data.config = config
149
+ /// resubscribe when the new config is engine-equivalent to the one the
150
+ /// bound `View` was built from (placeholder-only differences); the
151
+ /// snapshots still need updating so reads stay consistent with the run
152
+ /// that adopted them.
153
+ pub fn set_configs(&mut self, config: Rc<ViewConfig>, build_config: Rc<ViewConfig>) {
154
+ self.data.config = config;
155
+ self.data.build_config = build_config;
149
156
  }
150
157
 
151
158
  /// Getter for the underlying `View()`.
@@ -153,16 +160,24 @@ impl ViewSubscription {
153
160
  &self.data.view
154
161
  }
155
162
 
156
- /// Snapshot of the [`ViewConfig`] the bound `View` was constructed
157
- /// from (or its operationally-equivalent successor on the
158
- /// [`ValidSession::create_view`] fast path). This is the
159
- /// [`ViewConfig`] consistent with the data the active plugin is
160
- /// currently rendering — not the live session config, which may
161
- /// have been mutated synchronously ahead of the next queued draw.
163
+ /// User-facing snapshot of the [`ViewConfig`] the bound `View` was
164
+ /// constructed from (the persisted config at build time — global-filter
165
+ /// overlay excluded). This is the [`ViewConfig`] consistent with the
166
+ /// data the active plugin is currently rendering — not the live session
167
+ /// config, which may have been mutated synchronously ahead of the next
168
+ /// queued run.
162
169
  pub fn get_view_config(&self) -> Rc<ViewConfig> {
163
170
  self.data.config.clone()
164
171
  }
165
172
 
173
+ /// The EFFECTIVE [`ViewConfig`] the bound `View` was built from
174
+ /// (persisted config + global-filter overlay at build time). This is
175
+ /// the value `Session::bind_view` compares a run's snapshot against to
176
+ /// decide SKIP / REUSE / REBUILD.
177
+ pub fn build_config(&self) -> Rc<ViewConfig> {
178
+ self.data.build_config.clone()
179
+ }
180
+
166
181
  /// Delete this `View`. Neglecting to call this method before a
167
182
  /// `ViewSubscription` is dropped will result in a log warning, but the
168
183
  /// `View` will not leak.