flexdesk 0.17.13 → 0.17.14

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 (243) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/LICENSE +0 -0
  3. package/README.md +0 -0
  4. package/THIRD_PARTY_LICENSES.md +0 -0
  5. package/canvas.js +0 -0
  6. package/charts.js +0 -0
  7. package/core.js +0 -0
  8. package/css/base.css +45 -3
  9. package/css/flexdesk.css +60 -3
  10. package/css/overrides.css +15 -0
  11. package/css/reset.css +0 -0
  12. package/dist/canvas.js +0 -0
  13. package/dist/canvas.js.map +0 -0
  14. package/dist/chunk-3PHCPZHT.js +0 -0
  15. package/dist/chunk-3PHCPZHT.js.map +0 -0
  16. package/dist/chunk-FOOS3T5L.js +0 -0
  17. package/dist/chunk-FOOS3T5L.js.map +0 -0
  18. package/dist/chunk-GOQLR5WR.js +0 -0
  19. package/dist/chunk-GOQLR5WR.js.map +0 -0
  20. package/dist/chunk-HNU6MM2B.js +0 -0
  21. package/dist/chunk-HNU6MM2B.js.map +0 -0
  22. package/dist/chunk-JKZLNKG6.js +0 -0
  23. package/dist/chunk-JKZLNKG6.js.map +0 -0
  24. package/dist/chunk-JYWURG5T.js +0 -0
  25. package/dist/chunk-JYWURG5T.js.map +0 -0
  26. package/dist/chunk-N7HC3ADV.js +0 -0
  27. package/dist/chunk-N7HC3ADV.js.map +0 -0
  28. package/dist/core.js +3 -18
  29. package/dist/core.js.map +2 -2
  30. package/dist/editor.js.map +0 -0
  31. package/dist/flexdesk.css +60 -3
  32. package/dist/flow.js.map +0 -0
  33. package/dist/host.js +0 -0
  34. package/dist/host.js.map +0 -0
  35. package/dist/reset.css +0 -0
  36. package/dist/tile_registry-6WZPTUZV.js +0 -0
  37. package/dist/tile_registry-6WZPTUZV.js.map +0 -0
  38. package/dist/widgets.js +27 -18
  39. package/dist/widgets.js.map +2 -2
  40. package/dist/wm.js +195 -28
  41. package/dist/wm.js.map +3 -3
  42. package/docs/README.md +1 -1
  43. package/docs/best-practices.md +1 -1
  44. package/docs/components/command-palette.md +0 -0
  45. package/docs/components/conversation.md +0 -0
  46. package/docs/components/data-table-grid.md +0 -0
  47. package/docs/components/data-table.md +0 -0
  48. package/docs/components/editor.md +0 -0
  49. package/docs/components/flow-canvas-editor.md +0 -0
  50. package/docs/components/flow-editors.md +0 -0
  51. package/docs/components/flow-lane-editor.md +0 -0
  52. package/docs/components/flow-outline-editor.md +0 -0
  53. package/docs/components/forms-and-dialogs.md +0 -0
  54. package/docs/components/input-fields.md +0 -0
  55. package/docs/components/keymap.md +66 -3
  56. package/docs/components/layout-widgets.md +32 -0
  57. package/docs/components/markdown-and-outline.md +0 -0
  58. package/docs/components/menus.md +9 -5
  59. package/docs/components/notifications.md +0 -0
  60. package/docs/components/panels-and-desktops.md +56 -0
  61. package/docs/components/record-list-and-fact-table.md +0 -0
  62. package/docs/components/settings-view.md +0 -0
  63. package/docs/components/shell.md +0 -0
  64. package/docs/components/slide-out-panel.md +0 -0
  65. package/docs/components/wizard.md +0 -0
  66. package/docs/concepts/building-and-packaging.md +0 -0
  67. package/docs/concepts/domain-neutrality.md +0 -0
  68. package/docs/concepts/host-port.md +0 -0
  69. package/docs/concepts/packages-and-entry-points.md +0 -0
  70. package/docs/concepts/peer-dependencies.md +0 -0
  71. package/docs/concepts/state-guard.md +65 -2
  72. package/docs/concepts/styling.md +24 -6
  73. package/docs/quickstart.md +0 -0
  74. package/editor.js +0 -0
  75. package/flow.js +0 -0
  76. package/host.js +0 -0
  77. package/package.json +2 -2
  78. package/src/canvas/canvas_adapter.js +0 -0
  79. package/src/canvas/edge_router.js +0 -0
  80. package/src/canvas/graph/connector_router.js +0 -0
  81. package/src/canvas/graph/history_service.js +0 -0
  82. package/src/canvas/graph/node_platform.js +0 -0
  83. package/src/canvas/index.js +0 -0
  84. package/src/canvas/layered_layout.js +0 -0
  85. package/src/charts/data_series_plot_window.js +0 -0
  86. package/src/charts/downsample.js +0 -0
  87. package/src/charts/plot_config.js +0 -0
  88. package/src/charts/plotly_wrapper.js +0 -0
  89. package/src/core/event_bus.js +0 -0
  90. package/src/core/logging.js +0 -0
  91. package/src/core/settings.js +0 -0
  92. package/src/core/state_guard.js +0 -0
  93. package/src/core/state_guard_config.js +19 -18
  94. package/src/core/state_machine.js +0 -0
  95. package/src/editor/editor_pane.js +0 -0
  96. package/src/editor/monaco_editor_factory.js +0 -0
  97. package/src/editor/monaco_loader.js +0 -0
  98. package/src/editor/notebook_search.js +0 -0
  99. package/src/editor/notebook_undo_manager.js +0 -0
  100. package/src/editor/split_pane_container.js +0 -0
  101. package/src/flow/canvas_editor/connections.js +0 -0
  102. package/src/flow/canvas_editor/editor.js +0 -0
  103. package/src/flow/canvas_editor/index.js +0 -0
  104. package/src/flow/canvas_editor/layout.js +0 -0
  105. package/src/flow/canvas_editor/strings.js +0 -0
  106. package/src/flow/kit/always_before.js +0 -0
  107. package/src/flow/kit/catalogue.js +0 -0
  108. package/src/flow/kit/chip_input.js +0 -0
  109. package/src/flow/kit/dom.js +0 -0
  110. package/src/flow/kit/findings.js +0 -0
  111. package/src/flow/kit/history.js +0 -0
  112. package/src/flow/kit/index.js +0 -0
  113. package/src/flow/kit/keys.js +0 -0
  114. package/src/flow/kit/logic_graph.js +0 -0
  115. package/src/flow/kit/references.js +0 -0
  116. package/src/flow/kit/settings_panel.js +0 -0
  117. package/src/flow/kit/settings_schema.js +0 -0
  118. package/src/flow/kit/step_picker.js +0 -0
  119. package/src/flow/kit/strings.js +0 -0
  120. package/src/flow/kit/value_picker.js +0 -0
  121. package/src/flow/kit/widgets.js +0 -0
  122. package/src/flow/lanes/editor.js +0 -0
  123. package/src/flow/lanes/index.js +0 -0
  124. package/src/flow/lanes/layout.js +0 -0
  125. package/src/flow/lanes/pipeline.js +0 -0
  126. package/src/flow/lanes/preview.js +0 -0
  127. package/src/flow/lanes/strings.js +0 -0
  128. package/src/flow/outline/editor.js +0 -0
  129. package/src/flow/outline/index.js +0 -0
  130. package/src/flow/outline/mapping.js +0 -0
  131. package/src/flow/outline/menu.js +0 -0
  132. package/src/flow/outline/operations.js +0 -0
  133. package/src/flow/outline/recognise.js +0 -0
  134. package/src/flow/outline/strings.js +0 -0
  135. package/src/help/help_modal.js +0 -0
  136. package/src/help/help_registry.js +0 -0
  137. package/src/host/external_links.js +0 -0
  138. package/src/host/file_picker.js +0 -0
  139. package/src/host/host.js +0 -0
  140. package/src/host/photino_host.js +0 -0
  141. package/src/host/photino_host.md +0 -0
  142. package/src/host/pywebview_host.js +0 -0
  143. package/src/tiles/chart_tiles.js +0 -0
  144. package/src/tiles/config_schema.js +0 -0
  145. package/src/tiles/figure_tile.js +0 -0
  146. package/src/tiles/layout_persistence.js +0 -0
  147. package/src/tiles/list_tiles.js +0 -0
  148. package/src/tiles/scope_picker.js +0 -0
  149. package/src/tiles/tile_config_form.js +0 -0
  150. package/src/tiles/tile_grid.js +0 -0
  151. package/src/tiles/tile_registry.js +0 -0
  152. package/src/tiling/bottom_panel_tabs.js +0 -0
  153. package/src/tiling/command_palette.js +0 -0
  154. package/src/tiling/content_registry.js +0 -0
  155. package/src/tiling/desktops.js +79 -16
  156. package/src/tiling/entity_sources.js +0 -0
  157. package/src/tiling/keymap.js +70 -2
  158. package/src/tiling/kind_taxonomy.js +0 -0
  159. package/src/tiling/landing_table.js +0 -0
  160. package/src/tiling/loading_overlay.js +0 -0
  161. package/src/tiling/nav_panel.js +0 -0
  162. package/src/tiling/page_factory.js +0 -0
  163. package/src/tiling/panel_availability.js +0 -0
  164. package/src/tiling/panel_keys.js +0 -0
  165. package/src/tiling/shell.js +9 -2
  166. package/src/tiling/tab_strip.js +0 -0
  167. package/src/tiling/tile_breadcrumb.js +0 -0
  168. package/src/tiling/tile_renderer.js +0 -0
  169. package/src/tiling/tile_tab_menu.js +0 -0
  170. package/src/tiling/tile_tree.js +63 -5
  171. package/src/tiling/wm.js +59 -6
  172. package/src/tiling/zoom.js +0 -0
  173. package/src/ui/base/component_base.js +0 -0
  174. package/src/ui/base/controller_base.js +0 -0
  175. package/src/ui/base/page_base.js +0 -0
  176. package/src/ui/components/about_dialog.js +0 -0
  177. package/src/ui/components/alert_log.js +0 -0
  178. package/src/ui/components/alerts_pane.js +0 -0
  179. package/src/ui/components/attribute_list_editor.js +0 -0
  180. package/src/ui/components/autocomplete_field.js +0 -0
  181. package/src/ui/components/cell_editor_host.js +0 -0
  182. package/src/ui/components/cell_editors.js +0 -0
  183. package/src/ui/components/cell_grid.js +0 -0
  184. package/src/ui/components/cell_paste.js +0 -0
  185. package/src/ui/components/cell_selection.js +0 -0
  186. package/src/ui/components/choice_controls.js +0 -0
  187. package/src/ui/components/color_field.js +0 -0
  188. package/src/ui/components/color_list_field.js +0 -0
  189. package/src/ui/components/column_fit.js +0 -0
  190. package/src/ui/components/column_groups.js +0 -0
  191. package/src/ui/components/column_order.js +0 -0
  192. package/src/ui/components/computing_status_window.js +0 -0
  193. package/src/ui/components/confirm_dialog.js +0 -0
  194. package/src/ui/components/conversation_view.js +0 -0
  195. package/src/ui/components/crosstab.js +0 -0
  196. package/src/ui/components/detail_header.js +0 -0
  197. package/src/ui/components/document_outline.js +0 -0
  198. package/src/ui/components/drag_reorder.js +0 -0
  199. package/src/ui/components/fact_table.js +0 -0
  200. package/src/ui/components/fill_handle.js +0 -0
  201. package/src/ui/components/gallery_picker.js +0 -0
  202. package/src/ui/components/header_actions.js +0 -0
  203. package/src/ui/components/inline_renamer.js +0 -0
  204. package/src/ui/components/job_progress.js +0 -0
  205. package/src/ui/components/menu_bar.js +0 -0
  206. package/src/ui/components/modal.js +0 -0
  207. package/src/ui/components/notification_history.js +0 -0
  208. package/src/ui/components/progress.js +0 -0
  209. package/src/ui/components/record_list.js +0 -0
  210. package/src/ui/components/row_activation.js +0 -0
  211. package/src/ui/components/section_stack.js +0 -0
  212. package/src/ui/components/series.js +0 -0
  213. package/src/ui/components/settings_view.js +0 -0
  214. package/src/ui/components/slide_out_panel.js +0 -0
  215. package/src/ui/components/slider_field.js +0 -0
  216. package/src/ui/components/sortable_list.js +0 -0
  217. package/src/ui/components/staged_edits.js +0 -0
  218. package/src/ui/components/staged_history.js +0 -0
  219. package/src/ui/components/table_filter_chips.js +0 -0
  220. package/src/ui/components/table_state_store.js +0 -0
  221. package/src/ui/components/table_structure.js +0 -0
  222. package/src/ui/components/table_values.js +0 -0
  223. package/src/ui/components/tabs.js +0 -0
  224. package/src/ui/components/tag_input.js +0 -0
  225. package/src/ui/components/toast.js +0 -0
  226. package/src/ui/components/toggle_field.js +0 -0
  227. package/src/ui/components/tree_view.js +41 -18
  228. package/src/ui/components/tsv.js +0 -0
  229. package/src/ui/components/window_taskbar.js +0 -0
  230. package/src/ui/components/wizard.js +0 -0
  231. package/src/ui/controllers/window_chrome_controller.js +0 -0
  232. package/src/ui/notification_center.js +0 -0
  233. package/src/ui/utils/auto_scrollbars.js +0 -0
  234. package/src/ui/utils/markdown.js +0 -0
  235. package/src/ui/utils/native_context_menu.js +0 -0
  236. package/src/ui/utils/overlay_scrollbar.js +0 -0
  237. package/src/ui/utils/raf_resize_observer.js +0 -0
  238. package/src/ui/utils/scroll_lock.js +0 -0
  239. package/src/ui/utils/strip_scroll.js +0 -0
  240. package/src/ui/utils/tooltip_service.js +0 -0
  241. package/tiles.js +0 -0
  242. package/widgets.js +0 -0
  243. package/wm.js +0 -0
package/dist/wm.js CHANGED
@@ -342,6 +342,17 @@ function _compose(mods, key) {
342
342
  const used = MODIFIER_ORDER.filter((m) => mods.has(m) && (m !== "Shift" || _shiftCounts(key)));
343
343
  return [...used, k].join("+");
344
344
  }
345
+ var TEXT_FIELDS = 'input, textarea, select, [contenteditable="true"]';
346
+ function _altTypes() {
347
+ const nav = globalThis.navigator;
348
+ return /^(mac|iphone|ipad|ipod)/i.test(nav?.userAgentData?.platform || nav?.platform || "");
349
+ }
350
+ function _digitOfEvent(e) {
351
+ const typesCharacter = e.getModifierState?.("AltGraph") || e.ctrlKey && e.altKey || _altTypes() && !!e.target?.closest?.(TEXT_FIELDS);
352
+ const byPosition = typesCharacter ? null : /^Digit([1-9])$/.exec(e.code ?? "");
353
+ if (byPosition) return byPosition[1];
354
+ return /^[1-9]$/.test(e.key) ? e.key : null;
355
+ }
345
356
  function _chordOfEvent(e) {
346
357
  const key = e.key;
347
358
  if (!key || key === "Control" || key === "Shift" || key === "Alt" || key === "Meta") return null;
@@ -363,7 +374,8 @@ function _chordOfEvent(e) {
363
374
  return null;
364
375
  }
365
376
  const sh = shift ? "Shift+" : "";
366
- if (/^[1-9]$/.test(key)) return `Alt+${sh}${key}`;
377
+ const digit = _digitOfEvent(e);
378
+ if (digit) return `Alt+${sh}${digit}`;
367
379
  if (lower === "h" || lower === "v") return `Alt+${sh}${lower.toUpperCase()}`;
368
380
  if (lower === "t" || lower === "n" || lower === "w" || lower === "f") return `Alt+${lower.toUpperCase()}`;
369
381
  if (ARROWS.includes(key)) return `Alt+${sh}${key}`;
@@ -450,7 +462,7 @@ function installKeymap({ wm, palette, navSelector = null, bindings = null, logge
450
462
  if (!chord) return;
451
463
  const b = table.get(chord);
452
464
  if (!b || b.disabled) return;
453
- if (!b.inField && e.target?.closest?.('input, textarea, select, [contenteditable="true"]')) return;
465
+ if (!b.inField && e.target?.closest?.(TEXT_FIELDS)) return;
454
466
  if (b.run(ctx, e) !== false) e.preventDefault();
455
467
  };
456
468
  document.addEventListener("keydown", onKeyDown);
@@ -1301,6 +1313,23 @@ var _idCounter = 1;
1301
1313
  function _newId(prefix) {
1302
1314
  return `${prefix}-${_idCounter++}`;
1303
1315
  }
1316
+ function _isRecord(v) {
1317
+ return v !== null && typeof v === "object" && !Array.isArray(v);
1318
+ }
1319
+ function _expect(ok, path, want, got) {
1320
+ if (ok) return;
1321
+ let shown;
1322
+ if (got === void 0) shown = "nothing";
1323
+ else {
1324
+ try {
1325
+ shown = JSON.stringify(got);
1326
+ } catch {
1327
+ shown = String(got);
1328
+ }
1329
+ if (shown.length > 40) shown = `${shown.slice(0, 37)}...`;
1330
+ }
1331
+ throw new TypeError(`${path}: expected ${want}, found ${shown}`);
1332
+ }
1304
1333
  function _isPanel(leaf) {
1305
1334
  return String(leaf?.content?.kind || "").startsWith("panel:");
1306
1335
  }
@@ -2075,11 +2104,44 @@ var TileTree = class _TileTree {
2075
2104
  focusedTitle: this.focused()?.title || null
2076
2105
  };
2077
2106
  }
2078
- static deserialize(blob) {
2107
+ /** Rebuild a tree from what `serialize()` wrote. A blob with no root is an
2108
+ * empty tree, as it always was. Anything else is read STRICTLY: a node that
2109
+ * `serialize()` could not have written throws a `TypeError` naming where
2110
+ * (`at` is the path of the blob, for the message only) and what it wanted,
2111
+ * rather than building half a tree or failing later, inside the renderer,
2112
+ * with `node.sizes.reduce is not a function`. The caller decides what a
2113
+ * tree that cannot be read means; see `WindowManager.load`. */
2114
+ static deserialize(blob, at = "tree") {
2079
2115
  const t = new _TileTree();
2080
2116
  if (!blob || !blob.root) return t;
2081
- const build = (raw) => {
2117
+ const checkTab = (tab, path) => {
2118
+ _expect(_isRecord(tab) && typeof tab.kind === "string", path, "a tab, { kind, props, title }", tab);
2119
+ };
2120
+ const build = (raw, path) => {
2121
+ _expect(_isRecord(raw), path, "a node, an object", raw);
2122
+ _expect(raw.kind === "leaf" || raw.kind === "split", `${path}.kind`, '"leaf" or "split"', raw.kind);
2082
2123
  if (raw.kind === "leaf") {
2124
+ if (raw.content != null) checkTab(raw.content, `${path}.content`);
2125
+ _expect(raw.tabs == null || Array.isArray(raw.tabs), `${path}.tabs`, "an array of tabs", raw.tabs);
2126
+ (raw.tabs || []).forEach((tab, i) => checkTab(tab, `${path}.tabs[${i}]`));
2127
+ _expect(
2128
+ raw.activeTabIdx == null || Number.isFinite(raw.activeTabIdx),
2129
+ `${path}.activeTabIdx`,
2130
+ "a number",
2131
+ raw.activeTabIdx
2132
+ );
2133
+ _expect(raw.pageTabs == null || _isRecord(raw.pageTabs), `${path}.pageTabs`, "an object", raw.pageTabs);
2134
+ for (const [page, arc] of Object.entries(raw.pageTabs || {})) {
2135
+ const where = `${path}.pageTabs[${JSON.stringify(page)}]`;
2136
+ _expect(_isRecord(arc) && Array.isArray(arc.tabs), where, "{ tabs, activeTabIdx }", arc);
2137
+ arc.tabs.forEach((tab, i) => checkTab(tab, `${where}.tabs[${i}]`));
2138
+ _expect(
2139
+ arc.activeTabIdx == null || Number.isFinite(arc.activeTabIdx),
2140
+ `${where}.activeTabIdx`,
2141
+ "a number",
2142
+ arc.activeTabIdx
2143
+ );
2144
+ }
2083
2145
  const leaf = makeLeaf({ content: raw.content, title: raw.title });
2084
2146
  if (Array.isArray(raw.tabs) && raw.tabs.length > 0) {
2085
2147
  leaf.tabs = raw.tabs.map((tab) => ({
@@ -2096,7 +2158,6 @@ var TileTree = class _TileTree {
2096
2158
  if (raw.pageTabs && typeof raw.pageTabs === "object") {
2097
2159
  leaf.pageTabs = {};
2098
2160
  for (const [k, v] of Object.entries(raw.pageTabs)) {
2099
- if (!v || !Array.isArray(v.tabs)) continue;
2100
2161
  leaf.pageTabs[k] = {
2101
2162
  tabs: v.tabs.map((t2) => ({
2102
2163
  kind: t2.kind,
@@ -2109,7 +2170,20 @@ var TileTree = class _TileTree {
2109
2170
  }
2110
2171
  return leaf;
2111
2172
  }
2112
- const children = raw.children.map(build);
2173
+ _expect(raw.dir === "h" || raw.dir === "v", `${path}.dir`, '"h" or "v"', raw.dir);
2174
+ _expect(
2175
+ Array.isArray(raw.children) && raw.children.length > 0,
2176
+ `${path}.children`,
2177
+ "a non-empty array of nodes",
2178
+ raw.children
2179
+ );
2180
+ _expect(
2181
+ Array.isArray(raw.sizes) && raw.sizes.length === raw.children.length && raw.sizes.every((n) => Number.isFinite(n) && n >= 0),
2182
+ `${path}.sizes`,
2183
+ `${raw.children.length} non-negative numbers, one per child`,
2184
+ raw.sizes
2185
+ );
2186
+ const children = raw.children.map((c, i) => build(c, `${path}.children[${i}]`));
2113
2187
  const split = makeSplit({
2114
2188
  dir: raw.dir,
2115
2189
  children: children.map((c) => c.id),
@@ -2118,7 +2192,7 @@ var TileTree = class _TileTree {
2118
2192
  children.forEach((c) => t._register(c));
2119
2193
  return split;
2120
2194
  };
2121
- const root = build(blob.root);
2195
+ const root = build(blob.root, `${at}.root`);
2122
2196
  t.setRoot(root);
2123
2197
  return t;
2124
2198
  }
@@ -2130,6 +2204,23 @@ var DEFAULT_PANEL_STATE = {
2130
2204
  right: true,
2131
2205
  bottom: true
2132
2206
  };
2207
+ function _isRecord2(v) {
2208
+ return v !== null && typeof v === "object" && !Array.isArray(v);
2209
+ }
2210
+ function _expect2(ok, path, want, got) {
2211
+ if (ok) return;
2212
+ let shown;
2213
+ if (got === void 0) shown = "nothing";
2214
+ else {
2215
+ try {
2216
+ shown = JSON.stringify(got);
2217
+ } catch {
2218
+ shown = String(got);
2219
+ }
2220
+ if (shown.length > 40) shown = `${shown.slice(0, 37)}...`;
2221
+ }
2222
+ throw new TypeError(`${path}: expected ${want}, found ${shown}`);
2223
+ }
2133
2224
  function _makeDesktop(label, seed, panelDefaults = DEFAULT_PANEL_STATE) {
2134
2225
  const tree = new TileTree();
2135
2226
  tree.setRoot(makeLeaf(seed()));
@@ -2282,23 +2373,72 @@ var DesktopManager = class _DesktopManager {
2282
2373
  }))
2283
2374
  };
2284
2375
  }
2376
+ /**
2377
+ * Rebuild the manager from what `serialize()` wrote. `null`/`undefined` is
2378
+ * nothing saved and gives the fresh manager.
2379
+ *
2380
+ * The blob is whatever the host's `state` held under `DESKTOPS_KEY`: a file
2381
+ * anyone can truncate or edit, which is valid JSON of any shape. So it is
2382
+ * read STRICTLY, and a blob that `serialize()` could not have written throws
2383
+ * a `TypeError` naming the path and what was expected there. It builds only
2384
+ * objects of its own and touches nothing its caller holds, which is what lets
2385
+ * `WindowManager.load` treat a throw as "nothing saved" with nothing to undo.
2386
+ *
2387
+ * Strict about SHAPE, not about every value. What the loader, the tree
2388
+ * operations and the renderer index, iterate or branch on has to be what they
2389
+ * need (containers, node and tab kinds, `dir`, `children`, `sizes`,
2390
+ * `activeTabIdx`). What is only drawn or coerced is taken as it is: a desktop's
2391
+ * `id`, `label`, `group` and `fixed`, a panel's flag, a tab's title and props,
2392
+ * `activeIdx`. A layout that fails on one of those would be thrown away for
2393
+ * nothing, and an app can put almost anything in a title.
2394
+ *
2395
+ * And strict is not hostile to an old blob. A key the loader has always given
2396
+ * a default may be absent: a desktop's `id`, `label` and `panels`, the keys
2397
+ * later releases added (`panelShares`, `group`, `fixed`), and a leaf's `tabs`
2398
+ * and `pageTabs`. What `serialize()` has always written and the loader cannot
2399
+ * do without (`desktops`, a desktop's `tree`, a split's `dir`, `children` and
2400
+ * `sizes`) may not.
2401
+ */
2285
2402
  static deserialize(blob, { seed, panelDefaults = null } = {}) {
2286
2403
  const m = new _DesktopManager({ seed, panelDefaults });
2287
- if (!blob || !Array.isArray(blob.desktops) || blob.desktops.length === 0) return m;
2288
- m.desktops = blob.desktops.map((raw) => ({
2289
- id: raw.id || `desk-${Math.random().toString(36).slice(2, 8)}`,
2290
- label: raw.label || "?",
2291
- tree: raw.tree ? TileTree.deserialize(raw.tree) : new TileTree(),
2292
- windows: [],
2293
- // A RESTORED desktop's own answer wins over the default: the user
2294
- // closed that panel, and re-opening it on every reload is the bug
2295
- // this merge order avoids. The default only fills a key the stored
2296
- // blob predates.
2297
- panels: { ...m.panelDefaults, ...raw.panels || {} },
2298
- panelShares: { ...raw.panelShares || {} },
2299
- group: raw.group || null,
2300
- fixed: !!raw.fixed
2301
- }));
2404
+ if (blob == null) return m;
2405
+ _expect2(
2406
+ Array.isArray(blob.desktops) && blob.desktops.length > 0,
2407
+ "desktops",
2408
+ "a non-empty array of desktops",
2409
+ blob.desktops
2410
+ );
2411
+ m.desktops = blob.desktops.map((raw, i) => {
2412
+ const at = `desktops[${i}]`;
2413
+ _expect2(_isRecord2(raw), at, "a desktop, an object", raw);
2414
+ _expect2(
2415
+ raw.tree === null || _isRecord2(raw.tree) && _isRecord2(raw.tree.root),
2416
+ `${at}.tree`,
2417
+ "{ root }, or null for an empty desktop",
2418
+ raw.tree
2419
+ );
2420
+ _expect2(raw.panels == null || _isRecord2(raw.panels), `${at}.panels`, "an object", raw.panels);
2421
+ _expect2(
2422
+ raw.panelShares == null || _isRecord2(raw.panelShares),
2423
+ `${at}.panelShares`,
2424
+ "an object",
2425
+ raw.panelShares
2426
+ );
2427
+ return {
2428
+ id: raw.id || `desk-${Math.random().toString(36).slice(2, 8)}`,
2429
+ label: raw.label || "?",
2430
+ tree: raw.tree ? TileTree.deserialize(raw.tree, `${at}.tree`) : new TileTree(),
2431
+ windows: [],
2432
+ // A RESTORED desktop's own answer wins over the default: the user
2433
+ // closed that panel, and re-opening it on every reload is the bug
2434
+ // this merge order avoids. The default only fills a key the stored
2435
+ // blob predates.
2436
+ panels: { ...m.panelDefaults, ...raw.panels || {} },
2437
+ panelShares: { ...raw.panelShares || {} },
2438
+ group: raw.group || null,
2439
+ fixed: !!raw.fixed
2440
+ };
2441
+ });
2302
2442
  for (const d of m.desktops) {
2303
2443
  if (!d.tree.rootId) d.tree.setRoot(makeLeaf(seed()));
2304
2444
  }
@@ -5131,6 +5271,7 @@ var WindowManager = class _WindowManager {
5131
5271
  taxonomy,
5132
5272
  events,
5133
5273
  content,
5274
+ logger = null,
5134
5275
  panelDefaults = null,
5135
5276
  snapPromotion = false,
5136
5277
  promoteInPlace = false,
@@ -5151,6 +5292,7 @@ var WindowManager = class _WindowManager {
5151
5292
  this.events = events || {};
5152
5293
  this.ctx = ctx || {};
5153
5294
  this.eventBus = eventBus || null;
5295
+ this.logger = logger || null;
5154
5296
  this.onChange = onChange || (() => {
5155
5297
  });
5156
5298
  this._panelOverrides = {};
@@ -5287,12 +5429,20 @@ var WindowManager = class _WindowManager {
5287
5429
  // ── Persistence ─────────────────────────────────────────────────
5288
5430
  async load() {
5289
5431
  const blob = await loadDesktops(this.host?.state);
5290
- if (blob) {
5291
- this.desktops = DesktopManager.deserialize(blob, {
5292
- seed: this._rootLeaf,
5293
- panelDefaults: this.panelDefaults
5294
- });
5295
- this.renderer.tree = this.desktops.active().tree;
5432
+ if (blob != null) {
5433
+ let restored = null;
5434
+ try {
5435
+ restored = DesktopManager.deserialize(blob, {
5436
+ seed: this._rootLeaf,
5437
+ panelDefaults: this.panelDefaults
5438
+ });
5439
+ } catch (err) {
5440
+ (this.logger ?? console).warn?.(`[wm] bad layout for state key ${DESKTOPS_KEY} (opening the default layout): ${err?.message ?? err}`);
5441
+ }
5442
+ if (restored) {
5443
+ this.desktops = restored;
5444
+ this.renderer.tree = this.desktops.active().tree;
5445
+ }
5296
5446
  }
5297
5447
  for (const d of this.desktops.desktops) {
5298
5448
  for (const leaf of d.tree.leaves()) {
@@ -8299,13 +8449,28 @@ var WindowManager = class _WindowManager {
8299
8449
  this._notifyChange();
8300
8450
  return true;
8301
8451
  }
8452
+ /**
8453
+ * Move the focused tile, with every tab it holds, to desktop `idx` (from 0).
8454
+ * A refused move returns `undefined` and changes nothing; the callers (the
8455
+ * keymap's `toDesktop:N`, the tile menu's *Move to desktop*) read no result.
8456
+ *
8457
+ * The tile menu (`shell.js`, `_tileContextMenu`) never offers a panel, a
8458
+ * fixed desktop or the desktop you are on, and this method refuses all
8459
+ * three itself, because the menu is one door and Alt+Shift+N is another.
8460
+ * A fixed desktop it always refused; a panel and the desktop you are on it
8461
+ * did not, and the move to the desktop you are on destroys the tile: source
8462
+ * and target are one tree, so the tabs are appended to its primary tile and
8463
+ * then the focused tile is closed. A stray Shift on Alt+N reaches that.
8464
+ */
8302
8465
  moveFocusedToDesktop(idx) {
8303
8466
  const tree = this._tree();
8304
8467
  const focused = tree.focused();
8305
8468
  if (!focused || !focused.content) return;
8469
+ if (String(focused.content.kind || "").startsWith("panel:")) return;
8306
8470
  const closeChrome = this.renderer?.leafChrome?.(focused.id)?.close;
8307
8471
  if (closeChrome === false || closeChrome?.disabled === true) return;
8308
8472
  if (this.desktops.desktops[idx]?.fixed) return;
8473
+ if (idx === this.desktops.activeIdx) return;
8309
8474
  this.desktops.ensureCount(idx + 1);
8310
8475
  const target = this.desktops.desktops[idx];
8311
8476
  const tabs = Array.isArray(focused.tabs) && focused.tabs.length ? focused.tabs : [{ kind: focused.content.kind, props: focused.content.props, title: focused.title }];
@@ -9396,6 +9561,7 @@ async function createShell({
9396
9561
  host,
9397
9562
  taxonomy,
9398
9563
  events,
9564
+ logger,
9399
9565
  panelDefaults: panels,
9400
9566
  snapPromotion,
9401
9567
  promoteInPlace,
@@ -9837,6 +10003,7 @@ function _tileContextMenu(wm, leafId, x, y) {
9837
10003
  }
9838
10004
  export {
9839
10005
  BADGE_LEVELS,
10006
+ DESKTOPS_KEY,
9840
10007
  DesktopManager,
9841
10008
  KEYBOARD_SHORTCUTS_TOPIC_ID,
9842
10009
  KEYMAP_SHORTCUTS,