flexdesk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +195 -0
  3. package/THIRD_PARTY_LICENSES.md +53 -0
  4. package/css/base.css +3439 -0
  5. package/css/flexdesk.css +5797 -0
  6. package/css/overrides.css +2349 -0
  7. package/css/reset.css +46 -0
  8. package/css/tokens.css +137 -0
  9. package/dist/charts.js +780 -0
  10. package/dist/charts.js.map +7 -0
  11. package/dist/chunk-3PHCPZHT.js +222 -0
  12. package/dist/chunk-3PHCPZHT.js.map +7 -0
  13. package/dist/chunk-CT4YXXLP.js +1770 -0
  14. package/dist/chunk-CT4YXXLP.js.map +7 -0
  15. package/dist/chunk-DRYCDMEG.js +2217 -0
  16. package/dist/chunk-DRYCDMEG.js.map +7 -0
  17. package/dist/chunk-DVU44T77.js +855 -0
  18. package/dist/chunk-DVU44T77.js.map +7 -0
  19. package/dist/chunk-FL5KFNQH.js +640 -0
  20. package/dist/chunk-FL5KFNQH.js.map +7 -0
  21. package/dist/chunk-FOOS3T5L.js +117 -0
  22. package/dist/chunk-FOOS3T5L.js.map +7 -0
  23. package/dist/chunk-JYWURG5T.js +32 -0
  24. package/dist/chunk-JYWURG5T.js.map +7 -0
  25. package/dist/chunk-TLZUUFOE.js +276 -0
  26. package/dist/chunk-TLZUUFOE.js.map +7 -0
  27. package/dist/chunk-UCJ2WD4D.js +625 -0
  28. package/dist/chunk-UCJ2WD4D.js.map +7 -0
  29. package/dist/chunk-WVFGV5FT.js +205 -0
  30. package/dist/chunk-WVFGV5FT.js.map +7 -0
  31. package/dist/core.js +1098 -0
  32. package/dist/core.js.map +7 -0
  33. package/dist/editor.js +1661 -0
  34. package/dist/editor.js.map +7 -0
  35. package/dist/flexdesk.css +5797 -0
  36. package/dist/host.js +132 -0
  37. package/dist/host.js.map +7 -0
  38. package/dist/reset.css +46 -0
  39. package/dist/tile_registry-6WZPTUZV.js +28 -0
  40. package/dist/tile_registry-6WZPTUZV.js.map +7 -0
  41. package/dist/tiles.js +2068 -0
  42. package/dist/tiles.js.map +7 -0
  43. package/dist/tokens.css +137 -0
  44. package/dist/widgets.js +5438 -0
  45. package/dist/widgets.js.map +7 -0
  46. package/dist/wm.js +4830 -0
  47. package/dist/wm.js.map +7 -0
  48. package/package.json +80 -0
  49. package/src/charts/chart_types.js +699 -0
  50. package/src/charts/data_series_plot_window.js +761 -0
  51. package/src/charts/downsample.js +119 -0
  52. package/src/charts/plot_config.js +176 -0
  53. package/src/charts/plot_popout_window.js +1560 -0
  54. package/src/charts/plotly_wrapper.js +432 -0
  55. package/src/core/event_bus.js +230 -0
  56. package/src/core/logging.js +300 -0
  57. package/src/core/settings.js +813 -0
  58. package/src/core/state_guard.js +382 -0
  59. package/src/core/state_guard_config.js +309 -0
  60. package/src/core/state_machine.js +258 -0
  61. package/src/editor/editor_pane.js +264 -0
  62. package/src/editor/monaco_editor_factory.js +449 -0
  63. package/src/editor/monaco_loader.js +74 -0
  64. package/src/editor/notebook_search.js +460 -0
  65. package/src/editor/notebook_tab_bar.js +456 -0
  66. package/src/editor/notebook_undo_manager.js +108 -0
  67. package/src/editor/split_pane_container.js +197 -0
  68. package/src/help/help_modal.js +409 -0
  69. package/src/help/help_registry.js +65 -0
  70. package/src/host/host.js +69 -0
  71. package/src/host/pywebview_host.js +133 -0
  72. package/src/tiles/config_schema.js +256 -0
  73. package/src/tiles/layout_persistence.js +662 -0
  74. package/src/tiles/tile_base.js +785 -0
  75. package/src/tiles/tile_grid.js +905 -0
  76. package/src/tiles/tile_registry.js +136 -0
  77. package/src/tiling/command_palette.js +328 -0
  78. package/src/tiling/content_registry.js +110 -0
  79. package/src/tiling/desktops.js +133 -0
  80. package/src/tiling/entity_sources.js +179 -0
  81. package/src/tiling/keymap.js +156 -0
  82. package/src/tiling/kind_taxonomy.js +172 -0
  83. package/src/tiling/landing_table.js +717 -0
  84. package/src/tiling/loading_overlay.js +33 -0
  85. package/src/tiling/nav_panel.js +142 -0
  86. package/src/tiling/page_factory.js +215 -0
  87. package/src/tiling/panel_keys.js +145 -0
  88. package/src/tiling/shell.js +437 -0
  89. package/src/tiling/tile_breadcrumb.js +222 -0
  90. package/src/tiling/tile_renderer.js +459 -0
  91. package/src/tiling/tile_tab_menu.js +340 -0
  92. package/src/tiling/tile_tree.js +880 -0
  93. package/src/tiling/wm.js +1352 -0
  94. package/src/ui/base/component_base.js +30 -0
  95. package/src/ui/base/controller_base.js +48 -0
  96. package/src/ui/base/page_base.js +102 -0
  97. package/src/ui/components/about_dialog.js +224 -0
  98. package/src/ui/components/action_dropdown.js +389 -0
  99. package/src/ui/components/attribute_list_editor.js +652 -0
  100. package/src/ui/components/autocomplete_field.js +732 -0
  101. package/src/ui/components/computing_status_window.js +81 -0
  102. package/src/ui/components/confirm_dialog.js +307 -0
  103. package/src/ui/components/context_menu.js +87 -0
  104. package/src/ui/components/data_table.js +2228 -0
  105. package/src/ui/components/detail_header.js +177 -0
  106. package/src/ui/components/drag_reorder.js +0 -0
  107. package/src/ui/components/gallery_picker.js +147 -0
  108. package/src/ui/components/inline_renamer.js +129 -0
  109. package/src/ui/components/managed_window.js +804 -0
  110. package/src/ui/components/modal.js +512 -0
  111. package/src/ui/components/notification_history.js +264 -0
  112. package/src/ui/components/slide_out_panel.js +185 -0
  113. package/src/ui/components/slider_field.js +122 -0
  114. package/src/ui/components/sortable_list.js +309 -0
  115. package/src/ui/components/table_state_store.js +81 -0
  116. package/src/ui/components/toast.js +59 -0
  117. package/src/ui/components/tree_view.js +460 -0
  118. package/src/ui/components/window_taskbar.js +143 -0
  119. package/src/ui/controllers/panel_state_machine.js +634 -0
  120. package/src/ui/controllers/window_chrome_controller.js +659 -0
  121. package/src/ui/notification_center.js +576 -0
  122. package/src/ui/utils/overlay_scrollbar.js +492 -0
  123. package/src/ui/utils/raf_resize_observer.js +31 -0
  124. package/src/ui/utils/tooltip_service.js +406 -0
@@ -0,0 +1,177 @@
1
+ /**
2
+ * DetailHeader — shared header bar for detail views.
3
+ *
4
+ * Usage:
5
+ * import { createDetailHeader, updateDetailHeader } from '../ui/components/detail_header.js';
6
+ *
7
+ * const header = createDetailHeader({
8
+ * title: 'My Scenario',
9
+ * badges: [{ text: 'Static' }],
10
+ * renameable: true,
11
+ * onRename: async (name) => ({ success: true }),
12
+ * actions: [
13
+ * { key: 'run', label: 'Run', icon: 'play_arrow', variant: 'primary', handler: () => {} },
14
+ * { key: 'delete', label: 'Delete', icon: 'delete', variant: 'danger', handler: () => {} },
15
+ * ],
16
+ * });
17
+ * container.appendChild(header);
18
+ *
19
+ * To update in place (e.g., running state changed):
20
+ * updateDetailHeader(header, { ...newConfig });
21
+ */
22
+
23
+ import { attachInlineRenamer } from './inline_renamer.js';
24
+
25
+ const ESC = (s) => String(s ?? '').replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
26
+
27
+ /**
28
+ * @typedef {Object} DetailHeaderBadge
29
+ * @property {string} text
30
+ * @property {string} [className] - Extra CSS modifier (e.g., 'detail-header__badge--mc')
31
+ * @property {string} [icon] - Material Symbols icon name
32
+ */
33
+
34
+ /**
35
+ * @typedef {Object} DetailHeaderAction
36
+ * @property {string} key - data-action value
37
+ * @property {string} label - Button text
38
+ * @property {string} icon - Material Symbols icon name
39
+ * @property {'primary'|'secondary'|'danger'} [variant='secondary']
40
+ * @property {boolean} [disabled=false]
41
+ * @property {string} [title] - Tooltip
42
+ * @property {Function} handler - Click callback
43
+ */
44
+
45
+ /**
46
+ * @typedef {Object} DetailHeaderConfig
47
+ * @property {string} title
48
+ * @property {string} [subtitle]
49
+ * @property {string} [icon] - Material icon before title
50
+ * @property {DetailHeaderBadge[]} [badges]
51
+ * @property {DetailHeaderAction[]} [actions]
52
+ * @property {HTMLElement[]} [extraElements] - Custom elements appended to actions area
53
+ * @property {boolean} [renameable=false]
54
+ * @property {Function} [onRename] - (newName) => Promise<{success, newValue?}>
55
+ */
56
+
57
+ /**
58
+ * Create a detail header element.
59
+ * @param {DetailHeaderConfig} config
60
+ * @returns {HTMLElement}
61
+ */
62
+ export function createDetailHeader(config) {
63
+ const header = document.createElement('header');
64
+ header.className = 'twm-detail-header';
65
+ _populate(header, config);
66
+ return header;
67
+ }
68
+
69
+ /**
70
+ * Update an existing detail header in place.
71
+ * Destroys previous inline renamer and rebuilds inner DOM.
72
+ * @param {HTMLElement} headerEl
73
+ * @param {DetailHeaderConfig} config
74
+ */
75
+ export function updateDetailHeader(headerEl, config) {
76
+ // Destroy previous renamer if any
77
+ headerEl._renamer?.destroy();
78
+ headerEl._renamer = null;
79
+ headerEl.innerHTML = '';
80
+ _populate(headerEl, config);
81
+ }
82
+
83
+ // ─── Internal ────────────────────────────────────────────────────────────────
84
+
85
+ function _populate(header, config) {
86
+ const { title, subtitle, icon, badges = [], actions = [], extraElements = [], renameable = false, onRename } = config;
87
+
88
+ // Title row
89
+ const titleRow = document.createElement('div');
90
+ titleRow.className = 'twm-detail-header__title-row';
91
+
92
+ if (icon) {
93
+ const iconEl = document.createElement('span');
94
+ iconEl.className = 'twm-detail-header__icon material-symbols-outlined';
95
+ iconEl.textContent = icon;
96
+ titleRow.appendChild(iconEl);
97
+ }
98
+
99
+ const nameEl = document.createElement('h2');
100
+ nameEl.className = 'twm-detail-header__name';
101
+ nameEl.textContent = title || '';
102
+ if (renameable) {
103
+ nameEl.dataset.renameable = '';
104
+ nameEl.title = 'Double-click to rename';
105
+ }
106
+ titleRow.appendChild(nameEl);
107
+
108
+ for (const badge of badges) {
109
+ const badgeEl = document.createElement('span');
110
+ badgeEl.className = `detail-header__badge${badge.className ? ` ${badge.className}` : ''}`;
111
+ if (badge.icon) {
112
+ const bi = document.createElement('span');
113
+ bi.className = 'material-symbols-outlined';
114
+ bi.textContent = badge.icon;
115
+ badgeEl.appendChild(bi);
116
+ }
117
+ badgeEl.appendChild(document.createTextNode(badge.text));
118
+ titleRow.appendChild(badgeEl);
119
+ }
120
+
121
+ header.appendChild(titleRow);
122
+
123
+ // Subtitle
124
+ if (subtitle) {
125
+ const sub = document.createElement('div');
126
+ sub.className = 'twm-detail-header__subtitle';
127
+ sub.innerHTML = subtitle; // may contain <strong> etc.
128
+ header.appendChild(sub);
129
+ }
130
+
131
+ // Actions
132
+ if (actions.length > 0 || extraElements.length > 0) {
133
+ const actionsEl = document.createElement('div');
134
+ actionsEl.className = 'twm-detail-header__actions';
135
+
136
+ for (const extra of extraElements) {
137
+ actionsEl.appendChild(extra);
138
+ }
139
+
140
+ for (const action of actions) {
141
+ const btn = document.createElement('button');
142
+ btn.type = 'button';
143
+ const variant = action.variant || 'secondary';
144
+ btn.className = `twm-detail-header__btn detail-header__btn--${variant}`;
145
+ btn.dataset.action = action.key;
146
+ if (action.disabled) btn.disabled = true;
147
+ if (action.title) btn.title = action.title;
148
+
149
+ const iconSpan = document.createElement('span');
150
+ iconSpan.className = 'material-symbols-outlined';
151
+ iconSpan.textContent = action.icon;
152
+ btn.appendChild(iconSpan);
153
+ btn.appendChild(document.createTextNode(` ${action.label}`));
154
+
155
+ btn.addEventListener('click', () => action.handler?.());
156
+ actionsEl.appendChild(btn);
157
+ }
158
+
159
+ header.appendChild(actionsEl);
160
+ }
161
+
162
+ // Inline rename
163
+ if (renameable && onRename) {
164
+ const renamer = attachInlineRenamer({
165
+ label: nameEl,
166
+ onCommit: async (newName) => {
167
+ if (!newName?.trim()) return { success: false };
168
+ return await onRename(newName.trim());
169
+ },
170
+ });
171
+ nameEl.addEventListener('dblclick', (e) => {
172
+ e.stopPropagation();
173
+ renamer.start(e);
174
+ });
175
+ header._renamer = renamer;
176
+ }
177
+ }
@@ -0,0 +1,147 @@
1
+ /**
2
+ * gallery_picker.js — single-select card grid for the starter catalog.
3
+ *
4
+ * G1.3 in docs/STARTER_GALLERY_REQUIREMENTS.md. Used by every
5
+ * "New X" modal to let the user pick a starter shape (formerly
6
+ * called a "template"). The picker is mounted into a host element
7
+ * and fires `onChange(entry)` whenever the selection changes.
8
+ *
9
+ * Each entry is the raw gallery dict from `pywebview.api.gallery_list`:
10
+ *
11
+ * { id, kind, label, description, fields }
12
+ *
13
+ * The picker shows label + description as a card. `fields` is
14
+ * passed through to the caller untouched on select — the New-X
15
+ * handler decides what to do with it.
16
+ *
17
+ * Usage:
18
+ *
19
+ * const picker = await mountGalleryPicker(hostEl, {
20
+ * kind: 'agent',
21
+ * selectedId: 'blank',
22
+ * onChange: (entry) => { … },
23
+ * });
24
+ *
25
+ * // later …
26
+ * picker.getSelected(); // → { id, kind, label, description, fields }
27
+ * picker.dispose();
28
+ */
29
+
30
+ function esc(s) {
31
+ return String(s ?? '').replace(/[&<>"']/g, (c) => ({
32
+ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;',
33
+ }[c]));
34
+ }
35
+
36
+
37
+ /**
38
+ * Mount a starter-gallery picker into `hostEl`.
39
+ *
40
+ * @param {HTMLElement} hostEl
41
+ * @param {object} opts
42
+ * @param {string} opts.kind — gallery entries to load (e.g. 'agent').
43
+ * When undefined, every kind shows up.
44
+ * @param {string} [opts.selectedId] — id to pre-select. If absent, the
45
+ * first entry wins.
46
+ * @param {function} [opts.onChange] — `(entry) => void` fired on selection
47
+ * change AND on initial mount.
48
+ * @param {object[]} [opts.entries] — explicit entries (skips bridge fetch).
49
+ * Useful for tests + offline previews.
50
+ * @param {string} [opts.emptyLabel] — copy shown when the catalog is empty
51
+ * for that kind.
52
+ * @returns {Promise<{getSelected, setSelectedById, dispose}>}
53
+ */
54
+ export async function mountGalleryPicker(hostEl, opts = {}) {
55
+ const {
56
+ kind = null,
57
+ selectedId = null,
58
+ onChange = () => {},
59
+ entries = null,
60
+ emptyLabel = 'No starters available for this kind.',
61
+ } = opts;
62
+ if (!hostEl) throw new Error('mountGalleryPicker: hostEl is required');
63
+
64
+ let catalog = entries;
65
+ if (!catalog) {
66
+ const api = window.pywebview?.api;
67
+ try {
68
+ catalog = (await api?.gallery_list?.(kind)) || [];
69
+ } catch (err) {
70
+ console.warn('[gallery-picker] gallery_list failed', err);
71
+ catalog = [];
72
+ }
73
+ }
74
+ catalog = Array.isArray(catalog) ? catalog : [];
75
+
76
+ hostEl.classList.add('twm-gallery-picker');
77
+ if (catalog.length === 0) {
78
+ hostEl.innerHTML = `<div class="twm-gallery-picker__empty">${esc(emptyLabel)}</div>`;
79
+ return {
80
+ getSelected: () => null,
81
+ setSelectedById: () => false,
82
+ dispose: () => { hostEl.classList.remove('twm-gallery-picker'); },
83
+ };
84
+ }
85
+
86
+ let selected = catalog.find((e) => e?.id === selectedId)
87
+ || catalog[0];
88
+
89
+ const render = () => {
90
+ hostEl.innerHTML = catalog.map((e, i) => {
91
+ const isSel = e === selected;
92
+ const desc = e?.description
93
+ ? `<p class="twm-gallery-card__desc">${esc(e.description)}</p>`
94
+ : '';
95
+ return `
96
+ <button type="button"
97
+ class="twm-gallery-card ${isSel ? 'is-selected' : ''}"
98
+ data-idx="${i}"
99
+ title="${esc(e?.id || '')}">
100
+ <span class="twm-gallery-card__label">${esc(e?.label || e?.id || '')}</span>
101
+ ${desc}
102
+ </button>
103
+ `;
104
+ }).join('');
105
+ };
106
+ render();
107
+
108
+ const onClick = (ev) => {
109
+ const card = ev.target.closest?.('.twm-gallery-card');
110
+ if (!card || !hostEl.contains(card)) return;
111
+ const idx = Number(card.dataset.idx);
112
+ const next = catalog[idx];
113
+ if (!next || next === selected) return;
114
+ selected = next;
115
+ render();
116
+ try { onChange(selected); } catch (err) {
117
+ console.warn('[gallery-picker] onChange threw', err);
118
+ }
119
+ };
120
+ hostEl.addEventListener('click', onClick);
121
+
122
+ // Fire the initial selection so the caller can prime any
123
+ // downstream UI (preview pane, defaults, etc.) without a
124
+ // special case for first-render.
125
+ try { onChange(selected); } catch (err) {
126
+ console.warn('[gallery-picker] initial onChange threw', err);
127
+ }
128
+
129
+ return {
130
+ getSelected: () => (selected ? { ...selected } : null),
131
+ setSelectedById: (id) => {
132
+ const next = catalog.find((e) => e?.id === id);
133
+ if (!next || next === selected) return false;
134
+ selected = next;
135
+ render();
136
+ try { onChange(selected); } catch (err) {
137
+ console.warn('[gallery-picker] onChange threw', err);
138
+ }
139
+ return true;
140
+ },
141
+ dispose: () => {
142
+ hostEl.removeEventListener('click', onClick);
143
+ hostEl.innerHTML = '';
144
+ hostEl.classList.remove('twm-gallery-picker');
145
+ },
146
+ };
147
+ }
@@ -0,0 +1,129 @@
1
+ /**
2
+ * InlineRenamer (js_new)
3
+ * Minimal inline editing helper used by node labels and scenario titles.
4
+ * Attaches an input on demand, commits through provided callback, and cleans up deterministically.
5
+ */
6
+
7
+ function assertLabel(label) {
8
+ if (!label || typeof label.appendChild !== 'function') {
9
+ throw new Error('[InlineRenamer] options.label must be an HTMLElement');
10
+ }
11
+ }
12
+
13
+ function buildInput(label, options) {
14
+ const input = document.createElement('input');
15
+ input.type = 'text';
16
+ input.className = ['twm-node-edit-input', options?.inputClass || ''].join(' ').trim();
17
+ input.placeholder = options?.placeholder || label.textContent || 'Name';
18
+ input.value = options?.initialValue ?? label.textContent ?? '';
19
+ input.spellcheck = false;
20
+ input.autocomplete = 'off';
21
+ input.setAttribute('aria-label', 'Edit name');
22
+ return input;
23
+ }
24
+
25
+ export function attachInlineRenamer(options = {}) {
26
+ const label = options.label;
27
+ assertLabel(label);
28
+
29
+ let input = null;
30
+ let editing = false;
31
+ let destroyed = false;
32
+ let originalText = label.textContent || '';
33
+
34
+ const cleanup = () => {
35
+ if (input && input.parentNode === label) {
36
+ input.remove();
37
+ }
38
+ input = null;
39
+ editing = false;
40
+ };
41
+
42
+ const cancel = () => {
43
+ if (destroyed) return;
44
+ cleanup();
45
+ label.textContent = originalText;
46
+ options.onCancel?.({ value: originalText });
47
+ };
48
+
49
+ const commit = async (reason = 'commit') => {
50
+ if (destroyed || !input) return;
51
+ const raw = input.value;
52
+ let next = raw;
53
+ try {
54
+ next = options.transformInput ? options.transformInput(raw, { reason }) : raw;
55
+ } catch (err) {
56
+ window.logger?.warn('inline-renamer', 'transformInput failed', err);
57
+ }
58
+
59
+ try {
60
+ const result = (await options.onCommit?.(next, { reason })) || {};
61
+ if (result.success === false) {
62
+ if (result.keepEditing) {
63
+ input.focus();
64
+ input.select();
65
+ return;
66
+ }
67
+ cancel();
68
+ return;
69
+ }
70
+ const finalValue = result.newValue ?? next ?? '';
71
+ label.textContent = finalValue;
72
+ cleanup();
73
+ options.onFinish?.({ value: finalValue });
74
+ } catch (err) {
75
+ window.logger?.warn('inline-renamer', 'onCommit failed', err);
76
+ cancel();
77
+ }
78
+ };
79
+
80
+ const start = (triggerEvent = null) => {
81
+ if (destroyed || editing) return;
82
+ editing = true;
83
+ originalText = label.textContent || '';
84
+ label.textContent = '';
85
+ input = buildInput(label, { ...options, initialValue: originalText });
86
+ label.appendChild(input);
87
+
88
+ const stopEvents = (ev) => ev.stopPropagation();
89
+ input.addEventListener('mousedown', stopEvents);
90
+ input.addEventListener('click', stopEvents);
91
+
92
+ input.addEventListener('keydown', (ev) => {
93
+ if (ev.key === 'Enter') {
94
+ ev.preventDefault();
95
+ commit('enter');
96
+ } else if (ev.key === 'Escape') {
97
+ ev.preventDefault();
98
+ cancel();
99
+ }
100
+ });
101
+
102
+ input.addEventListener('blur', () => commit('blur'));
103
+
104
+ options.onStart?.({ value: originalText, triggerEvent });
105
+ setTimeout(() => {
106
+ input?.focus();
107
+ input?.select?.();
108
+ }, 0);
109
+ };
110
+
111
+ const destroy = () => {
112
+ destroyed = true;
113
+ cleanup();
114
+ };
115
+
116
+ return { start, cancel, commit, destroy };
117
+ }
118
+
119
+ // `window.InlineRenamer` used to be installed here, at module scope, "for existing
120
+ // callers". There are none: every caller in the tree — EcoAgent's and the
121
+ // library's — uses the `attachInlineRenamer` named export. It was a global that
122
+ // nothing read.
123
+ //
124
+ // It also made this module UNIMPORTABLE outside a browser, because the assignment
125
+ // ran on import. That is how it was found: a node test that does nothing but
126
+ // `import '@flexdesk/widgets'` died with `ReferenceError: window is not defined`. A
127
+ // library whose entry point cannot be imported without a DOM is a library that
128
+ // cannot be unit-tested, server-rendered, or loaded in a worker — and
129
+ // package.json's `"sideEffects": ["*.css"]` was a lie while this line existed.