flexdesk 0.17.13 → 0.17.15

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 (250) hide show
  1. package/CHANGELOG.md +43 -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 +58 -7
  9. package/css/flexdesk.css +90 -8
  10. package/css/overrides.css +21 -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-IG74S3ZH.js → chunk-AD7FIA3Z.js} +2 -2
  17. package/dist/chunk-FOOS3T5L.js +0 -0
  18. package/dist/chunk-FOOS3T5L.js.map +0 -0
  19. package/dist/chunk-GOQLR5WR.js +0 -0
  20. package/dist/chunk-GOQLR5WR.js.map +0 -0
  21. package/dist/chunk-HNU6MM2B.js +0 -0
  22. package/dist/chunk-HNU6MM2B.js.map +0 -0
  23. package/dist/chunk-JKZLNKG6.js +0 -0
  24. package/dist/chunk-JKZLNKG6.js.map +0 -0
  25. package/dist/chunk-JYWURG5T.js +0 -0
  26. package/dist/chunk-JYWURG5T.js.map +0 -0
  27. package/dist/chunk-N7HC3ADV.js +0 -0
  28. package/dist/chunk-N7HC3ADV.js.map +0 -0
  29. package/dist/{chunk-ZMH3OC7H.js → chunk-OEG6NPAN.js} +3 -1
  30. package/dist/chunk-OEG6NPAN.js.map +7 -0
  31. package/dist/core.js +3 -18
  32. package/dist/core.js.map +2 -2
  33. package/dist/editor.js.map +0 -0
  34. package/dist/flexdesk.css +90 -8
  35. package/dist/flow.js +1 -1
  36. package/dist/flow.js.map +0 -0
  37. package/dist/host.js +0 -0
  38. package/dist/host.js.map +0 -0
  39. package/dist/reset.css +0 -0
  40. package/dist/tile_registry-6WZPTUZV.js +0 -0
  41. package/dist/tile_registry-6WZPTUZV.js.map +0 -0
  42. package/dist/tiles.js +2 -2
  43. package/dist/widgets.js +29 -20
  44. package/dist/widgets.js.map +2 -2
  45. package/dist/wm.js +205 -35
  46. package/dist/wm.js.map +3 -3
  47. package/docs/README.md +1 -1
  48. package/docs/best-practices.md +1 -1
  49. package/docs/components/command-palette.md +0 -0
  50. package/docs/components/conversation.md +0 -0
  51. package/docs/components/data-table-grid.md +0 -0
  52. package/docs/components/data-table.md +0 -0
  53. package/docs/components/editor.md +0 -0
  54. package/docs/components/flow-canvas-editor.md +0 -0
  55. package/docs/components/flow-editors.md +0 -0
  56. package/docs/components/flow-lane-editor.md +0 -0
  57. package/docs/components/flow-outline-editor.md +0 -0
  58. package/docs/components/forms-and-dialogs.md +19 -2
  59. package/docs/components/input-fields.md +0 -0
  60. package/docs/components/keymap.md +66 -3
  61. package/docs/components/layout-widgets.md +32 -0
  62. package/docs/components/markdown-and-outline.md +0 -0
  63. package/docs/components/menus.md +9 -5
  64. package/docs/components/notifications.md +0 -0
  65. package/docs/components/panels-and-desktops.md +56 -0
  66. package/docs/components/record-list-and-fact-table.md +0 -0
  67. package/docs/components/settings-view.md +0 -0
  68. package/docs/components/shell.md +15 -0
  69. package/docs/components/slide-out-panel.md +15 -0
  70. package/docs/components/wizard.md +0 -0
  71. package/docs/concepts/building-and-packaging.md +0 -0
  72. package/docs/concepts/domain-neutrality.md +0 -0
  73. package/docs/concepts/host-port.md +0 -0
  74. package/docs/concepts/packages-and-entry-points.md +0 -0
  75. package/docs/concepts/peer-dependencies.md +0 -0
  76. package/docs/concepts/state-guard.md +65 -2
  77. package/docs/concepts/styling.md +24 -6
  78. package/docs/quickstart.md +0 -0
  79. package/editor.js +0 -0
  80. package/flow.js +0 -0
  81. package/host.js +0 -0
  82. package/package.json +2 -2
  83. package/src/canvas/canvas_adapter.js +0 -0
  84. package/src/canvas/edge_router.js +0 -0
  85. package/src/canvas/graph/connector_router.js +0 -0
  86. package/src/canvas/graph/history_service.js +0 -0
  87. package/src/canvas/graph/node_platform.js +0 -0
  88. package/src/canvas/index.js +0 -0
  89. package/src/canvas/layered_layout.js +0 -0
  90. package/src/charts/data_series_plot_window.js +0 -0
  91. package/src/charts/downsample.js +0 -0
  92. package/src/charts/plot_config.js +0 -0
  93. package/src/charts/plotly_wrapper.js +0 -0
  94. package/src/core/event_bus.js +0 -0
  95. package/src/core/logging.js +0 -0
  96. package/src/core/settings.js +0 -0
  97. package/src/core/state_guard.js +0 -0
  98. package/src/core/state_guard_config.js +19 -18
  99. package/src/core/state_machine.js +0 -0
  100. package/src/editor/editor_pane.js +0 -0
  101. package/src/editor/monaco_editor_factory.js +0 -0
  102. package/src/editor/monaco_loader.js +0 -0
  103. package/src/editor/notebook_search.js +0 -0
  104. package/src/editor/notebook_undo_manager.js +0 -0
  105. package/src/editor/split_pane_container.js +0 -0
  106. package/src/flow/canvas_editor/connections.js +0 -0
  107. package/src/flow/canvas_editor/editor.js +0 -0
  108. package/src/flow/canvas_editor/index.js +0 -0
  109. package/src/flow/canvas_editor/layout.js +0 -0
  110. package/src/flow/canvas_editor/strings.js +0 -0
  111. package/src/flow/kit/always_before.js +0 -0
  112. package/src/flow/kit/catalogue.js +0 -0
  113. package/src/flow/kit/chip_input.js +0 -0
  114. package/src/flow/kit/dom.js +0 -0
  115. package/src/flow/kit/findings.js +0 -0
  116. package/src/flow/kit/history.js +0 -0
  117. package/src/flow/kit/index.js +0 -0
  118. package/src/flow/kit/keys.js +0 -0
  119. package/src/flow/kit/logic_graph.js +0 -0
  120. package/src/flow/kit/references.js +0 -0
  121. package/src/flow/kit/settings_panel.js +0 -0
  122. package/src/flow/kit/settings_schema.js +0 -0
  123. package/src/flow/kit/step_picker.js +0 -0
  124. package/src/flow/kit/strings.js +0 -0
  125. package/src/flow/kit/value_picker.js +0 -0
  126. package/src/flow/kit/widgets.js +0 -0
  127. package/src/flow/lanes/editor.js +0 -0
  128. package/src/flow/lanes/index.js +0 -0
  129. package/src/flow/lanes/layout.js +0 -0
  130. package/src/flow/lanes/pipeline.js +0 -0
  131. package/src/flow/lanes/preview.js +0 -0
  132. package/src/flow/lanes/strings.js +0 -0
  133. package/src/flow/outline/editor.js +0 -0
  134. package/src/flow/outline/index.js +0 -0
  135. package/src/flow/outline/mapping.js +0 -0
  136. package/src/flow/outline/menu.js +0 -0
  137. package/src/flow/outline/operations.js +0 -0
  138. package/src/flow/outline/recognise.js +0 -0
  139. package/src/flow/outline/strings.js +0 -0
  140. package/src/help/help_modal.js +0 -0
  141. package/src/help/help_registry.js +0 -0
  142. package/src/host/external_links.js +0 -0
  143. package/src/host/file_picker.js +0 -0
  144. package/src/host/host.js +0 -0
  145. package/src/host/photino_host.js +0 -0
  146. package/src/host/photino_host.md +0 -0
  147. package/src/host/pywebview_host.js +0 -0
  148. package/src/tiles/chart_tiles.js +0 -0
  149. package/src/tiles/config_schema.js +0 -0
  150. package/src/tiles/figure_tile.js +0 -0
  151. package/src/tiles/layout_persistence.js +0 -0
  152. package/src/tiles/list_tiles.js +0 -0
  153. package/src/tiles/scope_picker.js +0 -0
  154. package/src/tiles/tile_config_form.js +0 -0
  155. package/src/tiles/tile_grid.js +0 -0
  156. package/src/tiles/tile_registry.js +0 -0
  157. package/src/tiling/bottom_panel_tabs.js +0 -0
  158. package/src/tiling/command_palette.js +0 -0
  159. package/src/tiling/content_registry.js +0 -0
  160. package/src/tiling/desktops.js +79 -16
  161. package/src/tiling/entity_sources.js +0 -0
  162. package/src/tiling/keymap.js +70 -2
  163. package/src/tiling/kind_taxonomy.js +0 -0
  164. package/src/tiling/landing_table.js +0 -0
  165. package/src/tiling/loading_overlay.js +0 -0
  166. package/src/tiling/nav_panel.js +0 -0
  167. package/src/tiling/page_factory.js +0 -0
  168. package/src/tiling/panel_availability.js +0 -0
  169. package/src/tiling/panel_keys.js +0 -0
  170. package/src/tiling/shell.js +9 -2
  171. package/src/tiling/tab_strip.js +0 -0
  172. package/src/tiling/tile_breadcrumb.js +12 -4
  173. package/src/tiling/tile_renderer.js +0 -0
  174. package/src/tiling/tile_tab_menu.js +0 -0
  175. package/src/tiling/tile_tree.js +63 -5
  176. package/src/tiling/wm.js +59 -6
  177. package/src/tiling/zoom.js +0 -0
  178. package/src/ui/base/component_base.js +0 -0
  179. package/src/ui/base/controller_base.js +0 -0
  180. package/src/ui/base/page_base.js +0 -0
  181. package/src/ui/components/about_dialog.js +0 -0
  182. package/src/ui/components/alert_log.js +0 -0
  183. package/src/ui/components/alerts_pane.js +0 -0
  184. package/src/ui/components/attribute_list_editor.js +0 -0
  185. package/src/ui/components/autocomplete_field.js +0 -0
  186. package/src/ui/components/cell_editor_host.js +0 -0
  187. package/src/ui/components/cell_editors.js +0 -0
  188. package/src/ui/components/cell_grid.js +0 -0
  189. package/src/ui/components/cell_paste.js +0 -0
  190. package/src/ui/components/cell_selection.js +0 -0
  191. package/src/ui/components/choice_controls.js +0 -0
  192. package/src/ui/components/color_field.js +0 -0
  193. package/src/ui/components/color_list_field.js +0 -0
  194. package/src/ui/components/column_fit.js +0 -0
  195. package/src/ui/components/column_groups.js +0 -0
  196. package/src/ui/components/column_order.js +0 -0
  197. package/src/ui/components/computing_status_window.js +0 -0
  198. package/src/ui/components/confirm_dialog.js +0 -0
  199. package/src/ui/components/conversation_view.js +0 -0
  200. package/src/ui/components/crosstab.js +0 -0
  201. package/src/ui/components/detail_header.js +0 -0
  202. package/src/ui/components/document_outline.js +0 -0
  203. package/src/ui/components/drag_reorder.js +0 -0
  204. package/src/ui/components/fact_table.js +0 -0
  205. package/src/ui/components/fill_handle.js +0 -0
  206. package/src/ui/components/gallery_picker.js +0 -0
  207. package/src/ui/components/header_actions.js +0 -0
  208. package/src/ui/components/inline_renamer.js +0 -0
  209. package/src/ui/components/job_progress.js +0 -0
  210. package/src/ui/components/menu_bar.js +0 -0
  211. package/src/ui/components/modal.js +11 -2
  212. package/src/ui/components/notification_history.js +0 -0
  213. package/src/ui/components/progress.js +0 -0
  214. package/src/ui/components/record_list.js +0 -0
  215. package/src/ui/components/row_activation.js +0 -0
  216. package/src/ui/components/section_stack.js +0 -0
  217. package/src/ui/components/series.js +0 -0
  218. package/src/ui/components/settings_view.js +0 -0
  219. package/src/ui/components/slide_out_panel.js +0 -0
  220. package/src/ui/components/slider_field.js +0 -0
  221. package/src/ui/components/sortable_list.js +0 -0
  222. package/src/ui/components/staged_edits.js +0 -0
  223. package/src/ui/components/staged_history.js +0 -0
  224. package/src/ui/components/table_filter_chips.js +0 -0
  225. package/src/ui/components/table_state_store.js +0 -0
  226. package/src/ui/components/table_structure.js +0 -0
  227. package/src/ui/components/table_values.js +0 -0
  228. package/src/ui/components/tabs.js +0 -0
  229. package/src/ui/components/tag_input.js +0 -0
  230. package/src/ui/components/toast.js +0 -0
  231. package/src/ui/components/toggle_field.js +0 -0
  232. package/src/ui/components/tree_view.js +41 -18
  233. package/src/ui/components/tsv.js +0 -0
  234. package/src/ui/components/window_taskbar.js +0 -0
  235. package/src/ui/components/wizard.js +0 -0
  236. package/src/ui/controllers/window_chrome_controller.js +0 -0
  237. package/src/ui/notification_center.js +0 -0
  238. package/src/ui/utils/auto_scrollbars.js +0 -0
  239. package/src/ui/utils/markdown.js +0 -0
  240. package/src/ui/utils/native_context_menu.js +0 -0
  241. package/src/ui/utils/overlay_scrollbar.js +0 -0
  242. package/src/ui/utils/raf_resize_observer.js +0 -0
  243. package/src/ui/utils/scroll_lock.js +0 -0
  244. package/src/ui/utils/strip_scroll.js +0 -0
  245. package/src/ui/utils/tooltip_service.js +0 -0
  246. package/tiles.js +0 -0
  247. package/widgets.js +0 -0
  248. package/wm.js +0 -0
  249. package/dist/chunk-ZMH3OC7H.js.map +0 -7
  250. /package/dist/{chunk-IG74S3ZH.js.map → chunk-AD7FIA3Z.js.map} +0 -0
package/dist/wm.js CHANGED
@@ -8,10 +8,10 @@ import {
8
8
  } from "./chunk-REJ5HLIM.js";
9
9
  import {
10
10
  showContextMenu
11
- } from "./chunk-IG74S3ZH.js";
11
+ } from "./chunk-AD7FIA3Z.js";
12
12
  import {
13
13
  openForm
14
- } from "./chunk-ZMH3OC7H.js";
14
+ } from "./chunk-OEG6NPAN.js";
15
15
  import {
16
16
  ManagedWindow,
17
17
  isRowActivation
@@ -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
  }
@@ -3331,7 +3471,11 @@ function mountTileBreadcrumb(kind, props, ctx) {
3331
3471
  const rootCrumb = ctx?.rootCrumb || null;
3332
3472
  if (!taxonomy) {
3333
3473
  console.error("[breadcrumb] no taxonomy in ctx \u2014 cannot render");
3334
- return { el: document.createElement("nav"), destroy: () => {
3474
+ const el = document.createElement("nav");
3475
+ el.className = "twm-topbar-breadcrumb twm-tile-breadcrumb";
3476
+ el.hidden = true;
3477
+ return { el, refresh: () => {
3478
+ }, destroy: () => {
3335
3479
  } };
3336
3480
  }
3337
3481
  const root = document.createElement("nav");
@@ -3365,10 +3509,9 @@ function mountTileBreadcrumb(kind, props, ctx) {
3365
3509
  }
3366
3510
  };
3367
3511
  const render = () => {
3368
- _renderInto(
3369
- root,
3370
- _segments(kind, props, taxonomy, rootCrumb, rootLabel, navigate, trailOf())
3371
- );
3512
+ const segments = _segments(kind, props, taxonomy, rootCrumb, rootLabel, navigate, trailOf());
3513
+ _renderInto(root, segments);
3514
+ root.hidden = segments.length < 2;
3372
3515
  };
3373
3516
  render();
3374
3517
  let unsubscribe = null;
@@ -5131,6 +5274,7 @@ var WindowManager = class _WindowManager {
5131
5274
  taxonomy,
5132
5275
  events,
5133
5276
  content,
5277
+ logger = null,
5134
5278
  panelDefaults = null,
5135
5279
  snapPromotion = false,
5136
5280
  promoteInPlace = false,
@@ -5151,6 +5295,7 @@ var WindowManager = class _WindowManager {
5151
5295
  this.events = events || {};
5152
5296
  this.ctx = ctx || {};
5153
5297
  this.eventBus = eventBus || null;
5298
+ this.logger = logger || null;
5154
5299
  this.onChange = onChange || (() => {
5155
5300
  });
5156
5301
  this._panelOverrides = {};
@@ -5287,12 +5432,20 @@ var WindowManager = class _WindowManager {
5287
5432
  // ── Persistence ─────────────────────────────────────────────────
5288
5433
  async load() {
5289
5434
  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;
5435
+ if (blob != null) {
5436
+ let restored = null;
5437
+ try {
5438
+ restored = DesktopManager.deserialize(blob, {
5439
+ seed: this._rootLeaf,
5440
+ panelDefaults: this.panelDefaults
5441
+ });
5442
+ } catch (err) {
5443
+ (this.logger ?? console).warn?.(`[wm] bad layout for state key ${DESKTOPS_KEY} (opening the default layout): ${err?.message ?? err}`);
5444
+ }
5445
+ if (restored) {
5446
+ this.desktops = restored;
5447
+ this.renderer.tree = this.desktops.active().tree;
5448
+ }
5296
5449
  }
5297
5450
  for (const d of this.desktops.desktops) {
5298
5451
  for (const leaf of d.tree.leaves()) {
@@ -8299,13 +8452,28 @@ var WindowManager = class _WindowManager {
8299
8452
  this._notifyChange();
8300
8453
  return true;
8301
8454
  }
8455
+ /**
8456
+ * Move the focused tile, with every tab it holds, to desktop `idx` (from 0).
8457
+ * A refused move returns `undefined` and changes nothing; the callers (the
8458
+ * keymap's `toDesktop:N`, the tile menu's *Move to desktop*) read no result.
8459
+ *
8460
+ * The tile menu (`shell.js`, `_tileContextMenu`) never offers a panel, a
8461
+ * fixed desktop or the desktop you are on, and this method refuses all
8462
+ * three itself, because the menu is one door and Alt+Shift+N is another.
8463
+ * A fixed desktop it always refused; a panel and the desktop you are on it
8464
+ * did not, and the move to the desktop you are on destroys the tile: source
8465
+ * and target are one tree, so the tabs are appended to its primary tile and
8466
+ * then the focused tile is closed. A stray Shift on Alt+N reaches that.
8467
+ */
8302
8468
  moveFocusedToDesktop(idx) {
8303
8469
  const tree = this._tree();
8304
8470
  const focused = tree.focused();
8305
8471
  if (!focused || !focused.content) return;
8472
+ if (String(focused.content.kind || "").startsWith("panel:")) return;
8306
8473
  const closeChrome = this.renderer?.leafChrome?.(focused.id)?.close;
8307
8474
  if (closeChrome === false || closeChrome?.disabled === true) return;
8308
8475
  if (this.desktops.desktops[idx]?.fixed) return;
8476
+ if (idx === this.desktops.activeIdx) return;
8309
8477
  this.desktops.ensureCount(idx + 1);
8310
8478
  const target = this.desktops.desktops[idx];
8311
8479
  const tabs = Array.isArray(focused.tabs) && focused.tabs.length ? focused.tabs : [{ kind: focused.content.kind, props: focused.content.props, title: focused.title }];
@@ -9396,6 +9564,7 @@ async function createShell({
9396
9564
  host,
9397
9565
  taxonomy,
9398
9566
  events,
9567
+ logger,
9399
9568
  panelDefaults: panels,
9400
9569
  snapPromotion,
9401
9570
  promoteInPlace,
@@ -9837,6 +10006,7 @@ function _tileContextMenu(wm, leafId, x, y) {
9837
10006
  }
9838
10007
  export {
9839
10008
  BADGE_LEVELS,
10009
+ DESKTOPS_KEY,
9840
10010
  DesktopManager,
9841
10011
  KEYBOARD_SHORTCUTS_TOPIC_ID,
9842
10012
  KEYMAP_SHORTCUTS,