@perspective-dev/viewer 5.1.0 → 5.3.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 (245) hide show
  1. package/dist/cdn/perspective-viewer.js +9 -9
  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/docs/perspective-docs.json +1 -0
  24. package/dist/esm/column-format.d.ts +19 -28
  25. package/dist/esm/perspective-viewer.d.ts +3 -0
  26. package/dist/esm/perspective-viewer.inline.js +9 -9
  27. package/dist/esm/perspective-viewer.inline.js.map +4 -4
  28. package/dist/esm/perspective-viewer.js +9 -9
  29. package/dist/esm/perspective-viewer.js.map +4 -4
  30. package/dist/esm/plugin.d.ts +10 -0
  31. package/dist/esm/providers.d.ts +79 -0
  32. package/dist/esm/ts-rs/CompactDisplay.d.ts +5 -0
  33. package/dist/esm/ts-rs/CurrencyCode.d.ts +1 -0
  34. package/dist/esm/ts-rs/CurrencyDisplay.d.ts +1 -0
  35. package/dist/esm/ts-rs/CurrencyNumberFormatStyle.d.ts +8 -0
  36. package/dist/esm/ts-rs/CurrencySign.d.ts +1 -0
  37. package/dist/esm/ts-rs/CustomDatetimeFormat.d.ts +1 -0
  38. package/dist/esm/ts-rs/CustomDatetimeStyleConfig.d.ts +54 -0
  39. package/dist/esm/ts-rs/CustomNumberFormatConfig.d.ts +34 -0
  40. package/dist/esm/ts-rs/DatetimeColorMode.d.ts +1 -0
  41. package/dist/esm/ts-rs/DatetimeFormatType.d.ts +6 -0
  42. package/dist/esm/ts-rs/FormatMode.d.ts +1 -0
  43. package/dist/esm/ts-rs/FormatUnit.d.ts +1 -0
  44. package/dist/esm/ts-rs/Notation.d.ts +17 -0
  45. package/dist/esm/ts-rs/NumberFormatStyle.d.ts +18 -0
  46. package/dist/esm/ts-rs/PanelViewerConfig.d.ts +42 -2
  47. package/dist/esm/ts-rs/PluginStaticConfig.d.ts +35 -0
  48. package/dist/esm/ts-rs/RestoreOptions.d.ts +19 -0
  49. package/dist/esm/ts-rs/RoundingMode.d.ts +1 -0
  50. package/dist/esm/ts-rs/RoundingPriority.d.ts +1 -0
  51. package/dist/esm/ts-rs/SaveWorkspaceOptions.d.ts +10 -0
  52. package/dist/esm/ts-rs/SignDisplay.d.ts +1 -0
  53. package/dist/esm/ts-rs/SimpleDatetimeFormat.d.ts +1 -0
  54. package/dist/esm/ts-rs/SimpleDatetimeStyleConfig.d.ts +24 -0
  55. package/dist/esm/ts-rs/SplitRollupMode.d.ts +9 -0
  56. package/dist/esm/ts-rs/StringColorMode.d.ts +1 -0
  57. package/dist/esm/ts-rs/TrailingZeroDisplay.d.ts +1 -0
  58. package/dist/esm/ts-rs/Unit.d.ts +1 -0
  59. package/dist/esm/ts-rs/UnitDisplay.d.ts +1 -0
  60. package/dist/esm/ts-rs/UnitNumberFormatStyle.d.ts +6 -0
  61. package/dist/esm/ts-rs/UseGrouping.d.ts +1 -0
  62. package/dist/esm/ts-rs/ViewerConfig.d.ts +42 -2
  63. package/dist/esm/ts-rs/ViewerConfigInitial.d.ts +175 -0
  64. package/dist/esm/ts-rs/ViewerConfigUpdate.d.ts +63 -0
  65. package/dist/esm/ts-rs/WindowSpec.d.ts +3 -2
  66. package/dist/esm/ts-rs/WorkspaceConfig.d.ts +9 -1
  67. package/dist/esm/ts-rs/WorkspaceConfigUpdate.d.ts +20 -9
  68. package/dist/wasm/perspective-viewer.d.ts +157 -41
  69. package/dist/wasm/perspective-viewer.js +452 -151
  70. package/dist/wasm/perspective-viewer.wasm +0 -0
  71. package/dist/wasm/perspective-viewer.wasm.d.ts +22 -17
  72. package/package.json +2 -1
  73. package/src/css/_viewer_bundle.css +1 -0
  74. package/src/css/column-selector.css +8 -0
  75. package/src/css/column-settings-panel.css +9 -5
  76. package/src/css/column-style.css +209 -31
  77. package/src/css/config-selector.css +38 -3
  78. package/src/css/dom/select.css +2 -1
  79. package/src/css/form/code-editor.css +6 -7
  80. package/src/css/form/mirrored-textarea.css +81 -0
  81. package/src/css/viewer.css +328 -0
  82. package/src/rust/agent/client/mod.rs +19 -0
  83. package/src/rust/agent/client/protocol.rs +320 -0
  84. package/src/rust/agent/client/transport.rs +287 -0
  85. package/src/rust/agent/client/turn.rs +174 -0
  86. package/src/rust/agent/config.rs +159 -0
  87. package/src/rust/agent/docs.rs +293 -0
  88. package/src/rust/agent/markdown/block.rs +509 -0
  89. package/src/rust/agent/markdown/entity.rs +52 -0
  90. package/src/rust/agent/markdown/events.rs +71 -0
  91. package/src/rust/agent/markdown/inline.rs +497 -0
  92. package/src/rust/agent/markdown/mod.rs +466 -0
  93. package/src/rust/agent/mod.rs +23 -0
  94. package/src/rust/agent/preamble.md +16 -0
  95. package/src/rust/agent/runtime.rs +201 -0
  96. package/src/rust/agent/slot.rs +236 -0
  97. package/src/rust/agent/tools.rs +1141 -0
  98. package/src/rust/components/chat_panel.rs +289 -0
  99. package/src/rust/components/column_selector/active_column.rs +2 -2
  100. package/src/rust/components/column_selector/add_expression_button.rs +3 -3
  101. package/src/rust/components/column_selector/aggregate_selector.rs +2 -3
  102. package/src/rust/components/column_selector/config_selector.rs +84 -7
  103. package/src/rust/components/column_selector/expr_edit_button.rs +4 -10
  104. package/src/rust/components/column_selector/inactive_column.rs +2 -2
  105. package/src/rust/components/column_selector.rs +55 -4
  106. package/src/rust/components/column_settings_sidebar/style_tab/primitive_field.rs +293 -126
  107. package/src/rust/components/column_settings_sidebar/style_tab.rs +262 -225
  108. package/src/rust/components/column_settings_sidebar.rs +88 -69
  109. package/src/rust/components/containers/dragdrop_list.rs +37 -0
  110. package/src/rust/components/editable_header.rs +85 -144
  111. package/src/rust/components/expression_editor.rs +9 -16
  112. package/src/rust/components/form/code_editor.rs +29 -28
  113. package/src/rust/components/form/debug.rs +9 -5
  114. package/src/rust/components/form/mirrored_textarea.rs +165 -0
  115. package/src/rust/components/form/mod.rs +4 -1
  116. package/src/rust/components/form/multi_stop_gradient_selector.rs +476 -0
  117. package/src/rust/components/form/named_value_picker.rs +86 -0
  118. package/src/rust/components/form/palette_selector.rs +440 -0
  119. package/src/rust/components/main_panel/msg.rs +2 -2
  120. package/src/rust/components/main_panel/reconcile.rs +4 -4
  121. package/src/rust/components/mod.rs +2 -0
  122. package/src/rust/components/plugin_tab.rs +4 -28
  123. package/src/rust/components/settings_panel.rs +105 -22
  124. package/src/rust/components/viewer/msg.rs +46 -7
  125. package/src/rust/components/viewer/panels.rs +17 -20
  126. package/src/rust/components/viewer/render.rs +10 -3
  127. package/src/rust/components/viewer/settings.rs +163 -23
  128. package/src/rust/components/viewer/snapshots.rs +46 -21
  129. package/src/rust/components/viewer/wiring.rs +14 -25
  130. package/src/rust/components/viewer.rs +12 -8
  131. package/src/rust/components/window_editor.rs +75 -103
  132. package/src/rust/config/column_config_schema.rs +100 -41
  133. package/src/rust/config/css_scale.rs +882 -0
  134. package/src/rust/config/datetime_column_style/custom.rs +17 -0
  135. package/src/rust/config/datetime_column_style/simple.rs +10 -0
  136. package/src/rust/config/mod.rs +2 -0
  137. package/src/rust/config/number_string_format.rs +33 -0
  138. package/src/rust/config/options.rs +28 -0
  139. package/src/rust/config/plugin_static_config.rs +120 -5
  140. package/src/rust/config/viewer_config.rs +216 -3
  141. package/src/rust/config/workspace_config.rs +138 -10
  142. package/src/rust/custom_elements/viewer.rs +369 -119
  143. package/src/rust/custom_events.rs +43 -1
  144. package/src/rust/js/regular_layout.rs +3 -3
  145. package/src/rust/lib.rs +15 -0
  146. package/src/rust/presentation/column_locator.rs +19 -5
  147. package/src/rust/presentation.rs +200 -36
  148. package/src/rust/queries/column_locator.rs +74 -44
  149. package/src/rust/queries/get_viewer_config.rs +2 -1
  150. package/src/rust/queries/mod.rs +2 -0
  151. package/src/rust/queries/palette_set.rs +82 -0
  152. package/src/rust/queries/plugin_column_styles.rs +6 -2
  153. package/src/rust/renderer/dispatch.rs +16 -4
  154. package/src/rust/renderer/plugin_config.rs +309 -47
  155. package/src/rust/renderer.rs +15 -42
  156. package/src/rust/session/column_defaults_update.rs +32 -7
  157. package/src/rust/session/metadata.rs +23 -21
  158. package/src/rust/session/props.rs +1 -0
  159. package/src/rust/session/replace_expression_update.rs +1 -0
  160. package/src/rust/session.rs +9 -6
  161. package/src/rust/tasks/create_panel.rs +46 -49
  162. package/src/rust/tasks/edit_expression.rs +19 -23
  163. package/src/rust/tasks/edit_window.rs +9 -11
  164. package/src/rust/tasks/eject.rs +4 -1
  165. package/src/rust/tasks/pipeline.rs +2 -2
  166. package/src/rust/tasks/presize_panels.rs +44 -5
  167. package/src/rust/tasks/reset_all.rs +4 -4
  168. package/src/rust/tasks/restore_and_render.rs +17 -44
  169. package/src/rust/tasks/restore_panel.rs +37 -22
  170. package/src/rust/tasks/set_edit_mode.rs +3 -1
  171. package/src/rust/tasks/sync_update_panels.rs +18 -24
  172. package/src/rust/tasks/update_theme.rs +23 -18
  173. package/src/rust/utils/css_vars.rs +26 -0
  174. package/src/rust/utils/mod.rs +2 -0
  175. package/src/rust/utils/spawn.rs +1 -1
  176. package/src/rust/workspace.rs +454 -199
  177. package/src/svg/lock.svg +7 -0
  178. package/src/themes/botanical.css +10 -0
  179. package/src/themes/defaults.css +12 -0
  180. package/src/themes/dracula.css +17 -0
  181. package/src/themes/gruvbox-dark.css +20 -0
  182. package/src/themes/gruvbox.css +10 -0
  183. package/src/themes/icons.css +1 -0
  184. package/src/themes/intl/de.css +16 -0
  185. package/src/themes/intl/es.css +16 -0
  186. package/src/themes/intl/fr.css +16 -0
  187. package/src/themes/intl/ja.css +16 -0
  188. package/src/themes/intl/pt.css +16 -0
  189. package/src/themes/intl/zh.css +16 -0
  190. package/src/themes/intl.css +16 -0
  191. package/src/themes/monokai.css +22 -0
  192. package/src/themes/phosphor.css +5 -0
  193. package/src/themes/pro-dark.css +10 -0
  194. package/src/themes/solarized-dark.css +7 -0
  195. package/src/themes/solarized.css +7 -0
  196. package/src/themes/vaporwave.css +20 -0
  197. package/src/ts/column-format.ts +67 -46
  198. package/src/ts/perspective-viewer.ts +3 -0
  199. package/src/ts/plugin.ts +14 -0
  200. package/src/ts/providers.ts +96 -0
  201. package/src/ts/ts-rs/CompactDisplay.ts +3 -0
  202. package/src/ts/ts-rs/CurrencyCode.ts +3 -0
  203. package/src/ts/ts-rs/CurrencyDisplay.ts +3 -0
  204. package/src/ts/ts-rs/CurrencyNumberFormatStyle.ts +6 -0
  205. package/src/ts/ts-rs/CurrencySign.ts +3 -0
  206. package/src/ts/ts-rs/CustomDatetimeFormat.ts +3 -0
  207. package/src/ts/ts-rs/CustomDatetimeStyleConfig.ts +54 -0
  208. package/src/ts/ts-rs/CustomNumberFormatConfig.ts +25 -0
  209. package/src/ts/ts-rs/DatetimeColorMode.ts +3 -0
  210. package/src/ts/ts-rs/DatetimeFormatType.ts +8 -0
  211. package/src/ts/ts-rs/FormatMode.ts +3 -0
  212. package/src/ts/ts-rs/FormatUnit.ts +3 -0
  213. package/src/ts/ts-rs/Notation.ts +11 -0
  214. package/src/ts/ts-rs/NumberFormatStyle.ts +12 -0
  215. package/src/ts/ts-rs/PanelViewerConfig.ts +48 -2
  216. package/src/ts/ts-rs/PluginStaticConfig.ts +36 -1
  217. package/src/ts/ts-rs/RestoreOptions.ts +20 -0
  218. package/src/ts/ts-rs/RoundingMode.ts +3 -0
  219. package/src/ts/ts-rs/RoundingPriority.ts +3 -0
  220. package/src/ts/ts-rs/SaveWorkspaceOptions.ts +11 -0
  221. package/src/ts/ts-rs/SignDisplay.ts +3 -0
  222. package/src/ts/ts-rs/SimpleDatetimeFormat.ts +3 -0
  223. package/src/ts/ts-rs/SimpleDatetimeStyleConfig.ts +25 -0
  224. package/src/ts/ts-rs/SplitRollupMode.ts +11 -0
  225. package/src/ts/ts-rs/StringColorMode.ts +3 -0
  226. package/src/ts/ts-rs/TrailingZeroDisplay.ts +3 -0
  227. package/src/ts/ts-rs/Unit.ts +3 -0
  228. package/src/ts/ts-rs/UnitDisplay.ts +3 -0
  229. package/src/ts/ts-rs/UnitNumberFormatStyle.ts +5 -0
  230. package/src/ts/ts-rs/UseGrouping.ts +3 -0
  231. package/src/ts/ts-rs/ViewerConfig.ts +48 -2
  232. package/src/ts/ts-rs/ViewerConfigInitial.ts +164 -0
  233. package/src/ts/ts-rs/ViewerConfigUpdate.ts +72 -2
  234. package/src/ts/ts-rs/WindowSpec.ts +3 -2
  235. package/src/ts/ts-rs/WorkspaceConfig.ts +8 -2
  236. package/src/ts/ts-rs/WorkspaceConfigUpdate.ts +18 -9
  237. package/dist/esm/ts-rs/WindowAggregate.d.ts +0 -1
  238. package/src/rust/components/form/color_range_selector.rs +0 -137
  239. package/src/ts/ts-rs/WindowAggregate.ts +0 -3
  240. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline0.js +0 -0
  241. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline1.js +0 -0
  242. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline2.js +0 -0
  243. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline3.js +0 -0
  244. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline4.js +0 -0
  245. /package/dist/wasm/snippets/{perspective-viewer-8a8c7d5d696b43ae → perspective-viewer-39c3c3b28e3cbd48}/inline5.js +0 -0
@@ -21,19 +21,21 @@ use super::plugin_selector::PluginSelector;
21
21
  use super::plugin_tab::PluginTab;
22
22
  use crate::components::containers::sidebar_close_button::SidebarCloseButton;
23
23
  use crate::components::form::debug::DebugPanel;
24
- use crate::config::PluginUpdate;
25
- use crate::presentation::{ColumnLocator, OpenColumnSettings, Presentation};
24
+ use crate::config::{PluginStaticConfig, PluginUpdate};
25
+ use crate::presentation::{ColumnLocator, ColumnSettingsTarget, OpenColumnSettings, Presentation};
26
+ use crate::queries::classify_column;
26
27
  use crate::renderer::*;
27
28
  use crate::session::column_defaults_update::*;
28
29
  use crate::session::*;
29
30
  use crate::tasks::update_plugin_and_render;
30
31
  use crate::utils::*;
32
+ use crate::workspace::Workspace;
31
33
 
32
34
  #[derive(Clone, Properties)]
33
35
  pub struct SettingsPanelProps {
34
36
  pub on_close: Callback<()>,
35
37
  pub on_resize: Rc<PubSub<()>>,
36
- pub on_select_column: Callback<Option<ColumnLocator>>,
38
+ pub on_select_column: Callback<Option<ColumnSettingsTarget>>,
37
39
  pub on_debug: Callback<()>,
38
40
  pub is_debug: bool,
39
41
 
@@ -42,6 +44,15 @@ pub struct SettingsPanelProps {
42
44
  pub available_plugins: PtrEqRc<Vec<String>>,
43
45
  pub has_table: Option<TableLoadState>,
44
46
  pub named_column_count: usize,
47
+
48
+ /// The ACTIVE plugin's declared contract, threaded as a value prop so
49
+ /// that switching plugins re-renders the panes that read it. The
50
+ /// renderer handle cannot serve this: it is excluded from prop
51
+ /// equality (it is a handle, not a value), so a plugin swap that
52
+ /// leaves the view config untouched — Y Line back to Datagrid, both
53
+ /// of which name one column slot — would otherwise change nothing any
54
+ /// component compares.
55
+ pub plugin_static_config: Rc<PluginStaticConfig>,
45
56
  pub view_config: PtrEqRc<ViewConfig>,
46
57
 
47
58
  /// Snapshot of the active plugin's `plugin_config` bucket, threaded
@@ -90,6 +101,7 @@ pub struct SettingsPanelProps {
90
101
  pub session: Session,
91
102
  pub renderer: Renderer,
92
103
  pub presentation: Presentation,
104
+ pub workspace: Workspace,
93
105
  }
94
106
 
95
107
  impl PartialEq for SettingsPanelProps {
@@ -99,6 +111,7 @@ impl PartialEq for SettingsPanelProps {
99
111
  && self.available_plugins == rhs.available_plugins
100
112
  && self.has_table == rhs.has_table
101
113
  && self.named_column_count == rhs.named_column_count
114
+ && self.plugin_static_config == rhs.plugin_static_config
102
115
  && self.view_config == rhs.view_config
103
116
  && self.plugin_config == rhs.plugin_config
104
117
  && self.drag_column == rhs.drag_column
@@ -116,6 +129,11 @@ pub enum SelectedTab {
116
129
  Query,
117
130
  Plugin,
118
131
  Debug,
132
+
133
+ /// The embedded LLM agent's chat panel. The variant exists in every
134
+ /// build; its tab button and body render only under the `llm-agent`
135
+ /// feature, and only once `agentConfig()` has been called.
136
+ Chat,
119
137
  }
120
138
 
121
139
  #[function_component]
@@ -128,25 +146,28 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
128
146
  } = &props;
129
147
 
130
148
  let selected_column = {
131
- let locator = props.open_column_settings.locator.clone();
132
149
  let config = &props.view_config;
133
- locator.filter(|locator| match locator {
134
- ColumnLocator::Table(_name) => {
135
- locator
136
- .name()
137
- .map(|n| {
138
- config.columns.iter().any(|maybe_col| {
139
- maybe_col.as_ref().map(|col| col == n).unwrap_or_default()
140
- }) || config.group_by.iter().any(|col| col == n)
141
- || config.split_by.iter().any(|col| col == n)
142
- || config.filter.iter().any(|col| col.column() == n)
143
- || config.sort.iter().any(|col| &col.0 == n)
144
- })
145
- .unwrap_or_default()
146
- && props.renderer.can_render_column_styles()
147
- },
148
- _ => true,
149
- })
150
+ props
151
+ .open_column_settings
152
+ .target
153
+ .as_ref()
154
+ .and_then(|target| match target {
155
+ ColumnSettingsTarget::NewExpression => Some(ColumnLocator::NewExpression),
156
+ ColumnSettingsTarget::Column(n) => {
157
+ let locator = classify_column(n, config, &props.metadata)?;
158
+ if !matches!(locator, ColumnLocator::Table(_)) {
159
+ return Some(locator);
160
+ }
161
+
162
+ let used = config.columns.iter().any(|maybe_col| {
163
+ maybe_col.as_ref().map(|col| col == n).unwrap_or_default()
164
+ }) || config.group_by.iter().any(|col| col == n)
165
+ || config.split_by.iter().any(|col| col == n)
166
+ || config.filter.iter().any(|col| col.column() == n)
167
+ || config.sort.iter().any(|col| &col.0 == n);
168
+ (used && props.renderer.can_render_column_styles()).then_some(locator)
169
+ },
170
+ })
150
171
  };
151
172
 
152
173
  let plugin_name = props.plugin_name.clone();
@@ -186,13 +207,21 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
186
207
  .unwrap();
187
208
 
188
209
  let group_rollups = plugin_config.get_group_rollups(&rollup_features);
210
+ let split_rollup_features = session_metadata
211
+ .get_features()
212
+ .map(|x| x.get_split_rollup_modes())
213
+ .unwrap();
214
+
215
+ let split_rollups = plugin_config.get_split_rollups(&split_rollup_features);
189
216
  let mut update = ViewConfigUpdate {
190
217
  group_rollup_mode: group_rollups.first().cloned(),
218
+ split_rollup_mode: split_rollups.first().cloned(),
191
219
  ..ViewConfigUpdate::default()
192
220
  };
193
221
 
194
222
  update.set_update_column_defaults(
195
223
  &session_metadata,
224
+ &view_config,
196
225
  &view_config.columns,
197
226
  plugin_config,
198
227
  );
@@ -237,6 +266,52 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
237
266
  }
238
267
  };
239
268
 
269
+ // The chat tab is zero-affordance until `agentConfig()` is called; the
270
+ // subscription re-renders this panel when that happens (and as the
271
+ // transcript updates while the chat body is mounted).
272
+ #[cfg(feature = "llm-agent")]
273
+ let (chat_tab_button, chat_body) = {
274
+ let update = use_force_update();
275
+ let agent = presentation.agent.clone();
276
+ use_effect_with((), move |_| {
277
+ let sub = agent
278
+ .on_update
279
+ .add_notify_listener(&Callback::from(move |_| update.force_update()));
280
+
281
+ move || drop(sub)
282
+ });
283
+
284
+ let button = if presentation.agent.is_configured() {
285
+ let on_select_column = props.on_select_column.clone();
286
+ let set_chat = {
287
+ let on_select_tab = props.on_select_tab.clone();
288
+ Callback::from(move |_: PointerEvent| {
289
+ on_select_tab.emit(SelectedTab::Chat);
290
+ on_select_column.emit(None)
291
+ })
292
+ };
293
+
294
+ html! {
295
+ <div
296
+ id="chat_tabbar_tab"
297
+ class={tab_class(selected, SelectedTab::Chat)}
298
+ onpointerdown={set_chat}
299
+ />
300
+ }
301
+ } else {
302
+ html! {}
303
+ };
304
+
305
+ let body = html! {
306
+ <crate::components::chat_panel::ChatPanel agent={presentation.agent.clone()} />
307
+ };
308
+
309
+ (button, body)
310
+ };
311
+
312
+ #[cfg(not(feature = "llm-agent"))]
313
+ let (chat_tab_button, chat_body) = (html! {}, html! {});
314
+
240
315
  let on_open_expr_panel = use_callback(props.on_select_column.clone(), |c, on_select| {
241
316
  on_select.emit(Some(c))
242
317
  });
@@ -270,6 +345,7 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
270
345
  class={tab_class(selected, SelectedTab::Debug)}
271
346
  onpointerdown={set_debug}
272
347
  />
348
+ { chat_tab_button }
273
349
  </div>
274
350
  if selected == SelectedTab::Query {
275
351
  <ColumnSelector
@@ -278,6 +354,7 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
278
354
  {selected_column}
279
355
  has_table={props.has_table.clone()}
280
356
  named_column_count={props.named_column_count}
357
+ plugin_static_config={props.plugin_static_config.clone()}
281
358
  view_config={props.view_config.clone()}
282
359
  drag_column={props.drag_column.clone()}
283
360
  metadata={props.metadata.clone()}
@@ -298,11 +375,14 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
298
375
  // initial_width={width}
299
376
  // on_auto_width={on_auto_width.clone()}
300
377
  />
378
+ } else if selected == SelectedTab::Chat {
379
+ { chat_body }
301
380
  } else {
302
381
  <DebugPanel
303
382
  {presentation}
304
383
  {renderer}
305
384
  {session}
385
+ workspace={props.workspace.clone()}
306
386
  initial_width={width}
307
387
  on_auto_width={on_auto_width.clone()}
308
388
  />
@@ -310,7 +390,10 @@ pub fn SettingsPanel(props: &SettingsPanelProps) -> Html {
310
390
  // Sibling sizer keeps the panel width pinned across tab
311
391
  // switches; lives outside the tab-body so it survives the
312
392
  // tab subtree's unmount.
313
- <div class="scroll-panel-auto-width" style={format!("width:{}px", width)} />
393
+ <div
394
+ class="scroll-panel-auto-width"
395
+ style={format!("width:{}px", width)}
396
+ />
314
397
  </div>
315
398
  }
316
399
  }
@@ -22,7 +22,9 @@ use wasm_bindgen::JsValue;
22
22
 
23
23
  use crate::components::settings_panel::SelectedTab;
24
24
  use crate::config::*;
25
- use crate::presentation::{ColumnLocator, ColumnSettingsTab, DragDropProps, PresentationProps};
25
+ use crate::presentation::{
26
+ ColumnSettingsTab, ColumnSettingsTarget, DragDropProps, PresentationProps,
27
+ };
26
28
  use crate::renderer::RendererProps;
27
29
  use crate::session::{SessionProps, TableLoadState, ViewStats};
28
30
  use crate::utils::Completion;
@@ -48,17 +50,19 @@ pub enum PerspectiveViewerMsg {
48
50
  ColumnSettingsPanelSizeUpdate(Option<i32>),
49
51
  ColumnSettingsPanelAutoWidth(f64),
50
52
  ToggleColumnSettingsPin,
53
+ ToggleColumnSettingsPinComplete(Sender<()>),
51
54
  ColumnSettingsTabChanged(ColumnSettingsTab),
52
55
  OpenColumnSettings {
53
- locator: Option<ColumnLocator>,
56
+ target: Option<ColumnSettingsTarget>,
54
57
  sender: Option<Sender<()>>,
55
58
  toggle: bool,
56
59
  },
57
60
  PreloadFontsUpdate,
58
61
 
59
62
  /// Element-level reset (the public `reset()` API): reset EVERY panel and
60
- /// clear the cross-filter overlay, symmetric with whole-element
61
- /// `save`/`restore`. The `bool` also clears expressions/column settings.
63
+ /// clear the cross-filter overlay, symmetric with
64
+ /// `saveWorkspace`/`restoreWorkspace`. The `bool` also clears
65
+ /// expressions/column settings.
62
66
  Reset(bool, Option<Completion>),
63
67
 
64
68
  /// Reset ONLY the named panel — or the active panel when `None` — to its
@@ -86,10 +90,10 @@ pub enum PerspectiveViewerMsg {
86
90
  /// I6) — carrying any teardown error, which was previously dropped.
87
91
  ClosePanel(String, Option<Completion>),
88
92
 
89
- /// Whole-element `restore` finished replacing the panel set in the
93
+ /// `restoreWorkspace` finished replacing the panel set in the
90
94
  /// `Workspace` (new models inserted, old panels ejected, layout staged):
91
95
  /// activate the named panel, re-subscribe the per-panel wiring, and
92
- /// re-render — the SINGLE visible commit of the whole restore.
96
+ /// re-render — the SINGLE visible commit of the restore.
93
97
  CommitWorkspaceRestore(String),
94
98
 
95
99
  /// Duplicate the named panel: snapshot its config into a new independent
@@ -149,8 +153,34 @@ pub enum PerspectiveViewerMsg {
149
153
  SettingsPanelTabChanged(SelectedTab),
150
154
  SettingsPanelAutoWidth(f64),
151
155
  ToggleDebug,
156
+
157
+ /// The toggle choreography's INTERNAL completion leaf: flip the pane
158
+ /// and resolve on the render commit. Never send this to toggle
159
+ /// settings from outside `settings.rs` — it skips the presize/resize
160
+ /// sweep (the pane's `SplitPanel` emits no `before-resize` and the
161
+ /// host box is unchanged, so nothing else resizes the plugins), which
162
+ /// leaves canvas plugins CSS-stretched at their old backing size. API
163
+ /// entry points send [`Self::ToggleSettingsInit`].
152
164
  ToggleSettingsComplete(SettingsUpdate, Sender<()>),
153
- ToggleSettingsInit(Option<SettingsUpdate>, Option<Sender<ApiResult<JsValue>>>),
165
+
166
+ /// Toggle (or force) the settings pane with the FULL choreography:
167
+ /// presize every visible plugin to its post-toggle box, commit the
168
+ /// pane, then the exactness-finalizer resize. The `Sender` resolves
169
+ /// after the sweep. The one settings-toggle entry point for both the
170
+ /// toolbar and the element API (`toggleConfig`, `restore({settings})`,
171
+ /// `restoreWorkspace`).
172
+ ///
173
+ /// The `bool` is `announce`: `true` when this toggle is the SOLE
174
+ /// carrier of the config change (a user gesture — toolbar,
175
+ /// `toggleConfig`), which emits `toggle-settings` + one
176
+ /// `perspective-config-update`; `false` for the `restore` family,
177
+ /// whose own view-config commit dispatch announces the settings field
178
+ /// — one API call, one config-update.
179
+ ToggleSettingsInit(
180
+ Option<SettingsUpdate>,
181
+ bool,
182
+ Option<Sender<ApiResult<JsValue>>>,
183
+ ),
154
184
  UpdateSession(Box<SessionProps>),
155
185
  UpdateRenderer(Box<RendererProps>),
156
186
  UpdatePresentation(Box<PresentationProps>),
@@ -161,7 +191,16 @@ pub enum PerspectiveViewerMsg {
161
191
  UpdateIsWorkspace(bool),
162
192
 
163
193
  /// Update only `open_column_settings` in the presentation snapshot.
194
+ /// Handled in `settings.rs` (not `snapshots.rs`): a docked-drawer
195
+ /// mount/unmount defers the snapshot behind a presize sweep.
164
196
  UpdateColumnSettings(Box<crate::presentation::OpenColumnSettings>),
197
+
198
+ /// Every visible panel has rendered at its post-transition box — NOW
199
+ /// apply the newest deferred `open_column_settings` target (the
200
+ /// latest-wins slot, not a copy captured at sweep spawn); the `Sender`
201
+ /// resolves on the render commit so the staged presents reveal in the
202
+ /// same paint (mirrors `ToggleSettingsComplete`).
203
+ UpdateColumnSettingsCommit(Sender<()>),
165
204
  UpdateDragDrop(Box<DragDropProps>),
166
205
 
167
206
  /// Update only stats-related fields of `session_props` without touching
@@ -27,7 +27,7 @@ use super::wiring::{
27
27
  clear_active_callbacks, create_active_subscriptions, inject_active_callbacks,
28
28
  subscribe_panel_titles,
29
29
  };
30
- use crate::config::{TableUpdate, ViewerConfigUpdate};
30
+ use crate::config::ViewerConfigInitial;
31
31
  use crate::queries::*;
32
32
  use crate::renderer::Renderer;
33
33
  use crate::session::*;
@@ -124,7 +124,7 @@ impl PerspectiveViewer {
124
124
  }
125
125
  }
126
126
 
127
- /// Whole-element `restore`'s single commit: the `Workspace` already holds
127
+ /// `restoreWorkspace`'s single commit: the `Workspace` already holds
128
128
  /// the final panel set (models inserted, olds ejected) and the staged
129
129
  /// layout. Re-subscribe the per-panel title wiring (the set changed
130
130
  /// wholesale), activate the restored panel, and re-render — `MainPanel`'s
@@ -193,7 +193,8 @@ impl PerspectiveViewer {
193
193
  })
194
194
  .await?;
195
195
 
196
- let update = ViewerConfigUpdate::decode(&config.encode()?)?;
196
+ // TODO(texodus): what the ****?
197
+ let update = ViewerConfigInitial::decode(&config.encode()?)?;
197
198
  let client = panel.session.get_client();
198
199
  let new_id = create_panel(
199
200
  &elem,
@@ -216,18 +217,18 @@ impl PerspectiveViewer {
216
217
 
217
218
  pub(super) fn on_new_panel(&mut self, ctx: &Context<Self>, id: String) -> bool {
218
219
  if let Some(panel) = ctx.props().workspace.panel(&PanelId::from(id)) {
219
- let table_name = panel.session.get_table().map(|t| t.get_name().to_owned());
220
+ let Some(table_name) = panel.session.get_table().map(|t| t.get_name().to_owned())
221
+ else {
222
+ tracing::warn!("Source panel has no `Table` to create a new panel from");
223
+ return false;
224
+ };
225
+
220
226
  let elem = ctx.props().elem.clone();
221
227
  let presentation = ctx.props().presentation.clone();
222
228
  let workspace = ctx.props().workspace.clone();
223
229
  let notify = ctx.link().callback(|_: ()| LayoutChanged);
224
230
  let activate = ctx.link().callback(|id| SetActivePanel(id, None));
225
231
  ApiFuture::spawn(async move {
226
- let update = ViewerConfigUpdate {
227
- table: table_name.map(TableUpdate::Update).unwrap_or_default(),
228
- ..Default::default()
229
- };
230
-
231
232
  let client = panel.session.get_client();
232
233
  let new_id = create_panel(
233
234
  &elem,
@@ -235,7 +236,7 @@ impl PerspectiveViewer {
235
236
  &workspace,
236
237
  &notify,
237
238
  None,
238
- update,
239
+ ViewerConfigInitial::new(table_name),
239
240
  client,
240
241
  )
241
242
  .await?;
@@ -273,18 +274,13 @@ impl PerspectiveViewer {
273
274
  let notify = ctx.link().callback(|_: ()| LayoutChanged);
274
275
  let activate = ctx.link().callback(|id| SetActivePanel(id, None));
275
276
  ApiFuture::spawn(async move {
276
- let update = ViewerConfigUpdate {
277
- table: TableUpdate::Update(table),
278
- ..Default::default()
279
- };
280
-
281
277
  let new_id = create_panel(
282
278
  &elem,
283
279
  &presentation,
284
280
  &workspace,
285
281
  &notify,
286
282
  None,
287
- update,
283
+ ViewerConfigInitial::new(table),
288
284
  Some(client),
289
285
  )
290
286
  .await?;
@@ -313,11 +309,12 @@ impl PerspectiveViewer {
313
309
  let presentation = ctx.props().presentation.clone();
314
310
  let workspace = ctx.props().workspace.clone();
315
311
  ApiFuture::spawn(async move {
316
- let default_theme = presentation.get_default_theme_name().await;
317
- if let Some(renderer) = workspace.active_renderer()
318
- && let Some(theme) = renderer.theme().or(default_theme)
319
- {
312
+ // Only an EXPLICITLY themed panel mirrors onto the host; an
313
+ // unpinned one inherits whatever the host already shows, so
314
+ // activating it must not restate (and thereby pin) that value.
315
+ if let Some(theme) = workspace.active_renderer().and_then(|x| x.theme()) {
320
316
  presentation.set_theme_name(Some(&theme)).await?;
317
+ presentation.publish_theme_config().await?;
321
318
  }
322
319
 
323
320
  Ok(())
@@ -53,7 +53,7 @@ impl PerspectiveViewer {
53
53
  }
54
54
 
55
55
  let on_open_expr_panel = ctx.link().callback(|c| OpenColumnSettings {
56
- locator: c,
56
+ target: c,
57
57
  sender: None,
58
58
  toggle: true,
59
59
  });
@@ -62,7 +62,9 @@ impl PerspectiveViewer {
62
62
  .link()
63
63
  .callback(|(x, _)| ColumnSettingsPanelSizeUpdate(Some(x)));
64
64
 
65
- let on_close_settings = ctx.link().callback(|()| ToggleSettingsInit(None, None));
65
+ let on_close_settings = ctx
66
+ .link()
67
+ .callback(|()| ToggleSettingsInit(None, true, None));
66
68
  let on_debug = ctx.link().callback(|_| ToggleDebug);
67
69
  let selected_column = get_current_column_locator(
68
70
  &self.presentation_props.open_column_settings,
@@ -94,6 +96,7 @@ impl PerspectiveViewer {
94
96
  {available_plugins}
95
97
  {has_table}
96
98
  {named_column_count}
99
+ plugin_static_config={self.renderer_props.config.clone()}
97
100
  {view_config}
98
101
  plugin_config={self.renderer_props.plugin_config.clone()}
99
102
  {drag_column}
@@ -108,13 +111,16 @@ impl PerspectiveViewer {
108
111
  {presentation}
109
112
  {renderer}
110
113
  {session}
114
+ workspace={ctx.props().workspace.clone()}
111
115
  />
112
116
  }
113
117
  } else {
114
118
  html! { <></> }
115
119
  };
116
120
 
117
- let on_settings = ctx.link().callback(|()| ToggleSettingsInit(None, None));
121
+ let on_settings = ctx
122
+ .link()
123
+ .callback(|()| ToggleSettingsInit(None, true, None));
118
124
  let on_select_tab = ctx.link().callback(ColumnSettingsTabChanged);
119
125
  // Pinning the drawer is a pure CSS positioning flip on `#modal_panel`
120
126
  // (absolute overlay over the main panel <-> static flex sibling
@@ -151,6 +157,7 @@ impl PerspectiveViewer {
151
157
  {presentation}
152
158
  {renderer}
153
159
  {session}
160
+ workspace={ctx.props().workspace.clone()}
154
161
  />
155
162
  <></>
156
163
  </SplitPanel>