flexdesk 0.16.1 → 0.17.1

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 (262) hide show
  1. package/CHANGELOG.md +112 -0
  2. package/LICENSE +0 -0
  3. package/README.md +2435 -42
  4. package/THIRD_PARTY_LICENSES.md +0 -0
  5. package/canvas.js +18 -0
  6. package/charts.js +21 -0
  7. package/core.js +14 -0
  8. package/css/base.css +1834 -75
  9. package/css/flexdesk.css +3165 -159
  10. package/css/overrides.css +16 -0
  11. package/css/reset.css +0 -0
  12. package/css/tokens.css +0 -0
  13. package/dist/canvas.js +6 -239
  14. package/dist/canvas.js.map +3 -3
  15. package/dist/charts.js +707 -18
  16. package/dist/charts.js.map +4 -4
  17. package/dist/chunk-3PHCPZHT.js +0 -0
  18. package/dist/chunk-3PHCPZHT.js.map +0 -0
  19. package/dist/chunk-4A7SJC3T.js +503 -0
  20. package/dist/chunk-4A7SJC3T.js.map +7 -0
  21. package/dist/{chunk-6JSOVNID.js → chunk-CVMCZZX2.js} +635 -10
  22. package/dist/chunk-CVMCZZX2.js.map +7 -0
  23. package/dist/chunk-D3COLZZ2.js +1311 -0
  24. package/dist/chunk-D3COLZZ2.js.map +7 -0
  25. package/dist/chunk-FOOS3T5L.js +0 -0
  26. package/dist/chunk-FOOS3T5L.js.map +0 -0
  27. package/dist/{chunk-SN4LSOAR.js → chunk-GOQLR5WR.js} +452 -2
  28. package/dist/chunk-GOQLR5WR.js.map +7 -0
  29. package/dist/{chunk-FL5KFNQH.js → chunk-HNU6MM2B.js} +52 -17
  30. package/dist/chunk-HNU6MM2B.js.map +7 -0
  31. package/dist/chunk-HYJYYEPK.js +375 -0
  32. package/dist/chunk-HYJYYEPK.js.map +7 -0
  33. package/dist/chunk-JKZLNKG6.js +2041 -0
  34. package/dist/chunk-JKZLNKG6.js.map +7 -0
  35. package/dist/chunk-JYWURG5T.js +0 -0
  36. package/dist/chunk-JYWURG5T.js.map +0 -0
  37. package/dist/{chunk-FBGP5KN5.js → chunk-LWV23YVE.js} +5073 -532
  38. package/dist/chunk-LWV23YVE.js.map +7 -0
  39. package/dist/chunk-N7HC3ADV.js +30 -0
  40. package/dist/chunk-N7HC3ADV.js.map +7 -0
  41. package/dist/{chunk-RQM5Z2MU.js → chunk-RMTRWWEO.js} +633 -311
  42. package/dist/chunk-RMTRWWEO.js.map +7 -0
  43. package/dist/{chunk-NLTOY57M.js → chunk-UURZ37SD.js} +28 -48
  44. package/dist/chunk-UURZ37SD.js.map +7 -0
  45. package/dist/{chunk-B5Q53LAJ.js → chunk-UWO43BZU.js} +2 -2
  46. package/dist/chunk-UWO43BZU.js.map +7 -0
  47. package/dist/chunk-WVFGV5FT.js +0 -0
  48. package/dist/chunk-WVFGV5FT.js.map +0 -0
  49. package/dist/core.js +170 -118
  50. package/dist/core.js.map +2 -2
  51. package/dist/editor.js +156 -70
  52. package/dist/editor.js.map +2 -2
  53. package/dist/flexdesk.css +3165 -159
  54. package/dist/flow.js +2789 -306
  55. package/dist/flow.js.map +4 -4
  56. package/dist/host.js +109 -3
  57. package/dist/host.js.map +3 -3
  58. package/dist/reset.css +0 -0
  59. package/dist/tile_registry-6WZPTUZV.js +0 -0
  60. package/dist/tile_registry-6WZPTUZV.js.map +0 -0
  61. package/dist/tiles.js +657 -234
  62. package/dist/tiles.js.map +4 -4
  63. package/dist/tokens.css +0 -0
  64. package/dist/widgets.js +3290 -2124
  65. package/dist/widgets.js.map +4 -4
  66. package/dist/wm.js +1227 -347
  67. package/dist/wm.js.map +4 -4
  68. package/editor.js +20 -0
  69. package/flow.js +21 -0
  70. package/host.js +14 -0
  71. package/package.json +12 -1
  72. package/src/canvas/canvas_adapter.js +5 -0
  73. package/src/canvas/edge_router.js +0 -0
  74. package/src/canvas/graph/connector_router.js +0 -0
  75. package/src/canvas/graph/history_service.js +0 -0
  76. package/src/canvas/graph/node_platform.js +0 -0
  77. package/src/canvas/index.js +1 -0
  78. package/src/canvas/layered_layout.js +244 -0
  79. package/src/charts/chart_types.js +0 -0
  80. package/src/charts/data_series_plot_window.js +0 -0
  81. package/src/charts/downsample.js +2 -2
  82. package/src/charts/plot_config.js +53 -29
  83. package/src/charts/plot_config_editor.js +627 -0
  84. package/src/charts/plot_grid.js +449 -0
  85. package/src/charts/plot_popout_window.js +103 -317
  86. package/src/charts/plotly_wrapper.js +22 -2
  87. package/src/charts/series_transforms.js +206 -0
  88. package/src/core/event_bus.js +0 -0
  89. package/src/core/logging.js +1 -1
  90. package/src/core/settings.js +105 -19
  91. package/src/core/state_guard.js +93 -51
  92. package/src/core/state_guard_config.js +85 -74
  93. package/src/core/state_machine.js +0 -0
  94. package/src/editor/editor_pane.js +3 -3
  95. package/src/editor/monaco_editor_factory.js +124 -60
  96. package/src/editor/monaco_loader.js +103 -28
  97. package/src/editor/notebook_search.js +0 -0
  98. package/src/editor/notebook_tab_bar.js +38 -52
  99. package/src/editor/notebook_undo_manager.js +0 -0
  100. package/src/editor/split_pane_container.js +6 -3
  101. package/src/flow/canvas_editor/connections.js +90 -0
  102. package/src/flow/canvas_editor/editor.js +2351 -153
  103. package/src/flow/canvas_editor/index.js +2 -1
  104. package/src/flow/canvas_editor/layout.js +119 -12
  105. package/src/flow/canvas_editor/strings.js +80 -0
  106. package/src/flow/kit/always_before.js +0 -0
  107. package/src/flow/kit/catalogue.js +97 -3
  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 +3 -3
  113. package/src/flow/kit/keys.js +7 -1
  114. package/src/flow/kit/logic_graph.js +97 -15
  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 +2 -1
  118. package/src/flow/kit/step_picker.js +0 -0
  119. package/src/flow/kit/strings.js +3 -1
  120. package/src/flow/kit/value_picker.js +0 -0
  121. package/src/flow/kit/widgets.js +86 -109
  122. package/src/flow/lanes/editor.js +136 -14
  123. package/src/flow/lanes/index.js +3 -1
  124. package/src/flow/lanes/layout.js +0 -0
  125. package/src/flow/lanes/pipeline.js +44 -2
  126. package/src/flow/lanes/preview.js +0 -0
  127. package/src/flow/lanes/strings.js +1 -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 +168 -42
  136. package/src/help/help_registry.js +12 -1
  137. package/src/host/external_links.js +0 -0
  138. package/src/host/file_picker.js +117 -0
  139. package/src/host/host.js +10 -2
  140. package/src/host/photino_host.js +9 -0
  141. package/src/host/photino_host.md +21 -3
  142. package/src/host/pywebview_host.js +10 -0
  143. package/src/tiles/chart_tiles.js +53 -245
  144. package/src/tiles/config_schema.js +8 -0
  145. package/src/tiles/figure_tile.js +322 -0
  146. package/src/tiles/layout_persistence.js +3 -3
  147. package/src/tiles/list_tiles.js +7 -7
  148. package/src/tiles/scope_picker.js +0 -0
  149. package/src/tiles/tile_base.js +1 -1
  150. package/src/tiles/tile_config_form.js +442 -0
  151. package/src/tiles/tile_grid.js +2 -0
  152. package/src/tiles/tile_registry.js +0 -0
  153. package/src/tiling/bottom_panel_tabs.js +67 -99
  154. package/src/tiling/command_palette.js +64 -19
  155. package/src/tiling/content_registry.js +4 -1
  156. package/src/tiling/desktops.js +11 -1
  157. package/src/tiling/entity_sources.js +0 -0
  158. package/src/tiling/keymap.js +403 -128
  159. package/src/tiling/kind_taxonomy.js +78 -0
  160. package/src/tiling/landing_table.js +0 -0
  161. package/src/tiling/loading_overlay.js +0 -0
  162. package/src/tiling/nav_panel.js +59 -10
  163. package/src/tiling/page_factory.js +5 -5
  164. package/src/tiling/panel_availability.js +49 -0
  165. package/src/tiling/panel_keys.js +0 -0
  166. package/src/tiling/shell.js +114 -12
  167. package/src/tiling/tab_strip.js +0 -0
  168. package/src/tiling/tile_breadcrumb.js +0 -0
  169. package/src/tiling/tile_renderer.js +105 -9
  170. package/src/tiling/tile_tab_menu.js +0 -0
  171. package/src/tiling/tile_tree.js +0 -0
  172. package/src/tiling/wm.js +562 -30
  173. package/src/tiling/zoom.js +0 -0
  174. package/src/ui/base/component_base.js +0 -0
  175. package/src/ui/base/controller_base.js +0 -0
  176. package/src/ui/base/page_base.js +0 -0
  177. package/src/ui/components/about_dialog.js +0 -0
  178. package/src/ui/components/action_dropdown.js +30 -2
  179. package/src/ui/components/alert_log.js +0 -0
  180. package/src/ui/components/alerts_pane.js +0 -0
  181. package/src/ui/components/attribute_list_editor.js +0 -0
  182. package/src/ui/components/autocomplete_field.js +0 -0
  183. package/src/ui/components/cell_editor_host.js +293 -0
  184. package/src/ui/components/cell_editors.js +480 -0
  185. package/src/ui/components/cell_grid.js +761 -0
  186. package/src/ui/components/cell_paste.js +243 -0
  187. package/src/ui/components/cell_selection.js +220 -0
  188. package/src/ui/components/choice_controls.js +351 -0
  189. package/src/ui/components/color_field.js +211 -0
  190. package/src/ui/components/color_list_field.js +210 -0
  191. package/src/ui/components/column_fit.js +0 -0
  192. package/src/ui/components/column_groups.js +314 -0
  193. package/src/ui/components/column_order.js +142 -0
  194. package/src/ui/components/computing_status_window.js +98 -25
  195. package/src/ui/components/confirm_dialog.js +81 -38
  196. package/src/ui/components/context_menu.js +2 -2
  197. package/src/ui/components/conversation_view.js +440 -0
  198. package/src/ui/components/crosstab.js +289 -0
  199. package/src/ui/components/data_table.js +696 -71
  200. package/src/ui/components/detail_header.js +22 -11
  201. package/src/ui/components/document_outline.js +397 -0
  202. package/src/ui/components/drag_reorder.js +0 -0
  203. package/src/ui/components/fact_table.js +0 -0
  204. package/src/ui/components/fill_handle.js +322 -0
  205. package/src/ui/components/gallery_picker.js +19 -8
  206. package/src/ui/components/header_actions.js +55 -0
  207. package/src/ui/components/inline_renamer.js +0 -0
  208. package/src/ui/components/job_progress.js +51 -0
  209. package/src/ui/components/managed_window.js +27 -4
  210. package/src/ui/components/menu_bar.js +745 -0
  211. package/src/ui/components/modal.js +449 -75
  212. package/src/ui/components/notification_history.js +3 -3
  213. package/src/ui/components/progress.js +483 -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 +298 -0
  217. package/src/ui/components/series.js +173 -0
  218. package/src/ui/components/settings_view.js +634 -0
  219. package/src/ui/components/slide_out_panel.js +43 -6
  220. package/src/ui/components/slider_field.js +275 -18
  221. package/src/ui/components/sortable_list.js +0 -0
  222. package/src/ui/components/staged_edits.js +361 -0
  223. package/src/ui/components/staged_history.js +172 -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 +182 -0
  227. package/src/ui/components/table_values.js +0 -0
  228. package/src/ui/components/tabs.js +497 -0
  229. package/src/ui/components/tag_input.js +215 -0
  230. package/src/ui/components/toast.js +25 -8
  231. package/src/ui/components/toggle_field.js +175 -0
  232. package/src/ui/components/tree_view.js +55 -34
  233. package/src/ui/components/tsv.js +74 -0
  234. package/src/ui/components/window_taskbar.js +0 -0
  235. package/src/ui/components/wizard.js +563 -0
  236. package/src/ui/controllers/window_chrome_controller.js +2 -2
  237. package/src/ui/notification_center.js +375 -119
  238. package/src/ui/utils/auto_scrollbars.js +0 -0
  239. package/src/ui/utils/markdown.js +709 -0
  240. package/src/ui/utils/native_context_menu.js +0 -0
  241. package/src/ui/utils/overlay_scrollbar.js +16 -12
  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 +55 -0
  245. package/src/ui/utils/tooltip_service.js +0 -8
  246. package/tiles.js +27 -0
  247. package/widgets.js +69 -0
  248. package/wm.js +28 -0
  249. package/dist/chunk-6JSOVNID.js.map +0 -7
  250. package/dist/chunk-B5Q53LAJ.js.map +0 -7
  251. package/dist/chunk-FBGP5KN5.js.map +0 -7
  252. package/dist/chunk-FL5KFNQH.js.map +0 -7
  253. package/dist/chunk-NLTOY57M.js.map +0 -7
  254. package/dist/chunk-NNFTETK3.js +0 -633
  255. package/dist/chunk-NNFTETK3.js.map +0 -7
  256. package/dist/chunk-O5OHMWBB.js +0 -284
  257. package/dist/chunk-O5OHMWBB.js.map +0 -7
  258. package/dist/chunk-RQM5Z2MU.js.map +0 -7
  259. package/dist/chunk-SN4LSOAR.js.map +0 -7
  260. package/dist/chunk-W6FBJLPW.js +0 -367
  261. package/dist/chunk-W6FBJLPW.js.map +0 -7
  262. package/src/ui/controllers/panel_state_machine.js +0 -634
package/css/flexdesk.css CHANGED
@@ -164,6 +164,10 @@
164
164
  font-size: var(--font-size-sm);
165
165
  line-height: 1.5;
166
166
  }
167
+ /* Shown: the service adds `visible` once the tooltip is positioned, and removes it to fade out */
168
+ .twm-latex-tooltip.visible {
169
+ opacity: 1;
170
+ }
167
171
  /* Tooltip header (first line) styling */
168
172
  .twm-latex-tooltip strong {
169
173
  display: block;
@@ -360,6 +364,46 @@
360
364
  background: var(--accent);
361
365
  border-color: var(--accent);
362
366
  }
367
+ /* NotificationCenter progress toasts: a bar, a status line, a collapse toggle. */
368
+ .twm-notification__collapse {
369
+ width: 22px;
370
+ height: 22px;
371
+ border: none;
372
+ border-radius: var(--radius-sm);
373
+ background: rgba(255, 255, 255, 0.06);
374
+ color: #999;
375
+ cursor: pointer;
376
+ display: inline-flex;
377
+ align-items: center;
378
+ justify-content: center;
379
+ padding: 0;
380
+ flex-shrink: 0;
381
+ transition: background var(--transition-fast), color var(--transition-fast), transform var(--transition-fast);
382
+ }
383
+ .twm-notification__collapse:hover {
384
+ background: rgba(255, 255, 255, 0.14);
385
+ color: var(--text-bright);
386
+ }
387
+ .twm-notification--collapsed .twm-notification__collapse {
388
+ transform: rotate(-90deg);
389
+ }
390
+ .twm-notification__progress {
391
+ margin-top: var(--space-sm);
392
+ }
393
+ .twm-notification__status {
394
+ margin-top: var(--space-xs);
395
+ font-size: var(--font-size-xs);
396
+ color: var(--text-secondary);
397
+ overflow: hidden;
398
+ text-overflow: ellipsis;
399
+ white-space: nowrap;
400
+ }
401
+ .twm-notification--collapsed .twm-notification__body,
402
+ .twm-notification--collapsed .twm-notification__status,
403
+ .twm-notification--collapsed .twm-notification__description,
404
+ .twm-notification--collapsed .twm-notification__actions {
405
+ display: none;
406
+ }
363
407
  /* Notification history (bottom bar bell + popover) */
364
408
  .twm-notification-history__trigger {
365
409
  display: flex;
@@ -461,6 +505,10 @@
461
505
  padding: var(--space-md) 10px;
462
506
  font-size: var(--font-size-sm);
463
507
  }
508
+ .twm-notification-history__item--success { border-left-color: var(--color-success, #3fb950); }
509
+ .twm-notification-history__item--warn { border-left-color: var(--color-warning, #ff9800); }
510
+ .twm-notification-history__item--error,
511
+ .twm-notification-history__item--fatal { border-left-color: var(--color-danger, #c74e4e); }
464
512
  .twm-notification-history__item-header {
465
513
  display: flex;
466
514
  align-items: center;
@@ -634,9 +682,14 @@ body.twm-dt-col-resizing {
634
682
  width: auto;
635
683
  min-width: 80px;
636
684
  }
637
- /* Alternating row backgrounds */
685
+ /* Alternating row backgrounds. The stripe is the row's `--twm-dt-row-bg`, set
686
+ at no specificity so a consumer's row class can replace it; a crosstab
687
+ (STORY-0091) also lays it under its pinned cells. */
688
+ :where(.twm-preview-table tbody tr:nth-child(even)) {
689
+ --twm-dt-row-bg: rgba(255, 255, 255, 0.02);
690
+ }
638
691
  .twm-preview-table tbody tr:nth-child(even) {
639
- background: rgba(255, 255, 255, 0.02);
692
+ background: var(--twm-dt-row-bg);
640
693
  }
641
694
  /* Hover state (disabled in wizard via modifier) */
642
695
  .twm-preview-table:not(.twm-preview-table--readonly) tr:hover td {
@@ -780,6 +833,296 @@ body.twm-dt-col-resizing {
780
833
  opacity: 1;
781
834
  outline: 1px solid var(--accent-bright, #007acc);
782
835
  }
836
+ /* STORY-0003 `selectionMode: 'cells'` — the range is a tint on its cells, the
837
+ * cursor a ring inside its cell (inset, so it moves no column by a pixel). A
838
+ * cell being edited gives its padding to the editor; a commit still out is
839
+ * dimmed, and a refused one carries a red ring and its message as the title. */
840
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--selected {
841
+ background: var(--state-selected, rgba(0, 122, 204, 0.15));
842
+ }
843
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--cursor {
844
+ box-shadow: inset 0 0 0 2px var(--accent-bright, #007acc);
845
+ }
846
+ .twm-preview-table.twm-dt--cells:focus {
847
+ outline: none;
848
+ }
849
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--editing {
850
+ padding: 0;
851
+ }
852
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--editing .twm-dt-editor {
853
+ display: block;
854
+ width: 100%;
855
+ box-sizing: border-box;
856
+ margin: 0;
857
+ padding: 2px 6px;
858
+ border: 0;
859
+ outline: 2px solid var(--accent-bright, #007acc);
860
+ outline-offset: -2px;
861
+ background: var(--surface-well, #1e1e1e);
862
+ color: inherit;
863
+ font: inherit;
864
+ }
865
+ /* STORY-0087 the built-in editors: a checkbox keeps its own size, a text
866
+ * area grows with what it holds, and the choice box shows no empty helper
867
+ * line under it. */
868
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--editing .twm-dt-editor--bool {
869
+ width: auto;
870
+ margin: 4px 6px;
871
+ outline-offset: 1px;
872
+ }
873
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--editing textarea.twm-dt-editor {
874
+ resize: vertical;
875
+ min-height: 3.5em;
876
+ }
877
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--editing .twm-dt-editor--choice .twm-autocomplete-field__helper:empty {
878
+ display: none;
879
+ }
880
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--pending {
881
+ opacity: 0.6;
882
+ font-style: italic;
883
+ }
884
+ /* STORY-0088 `staged`: a value typed and not yet committed. */
885
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--staged {
886
+ background: color-mix(in srgb, var(--accent-bright, #007acc) 16%, transparent);
887
+ }
888
+ /* STORY-0089 `fillHandle`: a square on the range's corner cell, the cells a
889
+ * drag covers dashed, and a cell the fill skipped (not editable) hatched. */
890
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--fill-corner {
891
+ position: relative;
892
+ }
893
+ .twm-preview-table.twm-dt--cells .twm-dt-fill-handle {
894
+ position: absolute;
895
+ right: 0;
896
+ bottom: 0;
897
+ width: 7px;
898
+ height: 7px;
899
+ box-sizing: border-box;
900
+ background: var(--accent-bright, #007acc);
901
+ border: 1px solid var(--surface-well, #1e1e1e);
902
+ cursor: crosshair;
903
+ z-index: 1;
904
+ touch-action: none;
905
+ }
906
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--fill-preview {
907
+ outline: 1px dashed var(--accent-bright, #007acc);
908
+ outline-offset: -1px;
909
+ }
910
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--fill-skipped {
911
+ background-image: repeating-linear-gradient(45deg,
912
+ color-mix(in srgb, var(--color-danger, #f14c4c) 22%, transparent) 0 4px, transparent 4px 8px);
913
+ }
914
+ /* STORY-0090. A cell a paste skipped (not editable) carries the same mark. */
915
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--paste-skipped {
916
+ background-image: repeating-linear-gradient(45deg,
917
+ color-mix(in srgb, var(--color-danger, #f14c4c) 22%, transparent) 0 4px, transparent 4px 8px);
918
+ }
919
+ /* While cells are staged, sort, filters and pages are held: the held control
920
+ * says why on itself (`data-refusal`). */
921
+ .twm-data-table-component.twm-dt--frozen th.sortable[data-refusal] {
922
+ cursor: not-allowed !important;
923
+ }
924
+ .twm-data-table-component.twm-dt--frozen [data-refusal]:disabled {
925
+ cursor: not-allowed;
926
+ opacity: 0.5;
927
+ }
928
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--error {
929
+ box-shadow: inset 0 0 0 1px var(--color-danger, #f14c4c);
930
+ }
931
+ .twm-preview-table.twm-dt--cells tbody td.twm-dt-cell--error.twm-dt-cell--cursor {
932
+ box-shadow: inset 0 0 0 2px var(--color-danger, #f14c4c);
933
+ }
934
+ /* STORY-0086 `reorderable` — the header being dragged is dimmed, and the one
935
+ * it would land on carries a bar on the side it lands (inset: no column moves
936
+ * by a pixel while the pointer passes). */
937
+ .twm-preview-table th.twm-dt-col--movable {
938
+ cursor: grab;
939
+ }
940
+ .twm-preview-table th.twm-dt-col--dragging {
941
+ opacity: 0.5;
942
+ }
943
+ .twm-preview-table th.twm-dt-col--drop {
944
+ box-shadow: inset 2px 0 0 0 var(--accent-bright, #007acc);
945
+ }
946
+ .twm-preview-table th.twm-dt-col--drop.twm-dt-col--drop-after {
947
+ box-shadow: inset -2px 0 0 0 var(--accent-bright, #007acc);
948
+ }
949
+ /* STORY-0066 `columnGroups` — the group row is a table of its own above the
950
+ * header table (`twm-dt-groups`), its `<col>`s copied from the header's widths;
951
+ * no first-column floor or cap of the header's applies to it. A crosstab's
952
+ * spanned header cells (STORY-0091) are the same group cells. A placeholder
953
+ * column (an empty group's) is drawn muted. The header action buttons are
954
+ * small icon buttons that keep to the cell. */
955
+ .twm-preview-table.twm-dt-groups th.twm-dt-group,
956
+ .twm-preview-table.twm-dt-crosstab th.twm-dt-group {
957
+ width: auto;
958
+ min-width: 0;
959
+ max-width: none;
960
+ text-align: center;
961
+ border-bottom: 1px solid var(--border-subtle, #333);
962
+ white-space: nowrap;
963
+ overflow: hidden;
964
+ text-overflow: ellipsis;
965
+ }
966
+ .twm-preview-table.twm-dt-groups th.twm-dt-group + th.twm-dt-group,
967
+ .twm-preview-table.twm-dt-crosstab th.twm-dt-group + th.twm-dt-group {
968
+ border-left: 1px solid var(--border-subtle, #333);
969
+ }
970
+ .twm-preview-table.twm-dt-groups th.twm-dt-group--empty .twm-dt-group__label {
971
+ opacity: 0.7;
972
+ }
973
+ .twm-preview-table .twm-dt-group__actions,
974
+ .twm-preview-table .twm-dt-col-actions {
975
+ display: inline-flex;
976
+ gap: 2px;
977
+ margin-left: 4px;
978
+ vertical-align: middle;
979
+ }
980
+ .twm-preview-table .twm-dt-group__add,
981
+ .twm-preview-table .twm-dt-group__action,
982
+ .twm-preview-table .twm-dt-col-action {
983
+ display: inline-flex;
984
+ align-items: center;
985
+ justify-content: center;
986
+ padding: 0 2px;
987
+ border: 0;
988
+ border-radius: 3px;
989
+ background: transparent;
990
+ color: inherit;
991
+ cursor: pointer;
992
+ opacity: 0.6;
993
+ font: inherit;
994
+ }
995
+ .twm-preview-table .twm-dt-group__add .material-symbols-outlined,
996
+ .twm-preview-table .twm-dt-group__action .material-symbols-outlined,
997
+ .twm-preview-table .twm-dt-col-action .material-symbols-outlined {
998
+ font-size: 16px;
999
+ }
1000
+ .twm-preview-table .twm-dt-group__add:hover,
1001
+ .twm-preview-table .twm-dt-group__action:hover,
1002
+ .twm-preview-table .twm-dt-col-action:hover {
1003
+ opacity: 1;
1004
+ background: rgba(255, 255, 255, 0.08);
1005
+ }
1006
+ .twm-preview-table .twm-dt-group__add:focus-visible,
1007
+ .twm-preview-table .twm-dt-group__action:focus-visible,
1008
+ .twm-preview-table .twm-dt-col-action:focus-visible {
1009
+ opacity: 1;
1010
+ outline: 1px solid var(--accent-bright, #007acc);
1011
+ }
1012
+ .twm-preview-table th.twm-dt-col--placeholder,
1013
+ .twm-preview-table td.twm-dt-cell--placeholder {
1014
+ background: rgba(255, 255, 255, 0.015);
1015
+ }
1016
+ .twm-preview-table th .twm-dt-col-rename {
1017
+ width: 100%;
1018
+ box-sizing: border-box;
1019
+ font: inherit;
1020
+ }
1021
+ /* STORY-0091 `crosstab` — one table in one scroller (`twm-dt-crosstab`).
1022
+ * Every header cell sticks to the top at the `top` crosstab.js measured for its
1023
+ * row; the pinned cells (select, row number and label columns) stick to the
1024
+ * left at the `left` it measured, and the corner — header cells over them — to
1025
+ * both, above everything else. Every crosstab row paints its `--twm-dt-row-bg`,
1026
+ * so a row coloured through it is one colour from the label cells to the last
1027
+ * column.
1028
+ *
1029
+ * A pinned body cell needs an OPAQUE background or the cells scrolling under it
1030
+ * show through, and every state FlexDesk paints on a `td` is translucent. So
1031
+ * the pinned cell's background is one `!important` stack nothing can replace —
1032
+ * not a stronger state rule, not an animation (an important declaration
1033
+ * outranks both) — and the states reach it as LAYERS instead:
1034
+ * --twm-dt-pin-mark a mark over everything (the fill's skipped hatch)
1035
+ * --twm-dt-pin-new the new-row flash (a registered colour, so it animates)
1036
+ * --twm-dt-pin-state the cell's state tint: hover, row and cell selection,
1037
+ * staged, placeholder, diff
1038
+ * --twm-dt-row-bg the row's background (the stripe or a consumer's colour)
1039
+ * --surface-0 the opaque surface under all of them.
1040
+ * Each state rule below is the unpinned rule it mirrors, with
1041
+ * `.twm-dt-crosstab` on the table and `.twm-dt-pin` on the cell — the same two
1042
+ * classes on every one, in the same order, so the state a pinned cell shows is
1043
+ * the one its unpinned neighbours show. */
1044
+ @property --twm-dt-pin-new {
1045
+ syntax: '<color>';
1046
+ inherits: false;
1047
+ initial-value: transparent;
1048
+ }
1049
+ .twm-preview-table.twm-dt-crosstab tbody tr {
1050
+ background: var(--twm-dt-row-bg, transparent);
1051
+ }
1052
+ .twm-preview-table.twm-dt-crosstab thead th {
1053
+ position: sticky;
1054
+ z-index: 3;
1055
+ }
1056
+ .twm-preview-table.twm-dt-crosstab thead th.twm-dt-pin {
1057
+ z-index: 4;
1058
+ }
1059
+ .twm-preview-table.twm-dt-crosstab tbody td.twm-dt-pin {
1060
+ position: sticky;
1061
+ z-index: 1;
1062
+ background:
1063
+ var(--twm-dt-pin-mark, none),
1064
+ linear-gradient(var(--twm-dt-pin-new, transparent), var(--twm-dt-pin-new, transparent)),
1065
+ var(--twm-dt-pin-state, none),
1066
+ linear-gradient(var(--twm-dt-row-bg, transparent), var(--twm-dt-row-bg, transparent)) var(--surface-0, #1e1e1e) !important;
1067
+ }
1068
+ .twm-preview-table.twm-dt-crosstab .twm-dt-pin--last {
1069
+ box-shadow: inset -1px 0 0 var(--border-strong, #3c3c3c);
1070
+ }
1071
+ .twm-preview-table.twm-dt-crosstab:not(.twm-preview-table--readonly) tr:hover td.twm-dt-pin {
1072
+ --twm-dt-pin-state: linear-gradient(rgba(255, 255, 255, 0.04), rgba(255, 255, 255, 0.04));
1073
+ }
1074
+ .twm-preview-table.twm-dt-crosstab:not(.twm-preview-table--readonly) tbody tr.selected td.twm-dt-pin,
1075
+ .twm-preview-table.twm-dt-crosstab tbody tr.selected td.twm-dt-pin {
1076
+ --twm-dt-pin-state: linear-gradient(var(--state-selected, rgba(0, 122, 204, 0.15)), var(--state-selected, rgba(0, 122, 204, 0.15)));
1077
+ }
1078
+ .twm-preview-table.twm-dt-crosstab.twm-dt--clickable tbody tr:not(.twm-dt-spacer):not(.data-table__empty-row):not(.selected):hover td.twm-dt-pin {
1079
+ --twm-dt-pin-state: linear-gradient(var(--state-hover, rgba(255, 255, 255, 0.05)), var(--state-hover, rgba(255, 255, 255, 0.05)));
1080
+ }
1081
+ .twm-preview-table.twm-dt-crosstab.twm-dt--cells tbody td.twm-dt-pin.twm-dt-cell--selected {
1082
+ --twm-dt-pin-state: linear-gradient(var(--state-selected, rgba(0, 122, 204, 0.15)), var(--state-selected, rgba(0, 122, 204, 0.15)));
1083
+ }
1084
+ .twm-preview-table.twm-dt-crosstab.twm-dt--cells tbody td.twm-dt-pin.twm-dt-cell--staged {
1085
+ --twm-dt-pin-state: linear-gradient(color-mix(in srgb, var(--accent-bright, #007acc) 16%, transparent), color-mix(in srgb, var(--accent-bright, #007acc) 16%, transparent));
1086
+ }
1087
+ .twm-preview-table.twm-dt-crosstab.twm-dt--cells tbody td.twm-dt-pin.twm-dt-cell--fill-skipped {
1088
+ --twm-dt-pin-mark: repeating-linear-gradient(45deg,
1089
+ color-mix(in srgb, var(--color-danger, #f14c4c) 22%, transparent) 0 4px, transparent 4px 8px);
1090
+ }
1091
+ .twm-preview-table.twm-dt-crosstab.twm-dt--cells tbody td.twm-dt-pin.twm-dt-cell--paste-skipped {
1092
+ --twm-dt-pin-mark: repeating-linear-gradient(45deg,
1093
+ color-mix(in srgb, var(--color-danger, #f14c4c) 22%, transparent) 0 4px, transparent 4px 8px);
1094
+ }
1095
+ .twm-preview-table.twm-dt-crosstab td.twm-dt-pin.twm-dt-cell--placeholder {
1096
+ --twm-dt-pin-state: linear-gradient(rgba(255, 255, 255, 0.015), rgba(255, 255, 255, 0.015));
1097
+ }
1098
+ .twm-data-table-component .twm-preview-table.twm-dt-crosstab tbody tr.twm-dt-diff--added:not(.selected) > td.twm-dt-pin {
1099
+ --twm-dt-pin-state: linear-gradient(color-mix(in srgb, var(--color-success, #3fb950) 16%, transparent), color-mix(in srgb, var(--color-success, #3fb950) 16%, transparent));
1100
+ }
1101
+ .twm-data-table-component .twm-preview-table.twm-dt-crosstab tbody tr.twm-dt-diff--removed:not(.selected) > td.twm-dt-pin {
1102
+ --twm-dt-pin-state: linear-gradient(color-mix(in srgb, var(--color-danger, #c74e4e) 18%, transparent), color-mix(in srgb, var(--color-danger, #c74e4e) 18%, transparent));
1103
+ }
1104
+ .twm-data-table-component .twm-preview-table.twm-dt-crosstab tbody tr.twm-dt-diff:not(.selected) > td.twm-dt-pin.twm-dt-diff__cell--changed {
1105
+ --twm-dt-pin-state: linear-gradient(color-mix(in srgb, var(--color-warning, #ff9800) 22%, transparent), color-mix(in srgb, var(--color-warning, #ff9800) 22%, transparent));
1106
+ }
1107
+ .twm-data-table-component .twm-preview-table.twm-dt-crosstab tbody tr.twm-dt-diff--gap > td.twm-dt-pin.twm-dt-diff__gap {
1108
+ --twm-dt-pin-state: repeating-linear-gradient(135deg,
1109
+ transparent 0 6px,
1110
+ color-mix(in srgb, var(--text-muted, #888) 14%, transparent) 6px 7px);
1111
+ }
1112
+ .twm-preview-table.twm-dt-crosstab tr:hover td.twm-dt-pin {
1113
+ --twm-dt-pin-state: linear-gradient(rgba(255,255,255,0.04), rgba(255,255,255,0.04));
1114
+ }
1115
+ @keyframes twm-dt-pin-new {
1116
+ from { --twm-dt-pin-new: var(--twm-dt-new-bg, rgba(0, 122, 204, 0.22)); }
1117
+ to { --twm-dt-pin-new: transparent; }
1118
+ }
1119
+ .twm-preview-table.twm-dt-crosstab tbody tr.twm-dt-row--new > td.twm-dt-pin {
1120
+ animation: twm-dt-pin-new 1.6s ease-out;
1121
+ }
1122
+ .twm-preview-table.twm-dt-crosstab th.twm-dt-crosstab__none {
1123
+ font-style: italic;
1124
+ opacity: 0.7;
1125
+ }
783
1126
  /* In the mode a click ticks: a pointer on every row, and the keyboard's row
784
1127
  * (the arrows move it, Space ticks it) drawn as an outline. */
785
1128
  .twm-preview-table.twm-dt--selecting tbody tr:not(.twm-dt-spacer):not(.data-table__empty-row) {
@@ -1107,6 +1450,82 @@ body.twm-dt-col-resizing {
1107
1450
  cursor: grab;
1108
1451
  border: 2px solid var(--surface-0);
1109
1452
  }
1453
+ /* ══ SLIDER FIELD COMPANIONS ══ number box, readout, reset, enable toggle (STORY-0045) */
1454
+ .twm-slider-field__row {
1455
+ display: flex;
1456
+ align-items: center;
1457
+ gap: var(--space-sm);
1458
+ min-width: 0;
1459
+ }
1460
+ .twm-slider-field__row > .twm-slider-field__input {
1461
+ flex: 1 1 auto;
1462
+ min-width: 0;
1463
+ }
1464
+ .twm-slider-field__toggle {
1465
+ flex: 0 0 auto;
1466
+ margin: 0;
1467
+ cursor: pointer;
1468
+ }
1469
+ .twm-slider-field__readout {
1470
+ flex: 0 0 auto;
1471
+ min-width: 3em;
1472
+ text-align: right;
1473
+ font-size: var(--font-size-sm);
1474
+ font-variant-numeric: tabular-nums;
1475
+ color: var(--text-dim);
1476
+ }
1477
+ .twm-slider-field__number {
1478
+ flex: 0 0 auto;
1479
+ width: 6em;
1480
+ padding: var(--space-xxs) var(--space-xs);
1481
+ font: inherit;
1482
+ font-size: var(--font-size-sm);
1483
+ font-variant-numeric: tabular-nums;
1484
+ text-align: right;
1485
+ color: var(--text-body);
1486
+ background: var(--surface-well);
1487
+ border: 1px solid var(--border-default);
1488
+ border-radius: var(--radius-sm);
1489
+ }
1490
+ .twm-slider-field__number:focus {
1491
+ outline: none;
1492
+ border-color: var(--accent);
1493
+ }
1494
+ .twm-slider-field__reset {
1495
+ flex: 0 0 auto;
1496
+ display: inline-flex;
1497
+ align-items: center;
1498
+ justify-content: center;
1499
+ padding: 0;
1500
+ width: 22px;
1501
+ height: 22px;
1502
+ color: var(--text-dim);
1503
+ background: transparent;
1504
+ border: none;
1505
+ border-radius: var(--radius-sm);
1506
+ cursor: pointer;
1507
+ visibility: hidden;
1508
+ }
1509
+ .twm-slider-field__reset .material-symbols-outlined {
1510
+ font-size: 16px;
1511
+ }
1512
+ .twm-slider-field__reset:hover {
1513
+ color: var(--text-bright);
1514
+ background: var(--state-hover);
1515
+ }
1516
+ .twm-slider-field--modified .twm-slider-field__reset {
1517
+ visibility: visible;
1518
+ }
1519
+ .twm-slider-field--modified .twm-slider-field__number,
1520
+ .twm-slider-field--modified .twm-slider-field__readout {
1521
+ color: var(--accent-bright);
1522
+ }
1523
+ .twm-slider-field--off .twm-slider-field__input,
1524
+ .twm-slider-field--off .twm-slider-field__number,
1525
+ .twm-slider-field--off .twm-slider-field__readout {
1526
+ opacity: 0.45;
1527
+ }
1528
+ /* ══ end SLIDER FIELD COMPANIONS ══ */
1110
1529
  /* ===== Sortable List Component ===== */
1111
1530
  .twm-sortable-list {
1112
1531
  display: flex;
@@ -2955,49 +3374,274 @@ body.twm-window-maximized .twm-resize-handle {
2955
3374
  cursor: default;
2956
3375
  }
2957
3376
 
2958
- /* ═══════════════════════════════════════════════════════════════════════
2959
- @flexdesk/tiles — THE TILE GRID'S CHROME
2960
-
2961
- THIS STYLESHEET HAD NEVER EXISTED. `src/tiles/` was extracted from
2962
- EcoAgent's `ui/js/results/` with its JavaScript and without its CSS, so
2963
- every consumer of `@flexdesk/tiles` got a `TileGrid` that ran, dragged,
2964
- resized, saved layouts — and drew a column of unstyled `<div>`s.
2965
- `TileGrid._init()` sets `--grid-columns`, `--grid-row-height` and
2966
- `--grid-gap` on the grid element (`src/tiles/tile_grid.js`), and until
2967
- these rules landed NOTHING IN ANY STYLESHEET READ THEM: no
2968
- `display: grid`, no `grid-template-columns`, no `grid-auto-rows`. That is
2969
- the CSS-side half of this repository's signature bug — not a class the JS
2970
- sets and no rule defines, but the reverse, three custom properties written
2971
- by the JS into a void.
2972
-
2973
- THE CLASS NAMES ARE UNPREFIXED, deliberately and unusually. Everything
2974
- else in this file is `twm-`; these are not, because the names are compiled
2975
- into the JavaScript that was extracted (`className = 'tile-grid'`,
2976
- `'tile-header'`, `classList.add('dragging')` …) and renaming them upstream
2977
- would be a breaking change to every consumer for a cosmetic gain. The one
2978
- place the prefix DOES appear — `.twm-tile-error`, below — is the exception
2979
- that proves it: `showError()` emits the prefixed spelling, so the donor's
2980
- `.tile-error` rule is NOT ported here. Porting it would have written a
2981
- dead rule directly beside a live class, which is the mistake this comment
2982
- exists to stop the next reader from repeating.
2983
-
2984
- Values come from `tokens.css`, so swapping the tokens restyles the tiles
2985
- with everything else. Two colour literals survive the port and each says at
2986
- its own rule why: a light accent, for which there is no token (the nearest,
2987
- `--accent-bright`, is a border colour on this background rather than a text
2988
- one), and the error red already shipped in `.twm-tile-error`.
2989
- Ported from EcoAgent `ui/css/results_widgets.css` (the donor still owns its
2990
- own copy — it does not load this file).
2991
- ═══════════════════════════════════════════════════════════════════════ */
3377
+ /* ─── Progress: ProgressBar, ProgressList, StepProgress ──────────────
3378
+ `src/ui/components/progress.js`. Every width is the fill's inline
3379
+ `style.width`, changed in place; the states are modifier classes. */
2992
3380
 
2993
- /* ── the grid container ──────────────────────────────────────────────── */
2994
- /* The three custom properties are set by `TileGrid._init()` as inline style
2995
- on this element; the fallbacks are the constructor's defaults, so a grid
2996
- element built by hand still lays out. */
2997
- .tile-grid {
2998
- display: grid;
2999
- grid-template-columns: repeat(var(--grid-columns, 12), 1fr);
3000
- grid-auto-rows: var(--grid-row-height, 80px);
3381
+ .twm-progress {
3382
+ display: flex;
3383
+ flex-direction: column;
3384
+ gap: var(--space-xs);
3385
+ min-width: 0;
3386
+ }
3387
+ .twm-progress__head {
3388
+ display: flex;
3389
+ align-items: baseline;
3390
+ gap: var(--space-sm);
3391
+ font-size: var(--font-size-sm);
3392
+ color: var(--text-secondary);
3393
+ }
3394
+ .twm-progress__label {
3395
+ flex: 1 1 auto;
3396
+ min-width: 0;
3397
+ overflow: hidden;
3398
+ text-overflow: ellipsis;
3399
+ white-space: nowrap;
3400
+ color: var(--text-primary);
3401
+ }
3402
+ .twm-progress__readout,
3403
+ .twm-progress__elapsed {
3404
+ flex: 0 0 auto;
3405
+ font-variant-numeric: tabular-nums;
3406
+ }
3407
+ .twm-progress__elapsed {
3408
+ color: var(--text-muted);
3409
+ }
3410
+ .twm-progress__cancel {
3411
+ flex: 0 0 auto;
3412
+ background: transparent;
3413
+ border: 1px solid var(--border-default);
3414
+ border-radius: var(--radius-sm);
3415
+ color: var(--text-secondary);
3416
+ font-size: var(--font-size-xs);
3417
+ padding: 0 var(--space-sm);
3418
+ cursor: pointer;
3419
+ }
3420
+ .twm-progress__cancel:hover:not(:disabled) {
3421
+ border-color: var(--color-danger);
3422
+ color: var(--color-danger);
3423
+ }
3424
+ .twm-progress__cancel:disabled {
3425
+ opacity: 0.5;
3426
+ cursor: default;
3427
+ }
3428
+ .twm-progress__track,
3429
+ .twm-progress-list__track {
3430
+ position: relative;
3431
+ height: 6px;
3432
+ overflow: hidden;
3433
+ background: var(--surface-3);
3434
+ border-radius: var(--radius-pill);
3435
+ }
3436
+ .twm-progress__fill,
3437
+ .twm-progress-list__fill {
3438
+ height: 100%;
3439
+ width: 0;
3440
+ background: var(--accent);
3441
+ border-radius: inherit;
3442
+ transition: width var(--transition-normal);
3443
+ }
3444
+ .twm-progress--indeterminate .twm-progress__fill {
3445
+ position: absolute;
3446
+ left: 0;
3447
+ width: 30%;
3448
+ animation: twm-progress-sweep 1.2s ease-in-out infinite;
3449
+ }
3450
+ .twm-progress--done .twm-progress__fill {
3451
+ background: var(--color-success);
3452
+ }
3453
+ .twm-progress--error .twm-progress__fill {
3454
+ background: var(--color-danger);
3455
+ }
3456
+ .twm-progress--cancelled .twm-progress__fill {
3457
+ background: var(--text-dim);
3458
+ }
3459
+ @keyframes twm-progress-sweep {
3460
+ 0% { transform: translateX(-100%); }
3461
+ 100% { transform: translateX(340%); }
3462
+ }
3463
+
3464
+ .twm-progress-list {
3465
+ display: flex;
3466
+ flex-direction: column;
3467
+ gap: var(--space-sm);
3468
+ min-width: 0;
3469
+ }
3470
+ .twm-progress-list__rows {
3471
+ display: flex;
3472
+ flex-direction: column;
3473
+ gap: var(--space-xs);
3474
+ }
3475
+ .twm-progress-list__row {
3476
+ display: grid;
3477
+ grid-template-columns: auto minmax(6em, 1fr) minmax(4em, 2fr) auto;
3478
+ align-items: center;
3479
+ gap: var(--space-sm);
3480
+ font-size: var(--font-size-sm);
3481
+ color: var(--text-primary);
3482
+ }
3483
+ .twm-progress-list__name {
3484
+ overflow: hidden;
3485
+ text-overflow: ellipsis;
3486
+ white-space: nowrap;
3487
+ }
3488
+ .twm-progress-list__count {
3489
+ color: var(--text-secondary);
3490
+ font-variant-numeric: tabular-nums;
3491
+ white-space: nowrap;
3492
+ }
3493
+ .twm-progress-list__state {
3494
+ width: 8px;
3495
+ height: 8px;
3496
+ border-radius: var(--radius-circle);
3497
+ background: var(--text-dim);
3498
+ }
3499
+ .twm-progress-list__row--running .twm-progress-list__state {
3500
+ background: var(--accent);
3501
+ }
3502
+ .twm-progress-list--pulse .twm-progress-list__row--running .twm-progress-list__state {
3503
+ animation: twm-progress-pulse 1.2s ease-in-out infinite;
3504
+ }
3505
+ .twm-progress-list__row--done .twm-progress-list__state,
3506
+ .twm-progress-list__row--done .twm-progress-list__fill {
3507
+ background: var(--color-success);
3508
+ }
3509
+ .twm-progress-list__row--failed .twm-progress-list__state,
3510
+ .twm-progress-list__row--failed .twm-progress-list__fill {
3511
+ background: var(--color-danger);
3512
+ }
3513
+ .twm-progress-list__row--pending {
3514
+ color: var(--text-muted);
3515
+ }
3516
+ .twm-progress-list__empty,
3517
+ .twm-progress-list__stats {
3518
+ font-size: var(--font-size-xs);
3519
+ color: var(--text-muted);
3520
+ font-variant-numeric: tabular-nums;
3521
+ }
3522
+ @keyframes twm-progress-pulse {
3523
+ 0%, 100% { opacity: 1; }
3524
+ 50% { opacity: 0.35; }
3525
+ }
3526
+
3527
+ .twm-steps {
3528
+ display: flex;
3529
+ flex-wrap: wrap;
3530
+ gap: var(--space-xs) var(--space-lg);
3531
+ margin: 0;
3532
+ padding: 0;
3533
+ list-style: none;
3534
+ font-size: var(--font-size-sm);
3535
+ }
3536
+ .twm-steps__step {
3537
+ display: inline-flex;
3538
+ align-items: center;
3539
+ gap: var(--space-xs);
3540
+ color: var(--text-muted);
3541
+ }
3542
+ .twm-steps__marker {
3543
+ width: 8px;
3544
+ height: 8px;
3545
+ border-radius: var(--radius-circle);
3546
+ border: 1px solid currentColor;
3547
+ }
3548
+ .twm-steps__step--done {
3549
+ color: var(--text-secondary);
3550
+ }
3551
+ .twm-steps__step--done .twm-steps__marker {
3552
+ background: var(--color-success);
3553
+ border-color: var(--color-success);
3554
+ }
3555
+ .twm-steps__step--active {
3556
+ color: var(--text-primary);
3557
+ font-weight: var(--font-weight-semibold);
3558
+ }
3559
+ .twm-steps__step--active .twm-steps__marker {
3560
+ background: var(--accent);
3561
+ border-color: var(--accent);
3562
+ }
3563
+ .twm-steps__step--error {
3564
+ color: var(--color-danger);
3565
+ }
3566
+ .twm-steps__step--error .twm-steps__marker {
3567
+ background: var(--color-danger);
3568
+ border-color: var(--color-danger);
3569
+ }
3570
+ .twm-steps__pass {
3571
+ font-weight: var(--font-weight-regular);
3572
+ color: var(--text-muted);
3573
+ font-variant-numeric: tabular-nums;
3574
+ }
3575
+ @media (prefers-reduced-motion: reduce) {
3576
+ .twm-progress__fill,
3577
+ .twm-progress-list__fill { transition: none; }
3578
+ .twm-progress--indeterminate .twm-progress__fill,
3579
+ .twm-progress-list--pulse .twm-progress-list__row--running .twm-progress-list__state { animation: none; }
3580
+ }
3581
+
3582
+ /* The bar and phases inside showRunProgressDialog / showComputingWindow,
3583
+ and the message's final state. */
3584
+ .twm-job-progress__steps,
3585
+ .twm-job-progress__bar {
3586
+ align-self: stretch;
3587
+ margin: var(--space-xs) 0;
3588
+ }
3589
+ .twm-run-progress__message.is-ok,
3590
+ .twm-computing-status__message.is-ok {
3591
+ color: var(--color-success);
3592
+ }
3593
+ .twm-run-progress__message.is-err,
3594
+ .twm-computing-status__message.is-err {
3595
+ color: var(--color-danger);
3596
+ }
3597
+ .twm-run-progress__message.is-cancelled,
3598
+ .twm-computing-status__message.is-cancelled {
3599
+ color: var(--text-muted);
3600
+ }
3601
+
3602
+ /* ═══════════════════════════════════════════════════════════════════════
3603
+ @flexdesk/tiles — THE TILE GRID'S CHROME
3604
+
3605
+ THIS STYLESHEET HAD NEVER EXISTED. `src/tiles/` was extracted from
3606
+ EcoAgent's `ui/js/results/` with its JavaScript and without its CSS, so
3607
+ every consumer of `@flexdesk/tiles` got a `TileGrid` that ran, dragged,
3608
+ resized, saved layouts — and drew a column of unstyled `<div>`s.
3609
+ `TileGrid._init()` sets `--grid-columns`, `--grid-row-height` and
3610
+ `--grid-gap` on the grid element (`src/tiles/tile_grid.js`), and until
3611
+ these rules landed NOTHING IN ANY STYLESHEET READ THEM: no
3612
+ `display: grid`, no `grid-template-columns`, no `grid-auto-rows`. That is
3613
+ the CSS-side half of this repository's signature bug — not a class the JS
3614
+ sets and no rule defines, but the reverse, three custom properties written
3615
+ by the JS into a void.
3616
+
3617
+ THE CLASS NAMES ARE UNPREFIXED, deliberately and unusually. Everything
3618
+ else in this file is `twm-`; these are not, because the names are compiled
3619
+ into the JavaScript that was extracted (`className = 'tile-grid'`,
3620
+ `'tile-header'`, `classList.add('dragging')` …) and renaming them upstream
3621
+ would be a breaking change to every consumer for a cosmetic gain. The one
3622
+ place the prefix DOES appear — `.twm-tile-error`, below — is the exception
3623
+ that proves it: `showError()` emits the prefixed spelling, so the donor's
3624
+ `.tile-error` rule is NOT ported here. Porting it would have written a
3625
+ dead rule directly beside a live class, which is the mistake this comment
3626
+ exists to stop the next reader from repeating.
3627
+
3628
+ Values come from `tokens.css`, so swapping the tokens restyles the tiles
3629
+ with everything else. Two colour literals survive the port and each says at
3630
+ its own rule why: a light accent, for which there is no token (the nearest,
3631
+ `--accent-bright`, is a border colour on this background rather than a text
3632
+ one), and the error red already shipped in `.twm-tile-error`.
3633
+ Ported from EcoAgent `ui/css/results_widgets.css` (the donor still owns its
3634
+ own copy — it does not load this file).
3635
+ ═══════════════════════════════════════════════════════════════════════ */
3636
+
3637
+ /* ── the grid container ──────────────────────────────────────────────── */
3638
+ /* The three custom properties are set by `TileGrid._init()` as inline style
3639
+ on this element; the fallbacks are the constructor's defaults, so a grid
3640
+ element built by hand still lays out. */
3641
+ .tile-grid {
3642
+ display: grid;
3643
+ grid-template-columns: repeat(var(--grid-columns, 12), 1fr);
3644
+ grid-auto-rows: var(--grid-row-height, 80px);
3001
3645
  gap: var(--grid-gap, 16px);
3002
3646
  padding: var(--grid-gap, 16px) 0 0 0;
3003
3647
  /* A floor rather than a height: the rows are what size the grid. It
@@ -3489,10 +4133,12 @@ body.twm-window-maximized .twm-resize-handle {
3489
4133
  font-size: 32px;
3490
4134
  }
3491
4135
 
3492
- /* ── the chart and KPI tiles (0.13.0, src/tiles/chart_tiles.js) ──────── */
3493
- /* The figure flexes into what `.tile-content`'s column leaves; the note takes
3494
- its own line BELOW it, so the height chain above is untouched. */
3495
- .fd-chart-tile {
4136
+ /* ── the figure, chart and KPI tiles (0.13.0, src/tiles/chart_tiles.js;
4137
+ 0.17.0, src/tiles/figure_tile.js) ─────────────────────────────── */
4138
+ /* A figure tile's plot (a chart tile's too: it is one) flexes into what
4139
+ `.tile-content`'s column leaves; a chart's note takes its own line BELOW it,
4140
+ so the height chain above is untouched. */
4141
+ .fd-figure-tile {
3496
4142
  display: flex;
3497
4143
  flex-direction: column;
3498
4144
  flex: 1 1 auto;
@@ -3504,7 +4150,7 @@ body.twm-window-maximized .twm-resize-handle {
3504
4150
  /* Plotly puts `.js-plotly-plot` on the figure itself, and the rule above makes
3505
4151
  that `height: 100% !important` — which, beside a note, pushes the note out of
3506
4152
  the tile. Here the flex column sizes the figure instead. */
3507
- .tile-content .fd-chart-tile.js-plotly-plot {
4153
+ .tile-content .fd-figure-tile.js-plotly-plot {
3508
4154
  height: auto !important;
3509
4155
  }
3510
4156
 
@@ -3718,6 +4364,60 @@ body.twm-window-maximized .twm-resize-handle {
3718
4364
  .fd-scope-picker__btn--on:hover { background: var(--accent); color: #fff; }
3719
4365
  .fd-scope-picker__btn:focus-visible { outline: 1px solid var(--accent-bright); outline-offset: -2px; }
3720
4366
 
4367
+ /* ── the tile config form (0.17.0, tile_config_form.js) ──────────────────
4368
+ One row per schema field: the label, the control, the description, the
4369
+ inline error. A hidden row (visibleWhen) is `hidden`, never rebuilt. */
4370
+ .fd-tile-config {
4371
+ display: flex;
4372
+ flex-direction: column;
4373
+ gap: var(--space-sm);
4374
+ padding: var(--space-sm);
4375
+ font-size: var(--font-size-sm);
4376
+ }
4377
+ .fd-tile-config__row {
4378
+ display: flex;
4379
+ flex-direction: column;
4380
+ gap: 3px;
4381
+ min-width: 0;
4382
+ }
4383
+ .fd-tile-config__row[hidden] { display: none; }
4384
+ .fd-tile-config__row--checkbox {
4385
+ flex-direction: row;
4386
+ flex-wrap: wrap;
4387
+ align-items: center;
4388
+ gap: var(--space-xs);
4389
+ }
4390
+ .fd-tile-config__row--checkbox .fd-tile-config__help,
4391
+ .fd-tile-config__row--checkbox .fd-tile-config__error { flex-basis: 100%; }
4392
+ .fd-tile-config__label { color: var(--text-secondary); font-size: var(--font-size-xs); }
4393
+ .fd-tile-config__input,
4394
+ .fd-tile-config__select {
4395
+ width: 100%;
4396
+ box-sizing: border-box;
4397
+ padding: var(--ea-input-pad, 4px 6px);
4398
+ border: 1px solid var(--border-subtle);
4399
+ border-radius: var(--radius-sm);
4400
+ background: var(--ea-input-bg, transparent);
4401
+ color: var(--text-bright);
4402
+ font: inherit;
4403
+ }
4404
+ .fd-tile-config__input:focus,
4405
+ .fd-tile-config__select:focus { outline: none; border-color: var(--accent-bright); }
4406
+ .fd-tile-config__input:disabled,
4407
+ .fd-tile-config__select:disabled,
4408
+ .fd-tile-config__checkbox:disabled,
4409
+ .fd-tile-config__color:disabled,
4410
+ .fd-tile-config__range:disabled { opacity: 0.6; cursor: default; }
4411
+ .fd-tile-config__row--invalid .fd-tile-config__input,
4412
+ .fd-tile-config__row--invalid .fd-tile-config__select { border-color: var(--color-danger); }
4413
+ .fd-tile-config__range-wrap { display: flex; align-items: center; gap: var(--space-sm); }
4414
+ .fd-tile-config__range { flex: 1 1 auto; }
4415
+ .fd-tile-config__range-value { min-width: 3ch; color: var(--text-secondary); font-variant-numeric: tabular-nums; }
4416
+ .fd-tile-config__color { width: 48px; height: 24px; padding: 0; border: 1px solid var(--border-subtle); background: transparent; }
4417
+ .fd-tile-config__help { color: var(--text-muted); font-size: var(--font-size-xs); line-height: 1.4; }
4418
+ .fd-tile-config__error { color: var(--color-danger); font-size: var(--font-size-xs); line-height: 1.4; }
4419
+ .fd-tile-config__error[hidden] { display: none; }
4420
+
3721
4421
  /* ===================================================================
3722
4422
  REUSABLE ACTION DROPDOWN COMPONENT
3723
4423
  Used by Add Scenario, Add Widget, and other action dropdowns
@@ -3782,6 +4482,14 @@ body.twm-window-maximized .twm-resize-handle {
3782
4482
  box-sizing: border-box;
3783
4483
  transition: background var(--transition-normal), border-color var(--transition-normal);
3784
4484
  }
4485
+ /* A `{ separator: true }` entry: a rule between two groups of options. It is
4486
+ not a button, so it takes no focus and the arrow keys pass over it. */
4487
+ .twm-action-dropdown-separator {
4488
+ height: 1px;
4489
+ /* the option above already leaves var(--space-xs) below itself */
4490
+ margin: 0 0 var(--space-xs);
4491
+ background: var(--border-subtle);
4492
+ }
3785
4493
  .twm-action-dropdown-option:last-child {
3786
4494
  margin-bottom: 0;
3787
4495
  }
@@ -4574,7 +5282,36 @@ button.twm-about-dialog__btn--close:focus {
4574
5282
  }
4575
5283
  /* ─── Badges ──────────────────────────────────────────────────────────── */
4576
5284
 
4577
- /* .detail-header__badge* — moved to components/controls.css (Phase 2). */
5285
+ /* A badge beside the title (`createDetailHeader({badges})`, BUG-0015): a
5286
+ * neutral pill from tokens. A caller's own `className` sits on the same
5287
+ * element, after this rule's single class, to recolour it. */
5288
+ .twm-detail-header__badge {
5289
+ display: inline-flex;
5290
+ align-items: center;
5291
+ gap: var(--space-xs);
5292
+ padding: 2px var(--space-md);
5293
+ border-radius: var(--radius-pill);
5294
+ border: 1px solid var(--border-subtle);
5295
+ background: var(--surface-2);
5296
+ color: var(--text-muted);
5297
+ font-size: var(--font-size-xs);
5298
+ font-weight: var(--font-weight-medium);
5299
+ line-height: var(--line-height-tight);
5300
+ white-space: nowrap;
5301
+ flex-shrink: 0;
5302
+ }
5303
+ /* Compounded for the Material Symbols tie — see `.twm-leaf__icon`. */
5304
+ .twm-detail-header__badge-icon,
5305
+ .twm-detail-header__badge-icon.material-symbols-outlined {
5306
+ font-size: 14px;
5307
+ line-height: 1;
5308
+ }
5309
+
5310
+ /* ─── Buttons ─────────────────────────────────────────────────────────── */
5311
+
5312
+ /* An action is a `.twm-btn` (BUG-0015): the shared button look, with
5313
+ * 'primary' wearing `.twm-btn--primary` and 'danger' `.twm-btn--danger`;
5314
+ * 'secondary' is the plain button. Its icon is a `.twm-btn__glyph`. */
4578
5315
 
4579
5316
  /* ─── Subtitle ────────────────────────────────────────────────────────── */
4580
5317
 
@@ -4862,6 +5599,11 @@ button.twm-about-dialog__btn--close:focus {
4862
5599
  margin-left: auto;
4863
5600
  line-height: 1;
4864
5601
  }
5602
+ /* The badge's variants: createTreeItem's badge.variant ('muted' when unset) */
5603
+ .twm-tree-item__badge--muted { background: color-mix(in srgb, var(--text-dim) 15%, transparent); color: var(--text-dim); }
5604
+ .twm-tree-item__badge--pass { background: color-mix(in srgb, var(--color-success) 15%, transparent); color: var(--color-success); }
5605
+ .twm-tree-item__badge--fail { background: color-mix(in srgb, var(--color-danger) 15%, transparent); color: var(--color-danger); }
5606
+ .twm-tree-item__badge--error { background: color-mix(in srgb, var(--color-danger) 15%, transparent); color: var(--color-danger); }
4865
5607
  /* Status dot variant (smaller, no text) */
4866
5608
  .twm-tree-item__status-dot {
4867
5609
  width: 7px;
@@ -4869,7 +5611,12 @@ button.twm-about-dialog__btn--close:focus {
4869
5611
  border-radius: 50%;
4870
5612
  flex-shrink: 0;
4871
5613
  margin-left: auto;
5614
+ background: var(--text-dim);
4872
5615
  }
5616
+ .twm-tree-item__status-dot--muted { background: var(--text-dim); }
5617
+ .twm-tree-item__status-dot--pass { background: var(--color-success); }
5618
+ .twm-tree-item__status-dot--fail { background: var(--color-danger); }
5619
+ .twm-tree-item__status-dot--error { background: var(--color-danger); }
4873
5620
 
4874
5621
  /* ── from slide_out_panel.css ───────────────────────── */
4875
5622
  /* ─── SlideOutPanel ─────────────────────────────────────────────────────────
@@ -5467,37 +6214,179 @@ button.twm-about-dialog__btn--close:focus {
5467
6214
  .twm-managed-window--modal .twm-picker__btn .material-symbols-outlined {
5468
6215
  font-size: 15px;
5469
6216
  }
5470
- /* A row (or named section) hidden by openForm's `setVisible`. `[hidden]`
5471
- * carries only the UA sheet's `display: none`, which the grid and flex rules
5472
- * on `.twm-modal__row` / `.twm-modal__section` beat — so a hidden row stayed
5473
- * on screen. */
5474
- .twm-modal__row[hidden],
5475
- .twm-modal__section[hidden] {
5476
- display: none !important;
5477
- }
5478
- /* Action strip — true footer band, flush against the modal edges,
5479
- * darker chrome bg, top border + 1px highlight above. Right-aligned
5480
- * buttons, primary stays rightmost. */
5481
- .twm-managed-window--modal .twm-modal__actions {
5482
- margin-top: auto;
6217
+ /* Colour field: swatch (native picker) + hex box + optional preset strip.
6218
+ * The hex box takes the field-well styling of the text inputs above. */
6219
+ .twm-color-field {
5483
6220
  display: flex;
5484
- justify-content: flex-end;
6221
+ flex-wrap: wrap;
5485
6222
  align-items: center;
5486
- gap: 6px;
5487
- background: var(--ea-bg-chrome);
5488
- border-top: 1px solid #4a4a4a;
5489
- box-shadow: 0 -1px 0 rgba(255, 255, 255, 0.03) inset;
5490
- padding: 6px 14px;
5491
- min-height: 32px;
6223
+ gap: 4px;
6224
+ min-width: 0;
5492
6225
  }
5493
- .twm-managed-window--modal .twm-modal__actions .twm-btn {
6226
+ .twm-color-field__swatch {
6227
+ flex: 0 0 auto;
6228
+ width: 28px;
6229
+ height: 22px;
6230
+ padding: 0;
6231
+ border: 1px solid #3a3a3a;
5494
6232
  border-radius: 0;
5495
- min-height: 22px;
5496
- padding: 0 14px;
5497
- font-size: var(--ea-font-body);
5498
- letter-spacing: 0.04em;
5499
- text-transform: uppercase;
5500
- }
6233
+ background: transparent;
6234
+ cursor: pointer;
6235
+ }
6236
+ .twm-color-field__hex {
6237
+ flex: 1 1 90px;
6238
+ min-width: 0;
6239
+ font-family: 'Consolas', 'Monaco', monospace;
6240
+ }
6241
+ .twm-color-field--invalid .twm-color-field__hex {
6242
+ border-color: #b03a3a;
6243
+ }
6244
+ .twm-color-field__presets {
6245
+ flex: 1 0 100%;
6246
+ display: flex;
6247
+ flex-wrap: wrap;
6248
+ gap: 3px;
6249
+ }
6250
+ .twm-color-field__preset {
6251
+ width: 16px;
6252
+ height: 16px;
6253
+ padding: 0;
6254
+ border: 1px solid rgba(255, 255, 255, 0.25);
6255
+ border-radius: 2px;
6256
+ cursor: pointer;
6257
+ }
6258
+ .twm-color-field__preset:hover,
6259
+ .twm-color-field__preset:focus-visible {
6260
+ outline: 1px solid var(--ea-accent, #569cd6);
6261
+ outline-offset: 1px;
6262
+ }
6263
+ /* Colour list: a SortableList whose rows are a colour field + move buttons. */
6264
+ .twm-color-list {
6265
+ min-width: 0;
6266
+ }
6267
+ .twm-color-list__row {
6268
+ display: flex;
6269
+ align-items: center;
6270
+ gap: 2px;
6271
+ min-width: 0;
6272
+ }
6273
+ .twm-color-list__row > .twm-color-field {
6274
+ flex: 1 1 auto;
6275
+ }
6276
+ .twm-color-list__move {
6277
+ display: flex;
6278
+ align-items: center;
6279
+ padding: 2px;
6280
+ background: transparent;
6281
+ border: none;
6282
+ color: #8c8c8c;
6283
+ cursor: pointer;
6284
+ }
6285
+ .twm-color-list__move:hover:not(:disabled) {
6286
+ color: var(--ea-accent, #569cd6);
6287
+ }
6288
+ .twm-color-list__move:disabled {
6289
+ opacity: 0.3;
6290
+ cursor: default;
6291
+ }
6292
+ .twm-color-list__move .material-symbols-outlined {
6293
+ font-size: 15px;
6294
+ }
6295
+ /* An option that cannot be chosen, and why — drawn by choice_controls.js
6296
+ * for openForm's select, checklist and radio (and the flow kit's widgets). */
6297
+ .twm-choice--disabled { cursor: default; opacity: 0.7; }
6298
+ .twm-choice__reason {
6299
+ font-size: var(--font-size-xs);
6300
+ font-style: italic;
6301
+ color: var(--ea-text-dim);
6302
+ }
6303
+ .twm-flow-togglechip > .twm-choice__reason { margin-left: 4px; }
6304
+ /* A group's heading over its chips or radio rows (a select's is its optgroup). */
6305
+ .twm-choice__group {
6306
+ flex-basis: 100%;
6307
+ font-size: var(--font-size-xs);
6308
+ font-weight: 600;
6309
+ text-transform: uppercase;
6310
+ letter-spacing: 0.04em;
6311
+ color: var(--ea-text-dim);
6312
+ }
6313
+ .twm-choice__group:not(:first-child) { margin-top: 4px; }
6314
+ .twm-flow-togglechip > .twm-choice__reason::before { content: '— '; }
6315
+ /* A row (or named section) hidden by openForm's `setVisible`. `[hidden]`
6316
+ * carries only the UA sheet's `display: none`, which the grid and flex rules
6317
+ * on `.twm-modal__row` / `.twm-modal__section` beat — so a hidden row stayed
6318
+ * on screen. */
6319
+ .twm-modal__row[hidden],
6320
+ .twm-modal__section[hidden] {
6321
+ display: none !important;
6322
+ }
6323
+ /* A `{ row: [...] }` entry of openForm: its fields side by side, each one an
6324
+ * ordinary `.twm-modal__row` with its own label, hint and error. The fields
6325
+ * after the first size their label to its text; the first keeps the form's
6326
+ * label column, so it lines up with the rows above and below, 10px from
6327
+ * each, as any two rows are. A form narrower than 360px stacks them — the
6328
+ * form is the query container, and only a form that has a row is made one,
6329
+ * so no other form's layout moves. */
6330
+ .twm-managed-window--modal .twm-modal__form:has(.twm-modal__field-group) {
6331
+ container: twm-form / inline-size;
6332
+ }
6333
+ .twm-modal__field-group {
6334
+ display: flex;
6335
+ align-items: flex-start;
6336
+ column-gap: 10px;
6337
+ }
6338
+ .twm-modal__field-group > .twm-modal__row {
6339
+ flex: 1 1 0;
6340
+ min-width: 0;
6341
+ }
6342
+ .twm-managed-window--modal .twm-modal__fields > .twm-modal__field-group {
6343
+ margin-left: 14px;
6344
+ margin-right: 14px;
6345
+ }
6346
+ .twm-managed-window--modal .twm-modal__fields > .twm-modal__field-group:first-child {
6347
+ margin-top: 14px;
6348
+ }
6349
+ /* A field after a leading row is still its label's first-of-type, so the
6350
+ * leading-row margin above would land on it a second time: it keeps the
6351
+ * normal 10px under the row (the row's own bottom margin) instead. */
6352
+ .twm-managed-window--modal .twm-modal__fields > .twm-modal__field-group ~ .twm-modal__row:first-of-type {
6353
+ margin-top: 0;
6354
+ }
6355
+ .twm-managed-window--modal .twm-modal__field-group > .twm-modal__row + .twm-modal__row {
6356
+ grid-template-columns: auto 1fr;
6357
+ }
6358
+ @container twm-form (max-width: 359px) {
6359
+ .twm-modal__field-group {
6360
+ flex-direction: column;
6361
+ align-items: stretch;
6362
+ }
6363
+ .twm-managed-window--modal .twm-modal__field-group > .twm-modal__row + .twm-modal__row {
6364
+ grid-template-columns: 130px 1fr;
6365
+ }
6366
+ }
6367
+ /* Action strip — true footer band, flush against the modal edges,
6368
+ * darker chrome bg, top border + 1px highlight above. Right-aligned
6369
+ * buttons, primary stays rightmost. */
6370
+ .twm-managed-window--modal .twm-modal__actions {
6371
+ margin-top: auto;
6372
+ display: flex;
6373
+ justify-content: flex-end;
6374
+ align-items: center;
6375
+ gap: 6px;
6376
+ background: var(--ea-bg-chrome);
6377
+ border-top: 1px solid #4a4a4a;
6378
+ box-shadow: 0 -1px 0 rgba(255, 255, 255, 0.03) inset;
6379
+ padding: 6px 14px;
6380
+ min-height: 32px;
6381
+ }
6382
+ .twm-managed-window--modal .twm-modal__actions .twm-btn {
6383
+ border-radius: 0;
6384
+ min-height: 22px;
6385
+ padding: 0 14px;
6386
+ font-size: var(--ea-font-body);
6387
+ letter-spacing: 0.04em;
6388
+ text-transform: uppercase;
6389
+ }
5501
6390
  .twm-managed-window--modal .twm-modal__actions .twm-btn--primary {
5502
6391
  background: var(--ea-accent);
5503
6392
  border-color: var(--ea-accent);
@@ -5526,6 +6415,12 @@ button.twm-about-dialog__btn--close:focus {
5526
6415
  min-height: 0;
5527
6416
  overflow-y: auto;
5528
6417
  }
6418
+ /* openConfirm's message is text (BUG-0014): `\n` breaks the line, and a long
6419
+ * unbroken name wraps instead of widening the dialog. */
6420
+ .twm-managed-window--modal .twm-modal__body--text {
6421
+ white-space: pre-line;
6422
+ overflow-wrap: anywhere;
6423
+ }
5529
6424
  /* Form-level error banner — left accent stripe, dim red panel. */
5530
6425
  .twm-managed-window--modal .twm-modal__error {
5531
6426
  margin-top: 8px;
@@ -6156,7 +7051,7 @@ body.twm-dragging * { user-select: none; }
6156
7051
  * size (24) draws strokes too heavy to read as a small control. */
6157
7052
  font-variation-settings: 'opsz' 20, 'wght' 400;
6158
7053
  }
6159
- .twm-leaf__btn:hover:not(:disabled) { background: var(--state-hover); color: var(--text-primary); }
7054
+ .twm-leaf__btn:hover:not(:disabled):not(.twm-leaf__btn--disabled) { background: var(--state-hover); color: var(--text-primary); }
6160
7055
  /* C20. A verb that applies to this kind of pane but not right now — greyed and
6161
7056
  * explained by its tooltip, rather than removed. `:disabled` alone would still
6162
7057
  * take the hover highlight above, which reads as clickable. */
@@ -6165,6 +7060,34 @@ body.twm-dragging * { user-select: none; }
6165
7060
  opacity: 0.35;
6166
7061
  cursor: default;
6167
7062
  }
7063
+
7064
+ /* C42. A content action that is a toggle, and on: `aria-pressed="true"`. The
7065
+ * selected tint the rest of the shell uses, so "on" reads the same everywhere. */
7066
+ .twm-leaf__btn--pressed {
7067
+ background: var(--state-selected);
7068
+ color: var(--text-primary);
7069
+ }
7070
+
7071
+ /* C42. A content action with a text label: the 18px square is for a glyph, so
7072
+ * a labelled button grows to its text. */
7073
+ .twm-leaf__btn--labelled {
7074
+ width: auto;
7075
+ gap: 2px;
7076
+ padding: 0 4px;
7077
+ }
7078
+ .twm-leaf__btn-label {
7079
+ font-size: 11px;
7080
+ line-height: 1;
7081
+ white-space: nowrap;
7082
+ }
7083
+
7084
+ /* C42. `{separator: true}` between groups of content actions. */
7085
+ .twm-leaf__sep {
7086
+ align-self: stretch;
7087
+ width: 1px;
7088
+ margin: 3px 2px;
7089
+ background: var(--border-subtle);
7090
+ }
6168
7091
  .twm-leaf__body {
6169
7092
  flex: 1 1 auto;
6170
7093
  overflow: auto;
@@ -6709,6 +7632,199 @@ body.twm-dragging * { user-select: none; }
6709
7632
  margin: var(--space-xxs) 0;
6710
7633
  background: var(--border-subtle);
6711
7634
  }
7635
+
7636
+ /* ══ EDITOR · PANES, SPLIT AND SEARCH (BUG-0025) ═════════════════════
7637
+ *
7638
+ * The same family as the tab bar above: `SplitPaneContainer`, `EditorPane`
7639
+ * and `NotebookSearchBar` set these classes and no FlexDesk stylesheet had a
7640
+ * rule for any of them, so with only FlexDesk CSS a split stacked its panes,
7641
+ * the divider was a zero-width div nobody could grab, and the search bar was
7642
+ * a row of bare inputs and icon names.
7643
+ *
7644
+ * `.split-pane-container*`, `.split-pane`, `.split-pane-divider*`
7645
+ * `editor/split_pane_container.js`
7646
+ * `.editor-pane*`, `.notebook-editor-container`
7647
+ * `editor/editor_pane.js`
7648
+ * `.nb-search-bar*` `editor/notebook_search.js`
7649
+ *
7650
+ * Layout first: the container is a row, each pane a column, and the divider
7651
+ * a fixed-width strip that never shrinks. The pane's flex is also written
7652
+ * inline by `SplitPaneContainer` (the ratio); the rule is the fallback. */
7653
+ .split-pane-container {
7654
+ display: flex;
7655
+ flex-direction: row;
7656
+ flex: 1 1 0;
7657
+ min-width: 0;
7658
+ min-height: 0;
7659
+ overflow: hidden;
7660
+ }
7661
+ .split-pane {
7662
+ display: flex;
7663
+ flex-direction: column;
7664
+ flex: 1 1 0;
7665
+ min-width: 0;
7666
+ min-height: 0;
7667
+ overflow: hidden;
7668
+ }
7669
+ /* 4px drawn, 10px grabbed: the `::before` widens the hit target 3px each side
7670
+ * without moving either pane. */
7671
+ .split-pane-divider {
7672
+ position: relative;
7673
+ z-index: 1;
7674
+ flex: 0 0 4px;
7675
+ width: 4px;
7676
+ background: var(--border-strong);
7677
+ cursor: col-resize;
7678
+ transition: background 0.15s;
7679
+ }
7680
+ .split-pane-divider::before {
7681
+ content: '';
7682
+ position: absolute;
7683
+ inset: 0 -3px;
7684
+ }
7685
+ .split-pane-divider:hover,
7686
+ .split-pane-divider--active {
7687
+ background: var(--accent-bright, #1683c4);
7688
+ }
7689
+ /* A pane is its tab bar over its body. The tab bar's own rule says
7690
+ * `height: 100%` (it fills a tile's strip); here it is a fixed 26px row, the
7691
+ * strip's height, and the body takes the rest. */
7692
+ .editor-pane {
7693
+ display: flex;
7694
+ flex-direction: column;
7695
+ flex: 1 1 0;
7696
+ min-width: 0;
7697
+ min-height: 0;
7698
+ overflow: hidden;
7699
+ }
7700
+ .editor-pane > .tabs.notebook-tabs {
7701
+ flex: 0 0 auto;
7702
+ height: 26px;
7703
+ }
7704
+ /* Which of two panes has focus — only drawn when there ARE two. An inset
7705
+ * shadow rather than a border, so focusing a pane does not move its body.
7706
+ * This and the tab-bar row above need `.editor-pane` to survive
7707
+ * `setPanes()`, which is why `SplitPaneContainer` ADDS `.split-pane` to a
7708
+ * pane instead of replacing its classes with it. */
7709
+ .split-pane-container--split .editor-pane--focused > .tabs.notebook-tabs {
7710
+ box-shadow: inset 0 -1px 0 var(--accent-bright, #1683c4);
7711
+ }
7712
+ /* The body is the positioning anchor; the editor container fills it
7713
+ * absolutely, so its height is the body's whatever its content is and the
7714
+ * scrollbar is its own. */
7715
+ .editor-pane__body {
7716
+ position: relative;
7717
+ flex: 1 1 0;
7718
+ min-height: 0;
7719
+ overflow: hidden;
7720
+ }
7721
+ .notebook-editor-container {
7722
+ position: absolute;
7723
+ inset: 0;
7724
+ overflow-x: hidden;
7725
+ overflow-y: auto;
7726
+ background: var(--surface-0);
7727
+ outline: none;
7728
+ }
7729
+ /* The search bar: mounted at the top of the editor container and sticky
7730
+ * there, hidden until `open()` adds `--open`. */
7731
+ .nb-search-bar {
7732
+ position: sticky;
7733
+ top: 0;
7734
+ z-index: 50;
7735
+ display: none;
7736
+ flex-direction: column;
7737
+ gap: var(--space-xs);
7738
+ padding: var(--space-sm) var(--space-md);
7739
+ background: var(--surface-1);
7740
+ border-bottom: 1px solid var(--border-strong);
7741
+ box-shadow: var(--shadow-md);
7742
+ color: var(--text-primary);
7743
+ font-size: var(--font-size-sm);
7744
+ }
7745
+ .nb-search-bar--open { display: flex; }
7746
+ .nb-search-bar__find-row,
7747
+ .nb-search-bar__replace-row {
7748
+ display: flex;
7749
+ align-items: center;
7750
+ gap: var(--space-xs);
7751
+ }
7752
+ /* Lines the replace input up under the find input: the toggle's width. */
7753
+ .nb-search-bar__spacer {
7754
+ flex: 0 0 26px;
7755
+ width: 26px;
7756
+ }
7757
+ .nb-search-bar__input-wrap {
7758
+ flex: 1 1 auto;
7759
+ min-width: 160px;
7760
+ max-width: 320px;
7761
+ }
7762
+ .nb-search-bar__input {
7763
+ box-sizing: border-box;
7764
+ width: 100%;
7765
+ padding: 3px var(--space-sm);
7766
+ background: var(--surface-3);
7767
+ border: 1px solid var(--border-strong);
7768
+ border-radius: var(--radius-sm);
7769
+ color: var(--text-primary);
7770
+ font: inherit;
7771
+ outline: none;
7772
+ }
7773
+ .nb-search-bar__input:focus { border-color: var(--accent-bright, #1683c4); }
7774
+ .nb-search-bar__match-info {
7775
+ min-width: 64px;
7776
+ color: var(--text-muted);
7777
+ font-size: var(--font-size-xs);
7778
+ text-align: center;
7779
+ white-space: nowrap;
7780
+ user-select: none;
7781
+ }
7782
+ .nb-search-bar__match-info--no-results { color: var(--color-danger, #c74e4e); }
7783
+ .nb-search-bar__btn {
7784
+ display: inline-flex;
7785
+ flex: 0 0 auto;
7786
+ align-items: center;
7787
+ justify-content: center;
7788
+ width: 26px;
7789
+ height: 26px;
7790
+ padding: 0;
7791
+ background: transparent;
7792
+ border: none;
7793
+ border-radius: var(--radius-sm);
7794
+ color: var(--text-secondary);
7795
+ cursor: pointer;
7796
+ font-size: var(--font-size-sm);
7797
+ font-weight: 600;
7798
+ }
7799
+ .nb-search-bar__btn:hover { background: var(--state-hover); }
7800
+ .nb-search-bar__btn:active { background: var(--state-active); }
7801
+ /* The case / word / regex toggles reserve the border `--active` colours. */
7802
+ .nb-search-bar__btn--toggle { border: 1px solid transparent; }
7803
+ .nb-search-bar__btn--active,
7804
+ .nb-search-bar__btn--active:hover {
7805
+ background: var(--state-selected);
7806
+ border-color: var(--accent-bright, #1683c4);
7807
+ color: var(--text-bright);
7808
+ }
7809
+ /* Two classes, for the Material Symbols tie — see `.twm-leaf__icon`. */
7810
+ .nb-search-bar__btn .material-symbols-outlined {
7811
+ font-size: 18px;
7812
+ line-height: 1;
7813
+ }
7814
+ .nb-search-bar__icon-word {
7815
+ font-size: var(--font-size-md);
7816
+ font-weight: 700;
7817
+ text-decoration: underline;
7818
+ text-underline-offset: 2px;
7819
+ }
7820
+ .nb-search-bar__separator {
7821
+ flex: 0 0 1px;
7822
+ width: 1px;
7823
+ height: 18px;
7824
+ margin: 0 2px;
7825
+ background: var(--border-strong);
7826
+ }
7827
+ /* ══ end EDITOR · PANES, SPLIT AND SEARCH ══ */
6712
7828
  /* ── Tile tab menu (hamburger pop-out) ────────────────────────────
6713
7829
  * Anchored above the hamburger button. Lists every entity that the
6714
7830
  * tile's home page would show, grouped by source, with search +
@@ -7048,6 +8164,14 @@ body.twm-dragging * { user-select: none; }
7048
8164
  border-color: var(--accent-bright);
7049
8165
  color: var(--text-bright);
7050
8166
  }
8167
+ .twm-chip[aria-disabled="true"] {
8168
+ color: var(--text-dim);
8169
+ cursor: not-allowed;
8170
+ }
8171
+ .twm-chip[aria-disabled="true"]:hover {
8172
+ background: var(--surface-2);
8173
+ color: var(--text-dim);
8174
+ }
7051
8175
  .twm-cmdpal__list {
7052
8176
  max-height: 40vh;
7053
8177
  overflow: auto;
@@ -7196,6 +8320,8 @@ body.twm-dragging * { user-select: none; }
7196
8320
  }
7197
8321
  .twm-cmdpal__acts { display: none; gap: 10px; }
7198
8322
  .twm-cmdpal__item--active .twm-cmdpal__acts { display: flex; }
8323
+ /* A copy verb a single kind refuses: drawn, greyed, with the reason as its title. */
8324
+ .twm-cmdpal__act--disabled { opacity: 0.45; }
7199
8325
  .twm-cmdpal__item--active .twm-cmdpal__kind,
7200
8326
  .twm-cmdpal__item--active .twm-cmdpal__shortcut { display: none; }
7201
8327
  .twm-cmdpal__item--more { color: var(--text-muted); font-size: var(--font-size-sm); }
@@ -8006,17 +9132,125 @@ tr:hover .twm-row-action { opacity: 1; }
8006
9132
  * status line sits at the foot. Only a pane holding the Alerts pane: other
8007
9133
  * panes keep their padding and their own height. */
8008
9134
  .twm-bp-pane:has(> .twm-alerts) { height: 100%; padding: 0; box-sizing: border-box; }
8009
- .twm-alerts .material-symbols-outlined { font-size: 15px; }
8010
- .twm-alerts__spacer { flex: 1 1 auto; }
8011
- .twm-alerts__rules { flex: 0 0 280px; display: flex; flex-direction: column; min-width: 0; border-right: 1px solid var(--border-subtle); }
8012
- .twm-alerts__head, .twm-alerts__bar { flex: none; display: flex; align-items: center; gap: 8px; min-height: 34px; padding: 0 12px; border-bottom: 1px solid var(--border-subtle); }
8013
- .twm-alerts__bar { flex-wrap: wrap; }
8014
- .twm-alerts__caps { color: var(--text-muted); font-size: 10.5px; letter-spacing: .08em; }
8015
- .twm-alerts__link { padding: 0; border: 0; background: none; color: var(--accent-bright); font: inherit; cursor: pointer; }
8016
- .twm-alerts__link:hover { text-decoration: underline; }
8017
- .twm-alerts__rulelist { flex: 1 1 auto; overflow-y: auto; padding: 4px 0; }
8018
- .twm-alerts__rulebtn { width: 100%; display: flex; align-items: center; gap: 10px; height: 30px; padding: 0 12px; border: 0; background: none; color: var(--text-body); font: inherit; cursor: pointer; }
8019
- .twm-alerts__rulebtn:hover { background: var(--state-hover); }
9135
+ /* The public tab strip (`createTabs`, STORY-0048). It SCROLLS, never wraps or
9136
+ * shrinks its tabs, and hides the scrollbar; the wheel and the reveal of the
9137
+ * tab in front are `strip_scroll.js`'s. `--compact` is the dense variant. The
9138
+ * bottom panel draws the same component under its own `.twm-bp` block. */
9139
+ .twm-tabs {
9140
+ display: flex;
9141
+ flex-direction: column;
9142
+ min-width: 0;
9143
+ min-height: 0;
9144
+ background: var(--surface-0);
9145
+ color: var(--text-body);
9146
+ }
9147
+ .twm-tabs__tabs {
9148
+ flex: 0 0 auto;
9149
+ display: flex;
9150
+ align-items: stretch;
9151
+ height: 30px;
9152
+ min-width: 0;
9153
+ background: var(--surface-1);
9154
+ border-bottom: 1px solid var(--border-subtle);
9155
+ overflow-x: auto;
9156
+ overflow-y: hidden;
9157
+ scrollbar-width: none;
9158
+ white-space: nowrap;
9159
+ user-select: none;
9160
+ }
9161
+ .twm-tabs__tabs::-webkit-scrollbar { width: 0; height: 0; display: none; }
9162
+ .twm-tabs__tab {
9163
+ position: relative;
9164
+ flex: 0 0 auto;
9165
+ display: inline-flex;
9166
+ align-items: center;
9167
+ gap: var(--space-xs);
9168
+ max-width: 220px;
9169
+ padding: 0 var(--space-sm);
9170
+ background: transparent;
9171
+ color: var(--text-muted);
9172
+ border: none;
9173
+ border-right: 1px solid var(--border-subtle);
9174
+ font: inherit;
9175
+ font-size: var(--font-size-xs);
9176
+ line-height: 1;
9177
+ cursor: pointer;
9178
+ }
9179
+ .twm-tabs__tab:hover { background: var(--surface-2); color: var(--text-primary); }
9180
+ .twm-tabs__tab:focus-visible { outline: 1px solid var(--accent-bright, #1683c4); outline-offset: -1px; }
9181
+ .twm-tabs__tab--on { background: var(--surface-0); color: var(--text-bright); }
9182
+ .twm-tabs__tab--on::before {
9183
+ content: '';
9184
+ position: absolute;
9185
+ inset: 0 0 auto 0;
9186
+ height: 2px;
9187
+ pointer-events: none;
9188
+ background: var(--accent-bright, #1683c4);
9189
+ }
9190
+ .twm-tabs__label { overflow: hidden; text-overflow: ellipsis; }
9191
+ /* Two classes: Google's `.material-symbols-outlined { font-size: 24px }` loads
9192
+ * after this sheet and would win a one-class tie. */
9193
+ .twm-tabs__icon,
9194
+ .twm-tabs__icon.material-symbols-outlined { flex: 0 0 auto; font-size: 14px; line-height: 1; }
9195
+ .twm-tabs__badge {
9196
+ display: inline-flex;
9197
+ align-items: center;
9198
+ justify-content: center;
9199
+ min-width: 16px;
9200
+ height: 16px;
9201
+ padding: 0 4px;
9202
+ box-sizing: border-box;
9203
+ border-radius: 8px;
9204
+ background: var(--accent);
9205
+ color: #fff;
9206
+ font-size: 10.5px;
9207
+ line-height: 16px;
9208
+ }
9209
+ .twm-tabs__badge--info { background: color-mix(in srgb, var(--color-info) 35%, var(--surface-0)); }
9210
+ .twm-tabs__badge--warning { background: color-mix(in srgb, var(--color-warning) 35%, var(--surface-0)); }
9211
+ .twm-tabs__badge--alert { background: color-mix(in srgb, var(--color-danger) 45%, var(--surface-0)); }
9212
+ .twm-tabs__indicator {
9213
+ flex: 0 0 auto;
9214
+ width: 6px;
9215
+ height: 6px;
9216
+ border-radius: 50%;
9217
+ background: var(--text-muted);
9218
+ }
9219
+ .twm-tabs__indicator--info { background: var(--color-info); }
9220
+ .twm-tabs__indicator--warning { background: var(--color-warning); }
9221
+ .twm-tabs__indicator--alert { background: var(--color-danger); }
9222
+ .twm-tabs__dirty { font-size: 10px; color: var(--text-primary); }
9223
+ .twm-tabs__close {
9224
+ padding: 0 2px;
9225
+ border-radius: var(--radius-sm);
9226
+ opacity: 0.6;
9227
+ font-size: 13px;
9228
+ }
9229
+ .twm-tabs__close:hover { opacity: 1; background: var(--surface-2); }
9230
+ /* A dirty, closable tab shows the dot until hovered, then the close mark.
9231
+ A dirty tab with no close mark keeps its dot. */
9232
+ .twm-tabs__tab--dirty:not(:hover) .twm-tabs__close { display: none; }
9233
+ .twm-tabs__tab--dirty.twm-tabs__tab--closable:hover .twm-tabs__dirty { display: none; }
9234
+ .twm-tabs__badge[hidden],
9235
+ .twm-tabs__indicator[hidden] { display: none; }
9236
+ .twm-tabs__body { flex: 1 1 auto; min-height: 0; overflow: auto; }
9237
+ .twm-tabs-pane { height: 100%; box-sizing: border-box; }
9238
+ .twm-tabs-pane[hidden] { display: none; }
9239
+ .twm-tabs--compact .twm-tabs__tabs { height: 22px; }
9240
+ .twm-tabs--compact .twm-tabs__tab { padding: 0 var(--space-xs); gap: 3px; }
9241
+ .twm-tabs--compact .twm-tabs__icon,
9242
+ .twm-tabs--compact .twm-tabs__icon.material-symbols-outlined { font-size: 12px; }
9243
+ .twm-alerts .material-symbols-outlined { font-size: 15px; }
9244
+ .twm-alerts__spacer { flex: 1 1 auto; }
9245
+ .twm-alerts__rules { flex: 0 0 280px; display: flex; flex-direction: column; min-width: 0; border-right: 1px solid var(--border-subtle); }
9246
+ .twm-alerts__head, .twm-alerts__bar { flex: none; display: flex; align-items: center; gap: 8px; min-height: 34px; padding: 0 12px; border-bottom: 1px solid var(--border-subtle); }
9247
+ .twm-alerts__bar { flex-wrap: wrap; }
9248
+ .twm-alerts__caps { color: var(--text-muted); font-size: 10.5px; letter-spacing: .08em; }
9249
+ .twm-alerts__link { padding: 0; border: 0; background: none; color: var(--accent-bright); font: inherit; cursor: pointer; }
9250
+ .twm-alerts__link:hover { text-decoration: underline; }
9251
+ .twm-alerts__rulelist { flex: 1 1 auto; overflow-y: auto; padding: 4px 0; }
9252
+ .twm-alerts__rulebtn { width: 100%; display: flex; align-items: center; gap: 10px; height: 30px; padding: 0 12px; border: 0; background: none; color: var(--text-body); font: inherit; cursor: pointer; }
9253
+ .twm-alerts__rulebtn:hover { background: var(--state-hover); }
8020
9254
  .twm-alerts__rulebtn--on { background: var(--state-selected); box-shadow: inset 2px 0 0 var(--accent-bright); }
8021
9255
  .twm-alerts__rulebtn--off { color: var(--text-dim); }
8022
9256
  .twm-alerts__dot { flex: none; width: 8px; height: 8px; border-radius: 50%; }
@@ -8426,78 +9660,1565 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
8426
9660
  outline: 1px solid var(--accent-bright, #007acc);
8427
9661
  outline-offset: -1px;
8428
9662
  }
8429
- .twm-record-list__empty {
8430
- margin: 0;
8431
- padding: 12px 6px;
8432
- color: var(--text-muted, #888);
8433
- font-size: var(--ea-font-body, 11px);
9663
+ .twm-record-list__empty {
9664
+ margin: 0;
9665
+ padding: 12px 6px;
9666
+ color: var(--text-muted, #888);
9667
+ font-size: var(--ea-font-body, 11px);
9668
+ }
9669
+
9670
+ /* ── the fact sheet ── */
9671
+ .twm-facts {
9672
+ width: 100%;
9673
+ border-collapse: collapse;
9674
+ table-layout: auto;
9675
+ font-size: var(--font-size-sm, 12px);
9676
+ }
9677
+ .twm-facts thead th {
9678
+ text-align: left;
9679
+ font-size: var(--ea-font-small, 10px);
9680
+ font-weight: 600;
9681
+ letter-spacing: 0.06em;
9682
+ text-transform: uppercase;
9683
+ color: var(--text-dim, #666);
9684
+ white-space: nowrap;
9685
+ border-bottom: 1px solid var(--border-default, #303030);
9686
+ padding: 0 12px 6px 0;
9687
+ }
9688
+ /* Every value WRAPS and keeps its own line breaks: nothing is cut off. */
9689
+ .twm-facts tbody td,
9690
+ .twm-facts tbody th {
9691
+ text-align: left;
9692
+ font-weight: normal;
9693
+ border-bottom: 1px solid var(--border-subtle, #2a2a2a);
9694
+ padding: 6px 12px 6px 0;
9695
+ color: var(--text-primary, #e0e0e0);
9696
+ vertical-align: top;
9697
+ white-space: pre-wrap;
9698
+ overflow-wrap: anywhere;
9699
+ }
9700
+ .twm-facts tbody tr:last-child > td,
9701
+ .twm-facts tbody tr:last-child > th { border-bottom: 0; }
9702
+ .twm-facts tbody th.twm-facts__label {
9703
+ color: var(--text-muted, #888);
9704
+ white-space: nowrap;
9705
+ }
9706
+ .twm-facts.twm-facts--wrap-labels tbody th.twm-facts__label { white-space: normal; }
9707
+ /* `wrap: 'words'` — a value wraps at spaces only, and a long word stays
9708
+ * whole: the sheet grows wider and a sideways scroller around it scrolls.
9709
+ * `wrap: 'none'` — a value keeps its own line breaks and no others. */
9710
+ .twm-facts.twm-facts--wrap-words tbody td,
9711
+ .twm-facts.twm-facts--wrap-words tbody th { overflow-wrap: normal; word-break: normal; }
9712
+ .twm-facts.twm-facts--wrap-none tbody td,
9713
+ .twm-facts.twm-facts--wrap-none tbody th { white-space: pre; overflow-wrap: normal; word-break: normal; }
9714
+ .twm-facts.twm-facts--mono tbody td.twm-facts__value,
9715
+ .twm-facts tbody td.twm-facts__mono { font-family: var(--font-family-mono, monospace); font-size: 0.95em; }
9716
+ .twm-facts .twm-facts__num { font-variant-numeric: tabular-nums; white-space: nowrap; }
9717
+ .twm-facts tbody td.twm-facts__null,
9718
+ .twm-facts tbody td.twm-facts__missing { color: var(--text-dim, #666); font-style: italic; }
9719
+ .twm-facts tbody td.twm-facts__arrow {
9720
+ color: var(--text-dim, #666);
9721
+ white-space: nowrap;
9722
+ width: 1%;
9723
+ }
9724
+ .twm-facts tbody tr.twm-facts__row--changed > td.twm-facts__before { color: var(--text-muted, #888); }
9725
+ .twm-facts tbody tr.twm-facts__row--changed > td.twm-facts__arrow { color: var(--color-warning, #ff9800); }
9726
+ .twm-facts tbody tr.twm-facts__row--same > td { color: var(--text-muted, #888); }
9727
+ .twm-facts tbody tr.twm-facts__row--added > td.twm-facts__after { color: var(--color-success, #3fb950); }
9728
+ .twm-facts tbody tr.twm-facts__row--removed > td.twm-facts__before { color: var(--color-danger, #c74e4e); }
9729
+ .twm-facts tbody td.twm-facts__empty {
9730
+ padding: 12px 0;
9731
+ color: var(--text-muted, #888);
9732
+ white-space: normal;
9733
+ }
9734
+ /* ══ end RECORD LIST & FACT SHEET ══ */
9735
+
9736
+ /* ══ HELP MODAL ══════════════════════════════════════════════════════
9737
+ *
9738
+ * `HelpModal` (src/help/help_modal.js): a centred dialog on a dimmed overlay,
9739
+ * a sidebar of categories, a search box and the content pane, which shows the
9740
+ * index, a topic, search results or an error. Its classes are `help-`, not
9741
+ * `twm-`, prefixed — they are older than this sheet, and an embedder styles
9742
+ * them by those names. `.help-article` holds the provider's rendered HTML, so
9743
+ * it styles plain elements. Every class the modal sets has a rule here, and
9744
+ * every class a rule here names is one it sets (tests/help_modal_css.test.mjs). */
9745
+ .help-modal-overlay {
9746
+ position: fixed;
9747
+ inset: 0;
9748
+ display: flex;
9749
+ align-items: center;
9750
+ justify-content: center;
9751
+ background: rgba(0, 0, 0, 0.7);
9752
+ backdrop-filter: blur(4px);
9753
+ z-index: var(--z-over-windows);
9754
+ }
9755
+ .help-modal {
9756
+ display: flex;
9757
+ flex-direction: column;
9758
+ width: 90%;
9759
+ max-width: 1000px;
9760
+ height: 80vh;
9761
+ max-height: 700px;
9762
+ background: var(--surface-0);
9763
+ color: var(--text-body);
9764
+ border: 1px solid var(--surface-3);
9765
+ border-radius: var(--radius-lg);
9766
+ box-shadow: var(--shadow-lg);
9767
+ overflow: hidden;
9768
+ user-select: text;
9769
+ }
9770
+ .help-modal__header {
9771
+ display: flex;
9772
+ align-items: center;
9773
+ justify-content: space-between;
9774
+ padding: var(--space-lg) var(--space-xl);
9775
+ background: var(--surface-1);
9776
+ border-bottom: 1px solid var(--surface-3);
9777
+ }
9778
+ .help-modal__title {
9779
+ display: flex;
9780
+ align-items: center;
9781
+ gap: var(--space-md);
9782
+ font-size: var(--font-size-xl);
9783
+ font-weight: var(--font-weight-semibold);
9784
+ color: var(--text-primary);
9785
+ }
9786
+ .help-modal__title .material-symbols-outlined {
9787
+ font-size: 20px;
9788
+ color: var(--accent-bright);
9789
+ }
9790
+ .help-modal__close {
9791
+ display: flex;
9792
+ align-items: center;
9793
+ justify-content: center;
9794
+ padding: var(--space-xs);
9795
+ background: transparent;
9796
+ border: none;
9797
+ border-radius: var(--radius-sm);
9798
+ color: var(--text-muted);
9799
+ cursor: pointer;
9800
+ }
9801
+ .help-modal__close:hover {
9802
+ background: var(--state-active);
9803
+ color: var(--text-primary);
9804
+ }
9805
+ .help-modal__search {
9806
+ display: flex;
9807
+ align-items: center;
9808
+ gap: var(--space-md);
9809
+ padding: var(--space-md) var(--space-xl);
9810
+ background: var(--surface-1);
9811
+ border-bottom: 1px solid var(--surface-3);
9812
+ }
9813
+ .help-modal__search .material-symbols-outlined {
9814
+ font-size: 18px;
9815
+ color: var(--text-dim);
9816
+ }
9817
+ .help-search-input {
9818
+ flex: 1;
9819
+ padding: var(--space-sm) 10px;
9820
+ background: var(--surface-0);
9821
+ border: 1px solid var(--surface-3);
9822
+ border-radius: var(--radius-sm);
9823
+ color: var(--text-body);
9824
+ font-size: var(--font-size-md);
9825
+ outline: none;
9826
+ }
9827
+ .help-search-input:focus { border-color: var(--accent-bright); }
9828
+ .help-search-input::placeholder { color: var(--text-dim); }
9829
+ .help-modal__body {
9830
+ display: flex;
9831
+ flex: 1;
9832
+ min-height: 0;
9833
+ overflow: hidden;
9834
+ }
9835
+ .help-modal__sidebar {
9836
+ flex: 0 0 220px;
9837
+ padding: var(--space-md) 0;
9838
+ background: var(--surface-1);
9839
+ border-right: 1px solid var(--surface-3);
9840
+ overflow-y: auto;
9841
+ }
9842
+ .help-sidebar__categories {
9843
+ display: flex;
9844
+ flex-direction: column;
9845
+ }
9846
+ .help-category { margin-bottom: var(--space-xs); }
9847
+ .help-category__header {
9848
+ display: flex;
9849
+ align-items: center;
9850
+ gap: var(--space-md);
9851
+ padding: var(--space-md) var(--space-lg);
9852
+ color: var(--text-muted);
9853
+ font-size: var(--font-size-sm);
9854
+ font-weight: var(--font-weight-semibold);
9855
+ text-transform: uppercase;
9856
+ letter-spacing: 0.5px;
9857
+ cursor: pointer;
9858
+ user-select: none;
9859
+ }
9860
+ .help-category__header:hover { color: var(--text-secondary); }
9861
+ .help-category__header .material-symbols-outlined { font-size: 16px; }
9862
+ .help-category__topics {
9863
+ list-style: none;
9864
+ margin: 0;
9865
+ padding: 0;
9866
+ }
9867
+ .help-category.collapsed .help-category__topics { display: none; }
9868
+ .help-topic-item {
9869
+ padding: var(--space-sm) var(--space-lg) var(--space-sm) 36px;
9870
+ border-left: 2px solid transparent;
9871
+ color: var(--text-secondary);
9872
+ font-size: var(--font-size-md);
9873
+ cursor: pointer;
9874
+ }
9875
+ .help-topic-item:hover {
9876
+ background: var(--state-hover);
9877
+ color: var(--text-primary);
9878
+ }
9879
+ .help-topic-item.active {
9880
+ background: var(--state-selected);
9881
+ border-left-color: var(--accent-bright);
9882
+ color: var(--text-bright);
9883
+ }
9884
+ .help-modal__content {
9885
+ flex: 1;
9886
+ min-width: 0;
9887
+ padding: 20px var(--space-2xl);
9888
+ background: var(--surface-0);
9889
+ overflow-y: auto;
9890
+ }
9891
+ .help-content__loading,
9892
+ .help-content__error {
9893
+ display: flex;
9894
+ flex-direction: column;
9895
+ align-items: center;
9896
+ justify-content: center;
9897
+ gap: var(--space-lg);
9898
+ height: 200px;
9899
+ color: var(--text-muted);
9900
+ font-size: var(--font-size-lg);
9901
+ }
9902
+ .help-content__error { color: var(--color-danger-hover); }
9903
+ .help-content__error .material-symbols-outlined {
9904
+ font-size: 48px;
9905
+ opacity: 0.5;
9906
+ }
9907
+ .help-content__article { max-width: 860px; }
9908
+ .help-content__breadcrumb {
9909
+ display: flex;
9910
+ align-items: center;
9911
+ gap: var(--space-md);
9912
+ margin-bottom: var(--space-xl);
9913
+ padding-bottom: var(--space-lg);
9914
+ border-bottom: 1px solid var(--surface-3);
9915
+ }
9916
+ .help-breadcrumb__home {
9917
+ display: flex;
9918
+ align-items: center;
9919
+ color: var(--text-muted);
9920
+ text-decoration: none;
9921
+ }
9922
+ .help-breadcrumb__home:hover { color: var(--accent-bright); }
9923
+ .help-breadcrumb__home .material-symbols-outlined { font-size: 18px; }
9924
+ .help-breadcrumb__separator { color: var(--text-dim); }
9925
+ .help-breadcrumb__title {
9926
+ color: var(--text-secondary);
9927
+ font-size: var(--font-size-md);
9928
+ }
9929
+ /* A topic's table of contents (a `createDocumentOutline`), between the
9930
+ * breadcrumb and the article; drawn only for two headings or more. */
9931
+ .help-content__toc {
9932
+ margin-bottom: var(--space-xl);
9933
+ padding: var(--space-md) 0;
9934
+ border-left: 2px solid var(--surface-3);
9935
+ font-size: var(--font-size-md);
9936
+ }
9937
+ .help-content__toc[hidden] { display: none; }
9938
+ .help-index h1 {
9939
+ margin: 0 0 var(--space-md);
9940
+ color: var(--text-primary);
9941
+ font-size: 24px;
9942
+ }
9943
+ .help-index > p {
9944
+ margin-bottom: var(--space-2xl);
9945
+ color: var(--text-muted);
9946
+ }
9947
+ .help-index__grid {
9948
+ display: grid;
9949
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
9950
+ gap: var(--space-xl);
9951
+ }
9952
+ .help-index__category {
9953
+ padding: var(--space-lg);
9954
+ background: var(--surface-1);
9955
+ border: 1px solid var(--surface-3);
9956
+ border-radius: var(--radius-md);
9957
+ }
9958
+ .help-index__category:hover { border-color: var(--accent-bright); }
9959
+ .help-index__category-header {
9960
+ display: flex;
9961
+ align-items: center;
9962
+ gap: var(--space-md);
9963
+ margin-bottom: var(--space-md);
9964
+ color: var(--accent-bright);
9965
+ font-weight: var(--font-weight-semibold);
9966
+ }
9967
+ .help-index__category-header .material-symbols-outlined { font-size: 18px; }
9968
+ .help-index__topics {
9969
+ list-style: none;
9970
+ margin: 0;
9971
+ padding: 0;
9972
+ }
9973
+ .help-index__topic,
9974
+ .help-index__more {
9975
+ padding: var(--space-xs) 0;
9976
+ color: var(--text-secondary);
9977
+ font-size: var(--font-size-md);
9978
+ }
9979
+ .help-index__topic { cursor: pointer; }
9980
+ .help-index__topic:hover { color: var(--text-primary); }
9981
+ .help-index__more {
9982
+ color: var(--text-dim);
9983
+ font-size: var(--font-size-sm);
9984
+ font-style: italic;
9985
+ }
9986
+ .help-search-results h2 {
9987
+ margin: 0 0 var(--space-md);
9988
+ color: var(--text-primary);
9989
+ font-size: var(--font-size-2xl);
9990
+ }
9991
+ .help-search-count {
9992
+ margin-bottom: var(--space-xl);
9993
+ color: var(--text-muted);
9994
+ font-size: var(--font-size-md);
9995
+ }
9996
+ .help-search-empty {
9997
+ color: var(--text-muted);
9998
+ font-style: italic;
9999
+ }
10000
+ .help-search-list {
10001
+ list-style: none;
10002
+ margin: 0;
10003
+ padding: 0;
10004
+ }
10005
+ .help-search-item {
10006
+ display: flex;
10007
+ justify-content: space-between;
10008
+ align-items: center;
10009
+ gap: var(--space-lg);
10010
+ margin-bottom: var(--space-md);
10011
+ padding: 10px var(--space-lg);
10012
+ background: var(--surface-1);
10013
+ border: 1px solid var(--surface-3);
10014
+ border-radius: var(--radius-sm);
10015
+ cursor: pointer;
10016
+ }
10017
+ .help-search-item:hover { border-color: var(--accent-bright); }
10018
+ .help-search-item__title {
10019
+ color: var(--text-primary);
10020
+ font-size: var(--font-size-lg);
10021
+ }
10022
+ .help-search-item__category {
10023
+ color: var(--text-dim);
10024
+ font-size: var(--font-size-sm);
10025
+ }
10026
+ .help-article {
10027
+ color: var(--text-body);
10028
+ font-size: var(--font-size-lg);
10029
+ line-height: 1.6;
10030
+ }
10031
+ .help-article h1,
10032
+ .help-article h2,
10033
+ .help-article h3 { color: var(--text-primary); }
10034
+ .help-article h1 {
10035
+ margin: 0 0 var(--space-xl);
10036
+ padding-bottom: var(--space-md);
10037
+ border-bottom: 1px solid var(--surface-3);
10038
+ font-size: 24px;
10039
+ }
10040
+ .help-article h2 { margin: var(--space-2xl) 0 var(--space-lg); font-size: 20px; }
10041
+ .help-article h3 { margin: 20px 0 var(--space-md); font-size: var(--font-size-xl); }
10042
+ .help-article h4,
10043
+ .help-article h5,
10044
+ .help-article h6 {
10045
+ margin: var(--space-xl) 0 var(--space-md);
10046
+ color: var(--text-secondary);
10047
+ }
10048
+ .help-article p { margin: 0 0 var(--space-lg); }
10049
+ .help-article a { color: var(--accent-bright); text-decoration: none; }
10050
+ .help-article a:hover { text-decoration: underline; }
10051
+ .help-article strong { color: var(--text-primary); font-weight: var(--font-weight-semibold); }
10052
+ .help-article em { font-style: italic; }
10053
+ .help-article code {
10054
+ padding: 2px 6px;
10055
+ background: var(--surface-2);
10056
+ border-radius: 3px;
10057
+ font-family: var(--font-family-mono);
10058
+ font-size: var(--font-size-md);
10059
+ }
10060
+ .help-article pre {
10061
+ margin: 0 0 var(--space-xl);
10062
+ padding: var(--space-lg) var(--space-xl);
10063
+ background: var(--surface-well);
10064
+ border: 1px solid var(--surface-3);
10065
+ border-radius: var(--radius-sm);
10066
+ overflow-x: auto;
10067
+ }
10068
+ .help-article pre code { padding: 0; background: transparent; }
10069
+ .help-article blockquote {
10070
+ margin: 0 0 var(--space-lg);
10071
+ padding: var(--space-md) var(--space-xl);
10072
+ background: var(--state-selected);
10073
+ border-left: 3px solid var(--accent-bright);
10074
+ color: var(--text-secondary);
10075
+ }
10076
+ .help-article hr {
10077
+ margin: var(--space-2xl) 0;
10078
+ border: none;
10079
+ border-top: 1px solid var(--surface-3);
10080
+ }
10081
+ .help-article img {
10082
+ max-width: 100%;
10083
+ margin: var(--space-lg) 0;
10084
+ border-radius: var(--radius-sm);
10085
+ }
10086
+ .help-article table {
10087
+ width: 100%;
10088
+ margin: var(--space-xl) 0;
10089
+ border-collapse: collapse;
10090
+ font-size: var(--font-size-md);
10091
+ }
10092
+ .help-article th,
10093
+ .help-article td {
10094
+ padding: var(--space-md) var(--space-lg);
10095
+ border: 1px solid var(--surface-3);
10096
+ text-align: left;
10097
+ }
10098
+ .help-article th {
10099
+ background: var(--surface-2);
10100
+ color: var(--text-primary);
10101
+ font-weight: var(--font-weight-semibold);
10102
+ }
10103
+ .help-article tr:nth-child(even) { background: rgba(255, 255, 255, 0.02); }
10104
+ .help-article ul,
10105
+ .help-article ol {
10106
+ margin: var(--space-md) 0 var(--space-xl);
10107
+ padding-left: var(--space-2xl);
10108
+ }
10109
+ .help-article li { margin-bottom: var(--space-xs); line-height: 1.5; }
10110
+ .help-article li p { margin: 0; }
10111
+ .help-article li > ul,
10112
+ .help-article li > ol { margin: var(--space-xs) 0; }
10113
+ @media (max-width: 600px) {
10114
+ .help-modal { width: 95%; height: 90vh; }
10115
+ .help-modal__sidebar { display: none; }
10116
+ .help-modal__content { padding: var(--space-xl); }
10117
+ }
10118
+ /* ══ end HELP MODAL ══ */
10119
+
10120
+ /* ══ MENU BAR ════════════════════════════════════════════════════════
10121
+ *
10122
+ * `MenuBar` (src/ui/components/menu_bar.js, STORY-0030): a row of titles,
10123
+ * each opening a menu that may fly out submenus. Not `.twm-menu-bar`, which
10124
+ * is the older strip of 25px icon buttons and keeps its meaning. A menu is
10125
+ * `position: fixed` (the JS places it), so a bar inside a clipped chrome strip
10126
+ * still drops its menus over the page. Same surface, border and shadow as
10127
+ * `.twm-context-menu`: two menus that look different read as two systems.
10128
+ * tests/menu_bar_css.test.mjs holds every class the bar sets to a rule here,
10129
+ * both ways, and base.css to the same text. */
10130
+ .twm-menubar {
10131
+ display: flex;
10132
+ align-items: stretch;
10133
+ gap: 1px;
10134
+ min-height: 24px;
10135
+ font-size: var(--font-size-md);
10136
+ color: var(--text-body);
10137
+ user-select: none;
10138
+ }
10139
+ .twm-menubar__entry {
10140
+ position: relative;
10141
+ display: flex;
10142
+ }
10143
+ .twm-menubar__title {
10144
+ appearance: none;
10145
+ background: none;
10146
+ border: 0;
10147
+ border-radius: var(--radius-sm);
10148
+ color: inherit;
10149
+ font: inherit;
10150
+ padding: var(--space-xxs) var(--space-md);
10151
+ cursor: default;
10152
+ }
10153
+ .twm-menubar__title:hover,
10154
+ .twm-menubar__title:focus-visible,
10155
+ .twm-menubar__title[aria-expanded="true"] {
10156
+ background: var(--state-active);
10157
+ color: var(--text-bright);
10158
+ outline: none;
10159
+ }
10160
+ .twm-menubar__menu {
10161
+ position: fixed;
10162
+ z-index: var(--z-over-windows);
10163
+ display: flex;
10164
+ flex-direction: column;
10165
+ min-width: 200px;
10166
+ max-height: calc(100vh - 8px);
10167
+ overflow-y: auto;
10168
+ padding: var(--space-xs) 0;
10169
+ background: var(--surface-0);
10170
+ color: var(--text-body);
10171
+ border: 1px solid var(--surface-3);
10172
+ border-radius: var(--radius-md);
10173
+ box-shadow: 0 6px 24px rgba(0, 0, 0, 0.45);
10174
+ }
10175
+ .twm-menubar__cell {
10176
+ display: flex;
10177
+ }
10178
+ .twm-menubar__item {
10179
+ appearance: none;
10180
+ background: none;
10181
+ border: 0;
10182
+ width: 100%;
10183
+ text-align: left;
10184
+ color: inherit;
10185
+ font: inherit;
10186
+ display: flex;
10187
+ align-items: center;
10188
+ gap: var(--space-md);
10189
+ padding: var(--space-xs) var(--space-lg) var(--space-xs) var(--space-xs);
10190
+ cursor: default;
10191
+ white-space: nowrap;
10192
+ }
10193
+ .twm-menubar__item:focus,
10194
+ .twm-menubar__item:hover:not(.twm-menubar__item--disabled),
10195
+ .twm-menubar__item[aria-expanded="true"] {
10196
+ background: var(--state-selected);
10197
+ outline: none;
10198
+ }
10199
+ .twm-menubar__item--submenu {
10200
+ padding-right: var(--space-md);
10201
+ }
10202
+ .twm-menubar__item--disabled {
10203
+ opacity: 0.4;
10204
+ color: var(--text-muted);
10205
+ }
10206
+ .twm-menubar__check {
10207
+ flex: 0 0 16px;
10208
+ text-align: center;
10209
+ }
10210
+ .twm-menubar__item--check[aria-checked="true"] .twm-menubar__check::before { content: "\2713"; }
10211
+ .twm-menubar__item--radio[aria-checked="true"] .twm-menubar__check::before { content: "\2022"; }
10212
+ .twm-menubar__label {
10213
+ flex: 1 1 auto;
10214
+ }
10215
+ .twm-menubar__shortcut {
10216
+ margin-left: var(--space-2xl);
10217
+ color: var(--text-muted);
10218
+ font-size: var(--font-size-sm);
10219
+ }
10220
+ .twm-menubar__arrow::before {
10221
+ content: "\25B8";
10222
+ color: var(--text-muted);
10223
+ }
10224
+ .twm-menubar__separator {
10225
+ height: 1px;
10226
+ margin: var(--space-xs) var(--space-md);
10227
+ background: var(--surface-3);
10228
+ }
10229
+ /* ══ end MENU BAR ══ */
10230
+
10231
+ /* ══ TOGGLE ══════════════════════════════════════════════════════════
10232
+ * ToggleField: a track and a knob drawn over a real checkbox with
10233
+ * role="switch". The input sits on top of the track, transparent, so the
10234
+ * pointer and the keyboard reach a native control. Tokens only: swap
10235
+ * tokens.css and the switch follows.
10236
+ */
10237
+ .twm-toggle {
10238
+ --twm-toggle-w: 34px;
10239
+ --twm-toggle-h: 18px;
10240
+ --twm-toggle-pad: 2px;
10241
+ display: inline-flex;
10242
+ align-items: center;
10243
+ gap: var(--space-sm);
10244
+ color: var(--text-body);
10245
+ font-size: var(--font-size-sm);
10246
+ line-height: var(--line-height-tight);
10247
+ cursor: pointer;
10248
+ user-select: none;
10249
+ vertical-align: middle;
10250
+ }
10251
+ .twm-toggle--compact {
10252
+ --twm-toggle-w: 26px;
10253
+ --twm-toggle-h: 14px;
10254
+ gap: var(--space-xs);
10255
+ font-size: var(--font-size-xs);
10256
+ }
10257
+ .twm-toggle__control {
10258
+ position: relative;
10259
+ display: inline-flex;
10260
+ flex: none;
10261
+ width: var(--twm-toggle-w);
10262
+ height: var(--twm-toggle-h);
10263
+ }
10264
+ .twm-toggle__input {
10265
+ position: absolute;
10266
+ inset: 0;
10267
+ width: 100%;
10268
+ height: 100%;
10269
+ margin: 0;
10270
+ opacity: 0;
10271
+ cursor: inherit;
10272
+ z-index: 1;
10273
+ }
10274
+ .twm-toggle__track {
10275
+ position: relative;
10276
+ display: block;
10277
+ width: 100%;
10278
+ height: 100%;
10279
+ box-sizing: border-box;
10280
+ border: 1px solid var(--border-strong);
10281
+ border-radius: var(--radius-pill);
10282
+ background: var(--surface-3);
10283
+ transition: background-color var(--transition-fast), border-color var(--transition-fast);
10284
+ }
10285
+ .twm-toggle__knob {
10286
+ position: absolute;
10287
+ top: var(--twm-toggle-pad);
10288
+ left: var(--twm-toggle-pad);
10289
+ width: calc(var(--twm-toggle-h) - 2 * var(--twm-toggle-pad) - 2px);
10290
+ height: calc(var(--twm-toggle-h) - 2 * var(--twm-toggle-pad) - 2px);
10291
+ border-radius: var(--radius-circle);
10292
+ background: var(--text-muted);
10293
+ box-shadow: var(--shadow-sm);
10294
+ transition: transform var(--transition-fast), background-color var(--transition-fast);
10295
+ }
10296
+ .twm-toggle__input:checked + .twm-toggle__track {
10297
+ background: var(--accent);
10298
+ border-color: var(--accent);
10299
+ }
10300
+ .twm-toggle__input:checked + .twm-toggle__track .twm-toggle__knob {
10301
+ transform: translateX(calc(var(--twm-toggle-w) - var(--twm-toggle-h)));
10302
+ background: var(--text-bright);
10303
+ }
10304
+ .twm-toggle:hover .twm-toggle__input:not(:disabled) + .twm-toggle__track {
10305
+ border-color: var(--accent-hover);
10306
+ }
10307
+ .twm-toggle__input:focus-visible + .twm-toggle__track {
10308
+ outline: 2px solid var(--accent-bright);
10309
+ outline-offset: 2px;
10310
+ }
10311
+ .twm-toggle__state {
10312
+ min-width: 2.5em;
10313
+ color: var(--text-muted);
10314
+ }
10315
+ .twm-toggle__label {
10316
+ color: var(--text-body);
10317
+ }
10318
+ .twm-toggle--disabled {
10319
+ cursor: not-allowed;
10320
+ opacity: 0.5;
10321
+ }
10322
+ .twm-modal__toggle {
10323
+ display: flex;
10324
+ align-items: center;
10325
+ min-height: var(--ea-row-h);
10326
+ }
10327
+ /* ══ end TOGGLE ══ */
10328
+
10329
+ /* ══ SETTINGS VIEW (STORY-0050) ══════════════════════════════════════
10330
+ * SettingsView: a sidebar of categories with a filter box, beside the
10331
+ * chosen category's settings in groups. Tokens only.
10332
+ */
10333
+ .twm-settings {
10334
+ display: flex;
10335
+ height: 100%;
10336
+ min-height: 0;
10337
+ background: var(--surface-1);
10338
+ color: var(--text-body);
10339
+ font-size: var(--font-size-sm);
10340
+ }
10341
+ .twm-settings__sidebar {
10342
+ display: flex;
10343
+ flex-direction: column;
10344
+ flex: 0 0 220px;
10345
+ min-width: 0;
10346
+ gap: var(--space-sm);
10347
+ padding: var(--space-md);
10348
+ border-right: 1px solid var(--border-subtle);
10349
+ background: var(--surface-0);
10350
+ }
10351
+ .twm-settings__sidebar-title {
10352
+ display: flex;
10353
+ align-items: center;
10354
+ gap: var(--space-sm);
10355
+ color: var(--text-bright);
10356
+ font-weight: var(--font-weight-semibold);
10357
+ }
10358
+ .twm-settings__search {
10359
+ display: flex;
10360
+ align-items: center;
10361
+ gap: var(--space-xs);
10362
+ padding: 0 var(--space-sm);
10363
+ min-height: var(--ea-row-h);
10364
+ background: var(--ea-input-bg);
10365
+ border: 1px solid var(--border-strong);
10366
+ border-radius: var(--radius-sm);
10367
+ color: var(--text-muted);
10368
+ }
10369
+ .twm-settings__search:focus-within { border-color: var(--accent-bright); }
10370
+ .twm-settings__search-input {
10371
+ flex: 1 1 auto;
10372
+ min-width: 0;
10373
+ background: transparent;
10374
+ border: none;
10375
+ outline: none;
10376
+ color: var(--text-body);
10377
+ font: inherit;
10378
+ }
10379
+ .twm-settings__nav {
10380
+ display: flex;
10381
+ flex-direction: column;
10382
+ flex: 1 1 auto;
10383
+ min-height: 0;
10384
+ overflow-y: auto;
10385
+ gap: var(--space-xxs);
10386
+ }
10387
+ .twm-settings__cat {
10388
+ display: flex;
10389
+ align-items: center;
10390
+ gap: var(--space-sm);
10391
+ min-height: var(--ea-row-h);
10392
+ padding: 0 var(--space-sm);
10393
+ background: transparent;
10394
+ border: none;
10395
+ border-radius: var(--radius-sm);
10396
+ color: var(--text-secondary);
10397
+ font: inherit;
10398
+ text-align: left;
10399
+ cursor: pointer;
10400
+ }
10401
+ .twm-settings__cat:hover { background: var(--state-hover); }
10402
+ .twm-settings__cat:focus-visible { outline: 1px solid var(--accent-bright); }
10403
+ .twm-settings__cat--active {
10404
+ background: var(--state-selected);
10405
+ color: var(--text-bright);
10406
+ }
10407
+ .twm-settings__cat .material-symbols-outlined,
10408
+ .twm-settings__reset-all .material-symbols-outlined,
10409
+ .twm-settings__reset-category .material-symbols-outlined {
10410
+ font-size: var(--font-size-lg);
10411
+ }
10412
+ .twm-settings__cat-label {
10413
+ overflow: hidden;
10414
+ text-overflow: ellipsis;
10415
+ white-space: nowrap;
10416
+ }
10417
+ .twm-settings__content {
10418
+ flex: 1 1 auto;
10419
+ min-width: 0;
10420
+ overflow-y: auto;
10421
+ padding: var(--space-lg) var(--space-xl);
10422
+ }
10423
+ .twm-settings__header {
10424
+ display: flex;
10425
+ align-items: flex-start;
10426
+ justify-content: space-between;
10427
+ gap: var(--space-lg);
10428
+ margin-bottom: var(--space-lg);
10429
+ }
10430
+ .twm-settings__header-info {
10431
+ flex: 1 1 auto;
10432
+ min-width: 0;
10433
+ }
10434
+ .twm-settings__title {
10435
+ margin: 0;
10436
+ color: var(--text-bright);
10437
+ font-size: var(--font-size-xl);
10438
+ font-weight: var(--font-weight-semibold);
10439
+ }
10440
+ .twm-settings__description {
10441
+ margin: var(--space-xs) 0 0;
10442
+ color: var(--text-muted);
10443
+ }
10444
+ .twm-settings__group { margin-bottom: var(--space-xl); }
10445
+ .twm-settings__group-title {
10446
+ margin: 0 0 var(--space-sm);
10447
+ padding-bottom: var(--space-xs);
10448
+ border-bottom: 1px solid var(--border-subtle);
10449
+ color: var(--text-secondary);
10450
+ font-size: var(--font-size-xs);
10451
+ font-weight: var(--font-weight-semibold);
10452
+ text-transform: uppercase;
10453
+ letter-spacing: 0.04em;
10454
+ }
10455
+ .twm-settings__row {
10456
+ display: flex;
10457
+ align-items: center;
10458
+ gap: var(--space-md);
10459
+ padding: var(--space-sm) var(--space-sm);
10460
+ border-left: 2px solid transparent;
10461
+ border-radius: var(--radius-sm);
10462
+ }
10463
+ .twm-settings__row:hover { background: var(--state-hover); }
10464
+ .twm-settings__row--modified { border-left-color: var(--accent-bright); }
10465
+ .twm-settings__info {
10466
+ flex: 1 1 auto;
10467
+ min-width: 0;
10468
+ }
10469
+ .twm-settings__label-line {
10470
+ display: flex;
10471
+ align-items: center;
10472
+ gap: var(--space-sm);
10473
+ }
10474
+ .twm-settings__label { color: var(--text-primary); }
10475
+ .twm-settings__marker {
10476
+ padding: 0 var(--space-xs);
10477
+ border-radius: var(--radius-pill);
10478
+ background: var(--surface-3);
10479
+ color: var(--accent-bright);
10480
+ font-size: var(--font-size-xs);
10481
+ }
10482
+ .twm-settings__marker[hidden],
10483
+ .twm-settings__reset[hidden] { display: none; }
10484
+ .twm-settings__row .twm-settings__description {
10485
+ margin-top: var(--space-xxs);
10486
+ font-size: var(--font-size-xs);
10487
+ }
10488
+ .twm-settings__path {
10489
+ display: block;
10490
+ margin-top: var(--space-xxs);
10491
+ color: var(--text-dim);
10492
+ font-family: var(--font-family-mono);
10493
+ font-size: var(--font-size-xs);
10494
+ }
10495
+ .twm-settings__control {
10496
+ flex: 0 0 auto;
10497
+ display: flex;
10498
+ align-items: center;
10499
+ max-width: 50%;
10500
+ }
10501
+ .twm-settings__select,
10502
+ .twm-settings__number,
10503
+ .twm-settings__text {
10504
+ min-height: var(--ea-row-h);
10505
+ padding: 0 var(--space-sm);
10506
+ background: var(--ea-input-bg);
10507
+ border: 1px solid var(--border-strong);
10508
+ border-radius: var(--radius-sm);
10509
+ color: var(--text-body);
10510
+ font: inherit;
10511
+ }
10512
+ .twm-settings__select:focus,
10513
+ .twm-settings__number:focus,
10514
+ .twm-settings__text:focus {
10515
+ outline: none;
10516
+ border-color: var(--accent-bright);
10517
+ }
10518
+ .twm-settings__number { width: 110px; }
10519
+ .twm-settings__text { width: 240px; }
10520
+ .twm-settings__readonly {
10521
+ color: var(--text-muted);
10522
+ font-family: var(--font-family-mono);
10523
+ }
10524
+ .twm-settings__reset {
10525
+ flex: 0 0 auto;
10526
+ display: inline-flex;
10527
+ align-items: center;
10528
+ justify-content: center;
10529
+ width: var(--ea-row-h);
10530
+ height: var(--ea-row-h);
10531
+ padding: 0;
10532
+ background: transparent;
10533
+ border: none;
10534
+ border-radius: var(--radius-sm);
10535
+ color: var(--text-muted);
10536
+ cursor: pointer;
10537
+ }
10538
+ .twm-settings__reset:hover { background: var(--state-hover); color: var(--text-bright); }
10539
+ .twm-settings__reset .material-symbols-outlined { font-size: var(--font-size-lg); }
10540
+ .twm-settings__empty {
10541
+ padding: var(--space-xl) 0;
10542
+ color: var(--text-muted);
10543
+ }
10544
+ /* ══ end SETTINGS VIEW ══ */
10545
+
10546
+ /* ══ DOCUMENT OUTLINE (0.17.0) ═══════════════════════════════════════
10547
+ * `createDocumentOutline` (src/ui/components/document_outline.js). No height
10548
+ * of its own: the host that holds it scrolls, if anything does. */
10549
+ .twm-doc-outline { display: block; min-width: 0; }
10550
+ .twm-doc-outline__items {
10551
+ list-style: none;
10552
+ margin: 0;
10553
+ padding: 2px 0;
10554
+ }
10555
+ .twm-doc-outline__item { margin: 0; padding: 0; min-width: 0; }
10556
+ /* One step of indentation per level below the first, set inline per item as
10557
+ * `--twm-doc-outline-depth` (0 for depth 1). */
10558
+ .twm-doc-outline__link {
10559
+ display: flex;
10560
+ align-items: baseline;
10561
+ gap: 6px;
10562
+ width: 100%;
10563
+ min-width: 0;
10564
+ margin: 0;
10565
+ padding: 3px 8px 3px calc(8px + var(--twm-doc-outline-depth, 0) * var(--twm-doc-outline-indent, 12px));
10566
+ border: none;
10567
+ border-left: 2px solid transparent;
10568
+ background: transparent;
10569
+ color: var(--text-secondary, #cccccc);
10570
+ font: inherit;
10571
+ text-align: left;
10572
+ cursor: pointer;
10573
+ }
10574
+ .twm-doc-outline__link:hover { background: var(--state-hover, rgba(255, 255, 255, 0.05)); color: var(--text-primary, #e0e0e0); }
10575
+ .twm-doc-outline__link:focus-visible { outline: 1px solid var(--accent-bright, #007acc); outline-offset: -1px; }
10576
+ .twm-doc-outline__item--level-1 > .twm-doc-outline__link { color: var(--text-primary, #e0e0e0); }
10577
+ .twm-doc-outline__number {
10578
+ flex: 0 0 auto;
10579
+ color: var(--text-muted, #888888);
10580
+ font-variant-numeric: tabular-nums;
10581
+ }
10582
+ .twm-doc-outline__label {
10583
+ flex: 1 1 auto;
10584
+ min-width: 0;
10585
+ overflow: hidden;
10586
+ text-overflow: ellipsis;
10587
+ white-space: nowrap;
10588
+ }
10589
+ .twm-doc-outline__item.twm-doc-outline__item--active > .twm-doc-outline__link {
10590
+ border-left-color: var(--accent-bright, #007acc);
10591
+ background: rgba(0, 122, 204, 0.15);
10592
+ color: var(--text-primary, #e0e0e0);
10593
+ }
10594
+ /* Listed, but nothing in the document answers to it. */
10595
+ .twm-doc-outline__item--unresolved > .twm-doc-outline__link { opacity: 0.6; }
10596
+ .twm-doc-outline__empty {
10597
+ margin: 0;
10598
+ padding: 8px;
10599
+ color: var(--text-muted, #888888);
10600
+ font-style: italic;
10601
+ }
10602
+ /* ══ end DOCUMENT OUTLINE ══ */
10603
+
10604
+ /* ══ TAG INPUT (0.17.0) ═══════════════════════════════════════════════
10605
+ * `createTagInput` (src/ui/components/tag_input.js): removable chips and a
10606
+ * text box on one wrapping line. No height of its own. */
10607
+ .twm-tag-input {
10608
+ display: flex;
10609
+ flex-wrap: wrap;
10610
+ align-items: center;
10611
+ gap: var(--space-xs, 4px);
10612
+ min-width: 0;
10613
+ padding: 3px var(--space-xs, 4px);
10614
+ background: var(--surface-3, #3c3c3c);
10615
+ border: 1px solid var(--border-strong, #383838);
10616
+ border-radius: var(--radius-sm, 4px);
10617
+ cursor: text;
10618
+ }
10619
+ .twm-tag-input:focus-within { border-color: var(--accent-bright, #007acc); }
10620
+ .twm-tag-input--readonly {
10621
+ background: transparent;
10622
+ border-color: transparent;
10623
+ padding-left: 0;
10624
+ padding-right: 0;
10625
+ cursor: default;
10626
+ }
10627
+ .twm-tag-input__chips {
10628
+ display: contents;
10629
+ list-style: none;
10630
+ margin: 0;
10631
+ padding: 0;
10632
+ }
10633
+ .twm-tag-input__chip {
10634
+ display: inline-flex;
10635
+ align-items: center;
10636
+ gap: 2px;
10637
+ max-width: 100%;
10638
+ min-width: 0;
10639
+ padding: 1px var(--space-xs, 4px) 1px var(--space-sm, 6px);
10640
+ background: var(--surface-2, #2d2d2d);
10641
+ border: 1px solid var(--border-strong, #383838);
10642
+ border-radius: var(--radius-pill, 999px);
10643
+ color: var(--text-primary, #e0e0e0);
10644
+ font-size: var(--font-size-sm, 12px);
10645
+ line-height: 18px;
10646
+ }
10647
+ .twm-tag-input--readonly .twm-tag-input__chip { padding-right: var(--space-sm, 6px); }
10648
+ .twm-tag-input__text {
10649
+ min-width: 0;
10650
+ overflow: hidden;
10651
+ text-overflow: ellipsis;
10652
+ white-space: nowrap;
10653
+ }
10654
+ .twm-tag-input__remove {
10655
+ display: inline-flex;
10656
+ align-items: center;
10657
+ justify-content: center;
10658
+ width: 16px;
10659
+ height: 16px;
10660
+ padding: 0;
10661
+ border: none;
10662
+ border-radius: var(--radius-circle, 50%);
10663
+ background: transparent;
10664
+ color: var(--text-muted, #888);
10665
+ font: inherit;
10666
+ line-height: 1;
10667
+ cursor: pointer;
10668
+ }
10669
+ .twm-tag-input__remove:hover,
10670
+ .twm-tag-input__remove:focus-visible {
10671
+ background: var(--state-active, rgba(255, 255, 255, 0.08));
10672
+ color: var(--text-bright, #fff);
10673
+ }
10674
+ .twm-tag-input__input {
10675
+ flex: 1 1 6em;
10676
+ min-width: 4em;
10677
+ padding: 1px 2px;
10678
+ border: none;
10679
+ outline: none;
10680
+ background: transparent;
10681
+ color: var(--text-primary, #e0e0e0);
10682
+ font: inherit;
10683
+ font-size: var(--font-size-sm, 12px);
10684
+ }
10685
+ /* ══ end TAG INPUT ══ */
10686
+
10687
+ /* ══ SERIES TRANSFORMS ═══════════════════════════════════════════════
10688
+ *
10689
+ * `renderSeriesTransformControls` (src/charts/series_transforms.js): per
10690
+ * injected transform, a group with a toggle per output and a number field per
10691
+ * parameter, in a subplot's config. */
10692
+ .twm-series-transforms {
10693
+ display: flex;
10694
+ flex-direction: column;
10695
+ gap: var(--space-md);
10696
+ }
10697
+ .twm-series-transforms__group {
10698
+ display: flex;
10699
+ flex-direction: column;
10700
+ gap: var(--space-xs);
10701
+ margin: 0;
10702
+ padding: var(--space-sm) var(--space-md);
10703
+ border: 1px solid var(--border-subtle);
10704
+ border-radius: var(--radius-sm);
10705
+ }
10706
+ .twm-series-transforms__label {
10707
+ padding: 0 var(--space-xs);
10708
+ font-size: var(--font-size-sm);
10709
+ font-weight: var(--font-weight-semibold);
10710
+ color: var(--text-muted);
10711
+ }
10712
+ .twm-series-transforms__row {
10713
+ display: flex;
10714
+ align-items: center;
10715
+ gap: var(--space-sm);
10716
+ font-size: var(--font-size-sm);
10717
+ color: var(--text-primary);
10718
+ }
10719
+ .twm-series-transforms__param {
10720
+ width: 96px;
10721
+ margin-left: auto;
10722
+ padding: var(--space-xxs) var(--space-xs);
10723
+ background: var(--surface-3);
10724
+ color: var(--text-primary);
10725
+ border: 1px solid var(--border-strong);
10726
+ border-radius: var(--radius-sm);
10727
+ }
10728
+ .twm-series-transforms__param:disabled {
10729
+ opacity: 0.5;
10730
+ }
10731
+ /* ══ end SERIES TRANSFORMS ══ */
10732
+
10733
+ /* ══ PLOT GRID ════════════════════════════════════════════════════════
10734
+ *
10735
+ * `renderPlotGrid` (src/charts/plot_grid.js): a CSS grid of one Plotly figure
10736
+ * per subplot. The template columns/rows and each cell's grid-area are inline,
10737
+ * from PLOT_LAYOUTS. */
10738
+ .twm-plot-grid {
10739
+ display: grid;
10740
+ gap: 4px;
10741
+ width: 100%;
10742
+ height: 100%;
10743
+ min-height: 0;
10744
+ }
10745
+ .twm-plot-grid__cell {
10746
+ position: relative; /* Anchor for Plotly's positioning */
10747
+ min-width: 0;
10748
+ min-height: 0;
10749
+ }
10750
+ .twm-plot-grid__cell > .twm-plot-no-data {
10751
+ padding: var(--space-lg);
10752
+ font-size: var(--font-size-xs);
10753
+ }
10754
+ /* ══ end PLOT GRID ══ */
10755
+
10756
+ /* ══ PLOT CONFIG EDITOR ═══════════════════════════════════════════════
10757
+ *
10758
+ * `renderPlotConfigEditor` (src/charts/plot_config_editor.js): a layout
10759
+ * picker, a tab per subplot slot and the fields of the subplot in front. */
10760
+ .twm-plot-config {
10761
+ display: flex;
10762
+ flex-direction: column;
10763
+ gap: var(--space-md);
10764
+ min-width: 0;
10765
+ font-size: var(--font-size-sm);
10766
+ color: var(--text-primary);
10767
+ }
10768
+ .twm-plot-config__section {
10769
+ display: flex;
10770
+ flex-direction: column;
10771
+ gap: var(--space-xs);
10772
+ }
10773
+ .twm-plot-config__heading {
10774
+ margin: 0;
10775
+ font-size: var(--font-size-sm);
10776
+ font-weight: var(--font-weight-semibold);
10777
+ color: var(--text-muted);
10778
+ }
10779
+ .twm-plot-config__layouts {
10780
+ display: flex;
10781
+ flex-wrap: wrap;
10782
+ gap: var(--space-xs);
10783
+ }
10784
+ .twm-plot-config__layout {
10785
+ min-width: 0;
10786
+ }
10787
+ .twm-plot-config__layout--on {
10788
+ border-color: var(--accent);
10789
+ color: var(--text-bright);
10790
+ }
10791
+ .twm-plot-config__tabs[hidden] {
10792
+ display: none;
10793
+ }
10794
+ .twm-plot-config__body {
10795
+ display: flex;
10796
+ flex-direction: column;
10797
+ gap: var(--space-md);
10798
+ }
10799
+ .twm-plot-config__field {
10800
+ display: flex;
10801
+ align-items: center;
10802
+ gap: var(--space-sm);
10803
+ min-width: 0;
10804
+ }
10805
+ .twm-plot-config__label {
10806
+ flex: 0 0 96px;
10807
+ color: var(--text-muted);
10808
+ }
10809
+ .twm-plot-config__input {
10810
+ box-sizing: border-box;
10811
+ min-width: 0;
10812
+ flex: 1 1 auto;
10813
+ padding: var(--space-xxs) var(--space-xs);
10814
+ font: inherit;
10815
+ background: var(--surface-3);
10816
+ color: var(--text-primary);
10817
+ border: 1px solid var(--border-strong);
10818
+ border-radius: var(--radius-sm);
10819
+ }
10820
+ .twm-plot-config__input--number {
10821
+ flex: 0 1 80px;
10822
+ width: 80px;
10823
+ }
10824
+ .twm-plot-config__input:disabled {
10825
+ opacity: 0.5;
10826
+ }
10827
+ .twm-plot-config__picker {
10828
+ flex: 1 1 140px;
10829
+ min-width: 0;
10830
+ }
10831
+ .twm-plot-config__axes,
10832
+ .twm-plot-config__list,
10833
+ .twm-plot-config__series-list {
10834
+ display: flex;
10835
+ flex-direction: column;
10836
+ gap: var(--space-xs);
10837
+ }
10838
+ .twm-plot-config__axis {
10839
+ display: flex;
10840
+ flex-direction: column;
10841
+ gap: var(--space-xs);
10842
+ margin: 0;
10843
+ padding: var(--space-sm) var(--space-md);
10844
+ border: 1px solid var(--border-subtle);
10845
+ border-radius: var(--radius-sm);
10846
+ min-width: 0;
10847
+ }
10848
+ .twm-plot-config__axis--disabled {
10849
+ opacity: 0.5;
10850
+ }
10851
+ .twm-plot-config__axis-title {
10852
+ display: flex;
10853
+ align-items: center;
10854
+ gap: var(--space-xs);
10855
+ padding: 0 var(--space-xs);
10856
+ font-weight: var(--font-weight-semibold);
10857
+ color: var(--text-muted);
10858
+ }
10859
+ .twm-plot-config__axis-fields {
10860
+ display: grid;
10861
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
10862
+ gap: var(--space-xs) var(--space-md);
10863
+ }
10864
+ .twm-plot-config__series {
10865
+ display: flex;
10866
+ flex-direction: column;
10867
+ gap: var(--space-xxs);
10868
+ padding: var(--space-xs) 0;
10869
+ border-top: 1px solid var(--border-subtle);
10870
+ }
10871
+ .twm-plot-config__series-main,
10872
+ .twm-plot-config__row {
10873
+ display: flex;
10874
+ flex-wrap: wrap;
10875
+ align-items: center;
10876
+ gap: var(--space-xs);
10877
+ }
10878
+ .twm-plot-config__series-main > .twm-plot-config__input:not(.twm-plot-config__input--number):not(.twm-plot-config__input--select),
10879
+ .twm-plot-config__row > .twm-plot-config__input:not(.twm-plot-config__input--number):not(.twm-plot-config__input--select) {
10880
+ flex: 1 1 100px;
10881
+ }
10882
+ .twm-plot-config__series-main > .twm-plot-config__input--select,
10883
+ .twm-plot-config__row > .twm-plot-config__input--select {
10884
+ flex: 0 1 auto;
10885
+ }
10886
+ .twm-plot-config__series-extra:empty {
10887
+ display: none;
10888
+ }
10889
+ .twm-plot-config__overlay,
10890
+ .twm-plot-config__overlay-fields {
10891
+ display: flex;
10892
+ flex-direction: column;
10893
+ gap: var(--space-xxs);
10894
+ padding-left: var(--space-md);
10895
+ }
10896
+ .twm-plot-config__overlay-source {
10897
+ color: var(--text-muted);
10898
+ }
10899
+ .twm-plot-config__overlay-source--error {
10900
+ color: var(--color-danger);
10901
+ }
10902
+ .twm-plot-config__empty {
10903
+ color: var(--text-muted);
10904
+ font-style: italic;
10905
+ }
10906
+ .twm-plot-config__add {
10907
+ align-self: flex-start;
10908
+ }
10909
+ .twm-plot-config__remove {
10910
+ flex: 0 0 auto;
10911
+ margin-left: auto;
10912
+ }
10913
+ .twm-plot-config__remove > .material-symbols-outlined {
10914
+ font-size: 16px;
10915
+ }
10916
+ /* ══ end PLOT CONFIG EDITOR ══ */
10917
+
10918
+ /* ══ WIZARD (STORY-0047) ══════════════════════════════════════════════════
10919
+ * A step indicator, a body slot and a footer. Steps are drawn done / current /
10920
+ * upcoming / error and joined by a connector that fills once the step after it
10921
+ * has been reached. Only a done (or errored) step before the current one is an
10922
+ * enabled button; the rest are disabled and look inert. */
10923
+ .twm-wizard {
10924
+ display: flex;
10925
+ flex-direction: column;
10926
+ height: 100%;
10927
+ min-height: 0;
10928
+ color: var(--text-body);
10929
+ font-size: var(--font-size-sm);
10930
+ }
10931
+ .twm-wizard__steps {
10932
+ display: flex;
10933
+ align-items: center;
10934
+ gap: var(--space-xs);
10935
+ margin: 0;
10936
+ padding: var(--space-md) var(--space-lg);
10937
+ list-style: none;
10938
+ border-bottom: 1px solid var(--border-subtle);
10939
+ }
10940
+ .twm-wizard__step {
10941
+ flex: 0 0 auto;
10942
+ min-width: 0;
10943
+ }
10944
+ .twm-wizard__step-btn {
10945
+ display: inline-flex;
10946
+ align-items: center;
10947
+ gap: var(--space-xs);
10948
+ padding: var(--space-xxs) var(--space-xs);
10949
+ border: 0;
10950
+ border-radius: var(--radius-sm);
10951
+ background: transparent;
10952
+ color: var(--text-muted);
10953
+ font: inherit;
10954
+ cursor: pointer;
10955
+ }
10956
+ .twm-wizard__step-btn:hover:not(:disabled) {
10957
+ background: var(--state-hover);
10958
+ }
10959
+ .twm-wizard__step-btn:focus-visible {
10960
+ outline: 1px solid var(--accent);
10961
+ outline-offset: 1px;
10962
+ }
10963
+ .twm-wizard__step-btn:disabled {
10964
+ cursor: default;
10965
+ }
10966
+ .twm-wizard__step-marker {
10967
+ display: inline-flex;
10968
+ align-items: center;
10969
+ justify-content: center;
10970
+ flex: 0 0 auto;
10971
+ width: 20px;
10972
+ height: 20px;
10973
+ border: 1px solid var(--border-strong);
10974
+ border-radius: var(--radius-circle);
10975
+ font-size: var(--font-size-xs);
10976
+ line-height: 1;
10977
+ }
10978
+ .twm-wizard__step-marker.material-symbols-outlined {
10979
+ font-size: 14px;
10980
+ }
10981
+ .twm-wizard__step-label {
10982
+ overflow: hidden;
10983
+ text-overflow: ellipsis;
10984
+ white-space: nowrap;
10985
+ }
10986
+ .twm-wizard__step--done .twm-wizard__step-btn {
10987
+ color: var(--text-body);
10988
+ }
10989
+ .twm-wizard__step--done .twm-wizard__step-marker {
10990
+ border-color: var(--color-success);
10991
+ color: var(--color-success);
10992
+ }
10993
+ .twm-wizard__step--current .twm-wizard__step-btn {
10994
+ color: var(--text-bright);
10995
+ font-weight: var(--font-weight-semibold);
10996
+ }
10997
+ .twm-wizard__step--current .twm-wizard__step-marker {
10998
+ border-color: var(--accent);
10999
+ background: var(--accent);
11000
+ color: var(--text-bright);
11001
+ }
11002
+ .twm-wizard__step--upcoming .twm-wizard__step-btn {
11003
+ color: var(--text-dim);
11004
+ }
11005
+ .twm-wizard__step--error .twm-wizard__step-btn {
11006
+ color: var(--color-danger);
11007
+ }
11008
+ .twm-wizard__step--error .twm-wizard__step-marker {
11009
+ border-color: var(--color-danger);
11010
+ color: var(--color-danger);
11011
+ }
11012
+ .twm-wizard__step--active .twm-wizard__step-label {
11013
+ text-decoration: underline;
11014
+ text-underline-offset: 3px;
11015
+ }
11016
+ .twm-wizard__connector {
11017
+ flex: 1 1 16px;
11018
+ min-width: 16px;
11019
+ height: 1px;
11020
+ background: var(--border-default);
11021
+ }
11022
+ .twm-wizard__connector--done {
11023
+ background: var(--color-success);
11024
+ }
11025
+ .twm-wizard__body {
11026
+ flex: 1 1 auto;
11027
+ min-height: 0;
11028
+ overflow: auto;
11029
+ padding: var(--space-lg);
11030
+ }
11031
+ .twm-wizard__footer {
11032
+ display: flex;
11033
+ align-items: center;
11034
+ gap: var(--space-md);
11035
+ padding: var(--space-sm) var(--space-lg);
11036
+ border-top: 1px solid var(--border-subtle);
11037
+ }
11038
+ .twm-wizard__message {
11039
+ flex: 1 1 auto;
11040
+ min-width: 0;
11041
+ color: var(--text-muted);
11042
+ }
11043
+ .twm-wizard__message--hint {
11044
+ color: var(--color-warning);
11045
+ }
11046
+ .twm-wizard__message--error {
11047
+ color: var(--color-danger);
11048
+ }
11049
+ .twm-wizard__actions {
11050
+ display: flex;
11051
+ flex: 0 0 auto;
11052
+ gap: var(--space-xs);
11053
+ }
11054
+ .twm-wizard--busy {
11055
+ cursor: progress;
11056
+ }
11057
+ .twm-wizard--busy .twm-wizard__body {
11058
+ opacity: 0.6;
11059
+ pointer-events: none;
11060
+ }
11061
+ /* ══ end WIZARD ══ */
11062
+
11063
+ /* ══ SECTION STACK ═══════════════════════════════════════════════════════
11064
+ * `createSectionStack` (ui/components/section_stack.js, STORY-0036): titled
11065
+ * sections in a column, built on the tree's `.twm-collapsible-*` primitive.
11066
+ *
11067
+ * THE STACK IS A COLUMN AS TALL AS ITS CONTAINER. A section is as tall as its
11068
+ * content, except the FILL section, which takes what the others leave
11069
+ * (`flex: 1 1 0`) — and only while it is open, so a closed one is a header.
11070
+ * Its body is the one that scrolls: `min-height: 0` on the box and the body is
11071
+ * what lets a flex child shrink below its content, without which a long
11072
+ * conversation grows the section, and the panel, past the window. If the
11073
+ * fixed sections alone outgrow the stack, the stack itself scrolls; the open
11074
+ * fill box then stops at its header's height (`min-height: 26px`, the header
11075
+ * rule's `height`) so its header never lands on the next section's, and only
11076
+ * its body shrinks away.
11077
+ *
11078
+ * `[hidden]` needs its own rule: the box is `display: flex`, and an author
11079
+ * `display` beats the UA's `[hidden] { display: none }`. */
11080
+ .twm-section-stack {
11081
+ display: flex;
11082
+ flex-direction: column;
11083
+ height: 100%;
11084
+ min-height: 0;
11085
+ overflow-x: hidden;
11086
+ overflow-y: auto;
11087
+ }
11088
+ .twm-section-stack > .twm-section-stack__section {
11089
+ display: flex;
11090
+ flex-direction: column;
11091
+ flex: 0 0 auto;
11092
+ min-height: 0;
11093
+ }
11094
+ .twm-section-stack > .twm-section-stack__section[hidden] {
11095
+ display: none;
11096
+ }
11097
+ .twm-section-stack > .twm-section-stack__section--fill.twm-section-stack__section--expanded {
11098
+ flex: 1 1 0;
11099
+ min-height: 26px;
11100
+ }
11101
+ .twm-section-stack__section--fill > .twm-collapsible-content.visible {
11102
+ flex: 1 1 0;
11103
+ min-height: 0;
11104
+ overflow-y: auto;
11105
+ }
11106
+ .twm-section-stack__header {
11107
+ gap: var(--space-xxs);
11108
+ height: 26px;
11109
+ padding: 0 var(--space-xs) 0 0;
11110
+ box-sizing: border-box;
11111
+ flex-shrink: 0;
11112
+ color: var(--text-secondary);
11113
+ }
11114
+ .twm-section-stack__header:hover {
11115
+ background: var(--state-hover);
11116
+ }
11117
+ /* A real button, so Enter and Space toggle without re-implementing them; the
11118
+ * first declarations are the reset that keeps the UA's own button off it. */
11119
+ .twm-section-stack__toggle {
11120
+ appearance: none;
11121
+ background: none;
11122
+ border: 0;
11123
+ color: inherit;
11124
+ font: inherit;
11125
+ text-transform: inherit;
11126
+ text-align: left;
11127
+ cursor: pointer;
11128
+ display: flex;
11129
+ align-items: center;
11130
+ gap: var(--space-xs);
11131
+ flex: 1 1 auto;
11132
+ min-width: 0;
11133
+ height: 100%;
11134
+ padding: 0 0 0 var(--space-xs);
11135
+ }
11136
+ .twm-section-stack__toggle:focus-visible,
11137
+ .twm-section-stack__action:focus-visible {
11138
+ outline: 2px solid var(--accent-bright);
11139
+ outline-offset: -2px;
8434
11140
  }
8435
-
8436
- /* ── the fact sheet ── */
8437
- .twm-facts {
8438
- width: 100%;
8439
- border-collapse: collapse;
8440
- table-layout: auto;
8441
- font-size: var(--font-size-sm, 12px);
11141
+ /* The arrow points down while open (`.twm-collapsible-arrow`) and right while
11142
+ * closed — the class `paint()` sets on it. A fixed, clipped box: without the
11143
+ * icon font the glyph is the word `expand_more`, and that word turned on its
11144
+ * side stood taller than the header and made the whole stack scroll. */
11145
+ .twm-section-stack .twm-collapsible-arrow {
11146
+ flex-shrink: 0;
11147
+ width: 12px;
11148
+ height: 12px;
11149
+ line-height: 12px;
11150
+ overflow: hidden;
8442
11151
  }
8443
- .twm-facts thead th {
8444
- text-align: left;
8445
- font-size: var(--ea-font-small, 10px);
8446
- font-weight: 600;
8447
- letter-spacing: 0.06em;
8448
- text-transform: uppercase;
8449
- color: var(--text-dim, #666);
8450
- white-space: nowrap;
8451
- border-bottom: 1px solid var(--border-default, #303030);
8452
- padding: 0 12px 6px 0;
11152
+ .twm-section-stack .twm-collapsible-arrow.collapsed {
11153
+ transform: rotate(-90deg);
8453
11154
  }
8454
- /* Every value WRAPS and keeps its own line breaks: nothing is cut off. */
8455
- .twm-facts tbody td,
8456
- .twm-facts tbody th {
8457
- text-align: left;
8458
- font-weight: normal;
8459
- border-bottom: 1px solid var(--border-subtle, #2a2a2a);
8460
- padding: 6px 12px 6px 0;
8461
- color: var(--text-primary, #e0e0e0);
8462
- vertical-align: top;
8463
- white-space: pre-wrap;
8464
- overflow-wrap: anywhere;
11155
+ .twm-section-stack__icon.material-symbols-outlined {
11156
+ font-size: var(--font-size-md);
11157
+ line-height: 1;
8465
11158
  }
8466
- .twm-facts tbody tr:last-child > td,
8467
- .twm-facts tbody tr:last-child > th { border-bottom: 0; }
8468
- .twm-facts tbody th.twm-facts__label {
8469
- color: var(--text-muted, #888);
11159
+ .twm-section-stack__title {
11160
+ flex: 1 1 auto;
11161
+ min-width: 0;
8470
11162
  white-space: nowrap;
11163
+ overflow: hidden;
11164
+ text-overflow: ellipsis;
8471
11165
  }
8472
- .twm-facts.twm-facts--wrap-labels tbody th.twm-facts__label { white-space: normal; }
8473
- /* `wrap: 'words'` — a value wraps at spaces only, and a long word stays
8474
- * whole: the sheet grows wider and a sideways scroller around it scrolls.
8475
- * `wrap: 'none'` — a value keeps its own line breaks and no others. */
8476
- .twm-facts.twm-facts--wrap-words tbody td,
8477
- .twm-facts.twm-facts--wrap-words tbody th { overflow-wrap: normal; word-break: normal; }
8478
- .twm-facts.twm-facts--wrap-none tbody td,
8479
- .twm-facts.twm-facts--wrap-none tbody th { white-space: pre; overflow-wrap: normal; word-break: normal; }
8480
- .twm-facts.twm-facts--mono tbody td.twm-facts__value,
8481
- .twm-facts tbody td.twm-facts__mono { font-family: var(--font-family-mono, monospace); font-size: 0.95em; }
8482
- .twm-facts .twm-facts__num { font-variant-numeric: tabular-nums; white-space: nowrap; }
8483
- .twm-facts tbody td.twm-facts__null,
8484
- .twm-facts tbody td.twm-facts__missing { color: var(--text-dim, #666); font-style: italic; }
8485
- .twm-facts tbody td.twm-facts__arrow {
8486
- color: var(--text-dim, #666);
8487
- white-space: nowrap;
8488
- width: 1%;
11166
+ .twm-section-stack__actions {
11167
+ display: flex;
11168
+ align-items: center;
11169
+ gap: var(--space-xxs);
11170
+ flex-shrink: 0;
8489
11171
  }
8490
- .twm-facts tbody tr.twm-facts__row--changed > td.twm-facts__before { color: var(--text-muted, #888); }
8491
- .twm-facts tbody tr.twm-facts__row--changed > td.twm-facts__arrow { color: var(--color-warning, #ff9800); }
8492
- .twm-facts tbody tr.twm-facts__row--same > td { color: var(--text-muted, #888); }
8493
- .twm-facts tbody tr.twm-facts__row--added > td.twm-facts__after { color: var(--color-success, #3fb950); }
8494
- .twm-facts tbody tr.twm-facts__row--removed > td.twm-facts__before { color: var(--color-danger, #c74e4e); }
8495
- .twm-facts tbody td.twm-facts__empty {
8496
- padding: 12px 0;
8497
- color: var(--text-muted, #888);
8498
- white-space: normal;
11172
+ .twm-section-stack__action {
11173
+ appearance: none;
11174
+ background: none;
11175
+ border: 0;
11176
+ color: var(--text-muted);
11177
+ cursor: pointer;
11178
+ display: flex;
11179
+ align-items: center;
11180
+ justify-content: center;
11181
+ width: 20px;
11182
+ height: 20px;
11183
+ padding: 0;
11184
+ border-radius: var(--radius-sm);
8499
11185
  }
8500
- /* ══ end RECORD LIST & FACT SHEET ══ */
11186
+ .twm-section-stack__action:hover {
11187
+ color: var(--text-primary);
11188
+ background: var(--state-active);
11189
+ }
11190
+ .twm-section-stack__action .material-symbols-outlined {
11191
+ font-size: var(--font-size-md);
11192
+ line-height: 1;
11193
+ }
11194
+ /* ══ end SECTION STACK ══ */
11195
+
11196
+ /* ── The conversation view (conversation_view.js, STORY-0057) ─────────────
11197
+ * A transcript of role-tagged messages above a growing composer. The view
11198
+ * fills its host; the transcript scrolls, the composer stays at the foot. */
11199
+ .twm-chat { display: flex; flex-direction: column; height: 100%; min-height: 0; font-size: var(--font-size-sm); color: var(--text-body); }
11200
+ .twm-chat .material-symbols-outlined { font-size: 18px; line-height: 1; }
11201
+ .twm-chat__viewport { position: relative; flex: 1 1 auto; min-height: 0; display: flex; flex-direction: column; }
11202
+ .twm-chat__log { flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: var(--space-md); display: flex; flex-direction: column; gap: var(--space-sm); }
11203
+ .twm-chat__msg { max-width: 85%; padding: var(--space-sm) var(--space-md); border-radius: var(--radius-md); background: var(--surface-1); line-height: var(--line-height-relaxed); overflow-wrap: anywhere; }
11204
+ .twm-chat__msg--user { align-self: flex-end; background: var(--state-selected); }
11205
+ .twm-chat__msg--assistant { align-self: flex-start; }
11206
+ .twm-chat__msg--system { align-self: center; background: none; color: var(--text-muted); font-size: var(--font-size-xs); }
11207
+ .twm-chat__msg--streaming .twm-chat__body::after { content: '\258D'; margin-left: 1px; color: var(--text-muted); }
11208
+ .twm-chat__body--plain { white-space: pre-wrap; }
11209
+ .twm-chat__extra:empty { display: none; }
11210
+ .twm-chat__extra { margin-top: var(--space-sm); }
11211
+ .twm-chat__jump { position: absolute; left: 50%; bottom: var(--space-md); transform: translateX(-50%); display: inline-flex; align-items: center; gap: 4px; height: 26px; padding: 0 12px; border: 1px solid var(--border-default); border-radius: var(--radius-pill); background: var(--surface-2); color: var(--text-body); font: inherit; font-size: var(--font-size-xs); box-shadow: var(--shadow-md); cursor: pointer; }
11212
+ .twm-chat__jump:hover { background: var(--state-hover); }
11213
+ .twm-chat__jump[hidden] { display: none; }
11214
+ .twm-chat__composer { flex: 0 0 auto; display: flex; align-items: flex-end; gap: var(--space-sm); padding: var(--space-sm) var(--space-md); border-top: 1px solid var(--border-subtle); }
11215
+ .twm-chat__input { flex: 1 1 auto; min-height: 30px; box-sizing: border-box; padding: 6px 8px; border: 1px solid var(--surface-3); border-radius: var(--radius-sm); background: var(--surface-1); color: var(--text-body); font: inherit; line-height: var(--line-height-normal); resize: none; overflow-y: hidden; }
11216
+ .twm-chat__input:focus { outline: none; border-color: var(--accent-bright); }
11217
+ .twm-chat__send { flex: 0 0 auto; display: inline-flex; align-items: center; justify-content: center; width: 30px; height: 30px; border: 0; border-radius: var(--radius-sm); background: var(--accent); color: var(--text-bright); cursor: pointer; }
11218
+ .twm-chat__send:hover { background: var(--accent-hover); }
11219
+ .twm-chat__send--cancel { background: var(--color-danger); }
11220
+ .twm-chat__send--cancel:hover { background: var(--color-danger-hover); }
11221
+ .twm-chat--busy .twm-chat__input { border-color: var(--border-default); }
8501
11222
 
8502
11223
  /* ══ FLOW · KIT ═══════════════════════════════════════════════════════ */
8503
11224
  /* The shared flow-editor kit (36 §3, §3.12): the settings panel and its
@@ -8725,10 +11446,6 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
8725
11446
  font-size: var(--font-size-xs);
8726
11447
  color: var(--twm-flow-muted);
8727
11448
  }
8728
- .twm-flow-field__kept {
8729
- font-family: var(--font-family-mono);
8730
- color: var(--text-secondary);
8731
- }
8732
11449
  .twm-flow-field__refusal {
8733
11450
  margin: 0;
8734
11451
  padding: 6px 8px;
@@ -8961,6 +11678,30 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
8961
11678
  outline-offset: 1px;
8962
11679
  }
8963
11680
 
11681
+ /* ── a column typed by name, while the columns are not known ─────────── */
11682
+ .twm-flow-field__typed {
11683
+ display: flex;
11684
+ flex-direction: column;
11685
+ min-width: 0;
11686
+ }
11687
+ .twm-flow-typedchips:focus-within {
11688
+ border-color: var(--twm-flow-focus);
11689
+ }
11690
+ .twm-flow-input.twm-flow-typedchips__input {
11691
+ flex: 1 1 8em;
11692
+ width: auto;
11693
+ min-width: 6em;
11694
+ height: 18px;
11695
+ padding: 0 2px;
11696
+ border: 0;
11697
+ background: transparent;
11698
+ font-size: var(--font-size-xs);
11699
+ }
11700
+ .twm-flow-input.twm-flow-typedchips__input:focus {
11701
+ outline: none;
11702
+ box-shadow: none;
11703
+ }
11704
+
8964
11705
  /* ── choice-cards ───────────────────────────────────────────────────── */
8965
11706
  .twm-flow-choices {
8966
11707
  display: flex;
@@ -9469,6 +12210,21 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
9469
12210
  color: var(--twm-flow-blue-fg);
9470
12211
  border-bottom: 1px solid var(--twm-flow-blue-line);
9471
12212
  }
12213
+ /* A host policy's offer: its sentence, Accept and Not now, on one line. */
12214
+ .twm-flow-graph__offer {
12215
+ display: flex;
12216
+ flex-wrap: wrap;
12217
+ align-items: center;
12218
+ gap: 6px 8px;
12219
+ padding: 4px 10px;
12220
+ font-size: var(--font-size-xs);
12221
+ background: var(--twm-flow-blue-bg);
12222
+ color: var(--twm-flow-blue-fg);
12223
+ border-bottom: 1px solid var(--twm-flow-blue-line);
12224
+ }
12225
+ .twm-flow-graph__offer-text { margin: 0; flex: 1 1 200px; min-width: 0; line-height: 1.4; }
12226
+ .twm-flow-graph__offer-accept,
12227
+ .twm-flow-graph__offer-dismiss { flex: 0 0 auto; }
9472
12228
 
9473
12229
  /* Three columns; the CANVAS is the one that flexes — `flex: 1 1 0px` AND
9474
12230
  * `min-width: 0`, or an open settings panel squeezes the palette instead. */
@@ -9540,6 +12296,8 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
9540
12296
  }
9541
12297
  .twm-flow-graph__canvas { position: relative; flex: 1 1 0px; min-width: 0; min-height: 0; }
9542
12298
  .twm-flow-graph__canvas--drop { outline: 2px dashed var(--twm-flow-focus); outline-offset: -2px; }
12299
+ /* Where a dragged line was let go: what the step picker is anchored to. */
12300
+ .twm-flow-graph__drop-anchor { position: absolute; width: 1px; height: 1px; pointer-events: none; }
9543
12301
  .twm-flow-graph__aside {
9544
12302
  flex: 0 0 300px;
9545
12303
  min-width: 0;
@@ -9671,6 +12429,168 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
9671
12429
  .twm-flow-graph__node-run[data-state="skipped"],
9672
12430
  .twm-flow-graph__node-run[data-state="cancelled"],
9673
12431
  .twm-flow-graph__node-run[data-state="not-reached"] { font-style: italic; }
12432
+ /* A TYPE'S OWN BODY, below its ports (a catalogue type's `render`). It adds
12433
+ * height under the rows and moves nothing above them, so the lines' arithmetic
12434
+ * stands. A type with `size.h` is exactly that tall: its body takes what is
12435
+ * left and clips (the node itself never does: its port dots stand out of it).
12436
+ * An `accent` is a stripe over the header, drawn on top of the box so it takes
12437
+ * no height. */
12438
+ .twm-flow-graph__node-body { box-sizing: border-box; padding: 4px 8px 6px; min-width: 0; }
12439
+ .twm-flow-graph__node--sized { display: flex; flex-direction: column; }
12440
+ .twm-flow-graph__node--sized > .twm-flow-graph__node-head,
12441
+ .twm-flow-graph__node--sized > .twm-flow-graph__node-row { flex: none; }
12442
+ .twm-flow-graph__node--sized > .twm-flow-graph__node-body { flex: 1 1 auto; min-height: 0; overflow: hidden; }
12443
+ /* EDITED ON THE NODE (`renameInPlace`, a type's `inline`). The title's box
12444
+ * takes the title's place in the header, as tall as the name was; the inline
12445
+ * settings sit under the ports, above a type's own body, and add height there
12446
+ * only, so the lines' arithmetic stands. */
12447
+ .twm-flow-graph__node-title-input {
12448
+ flex: 1 1 auto;
12449
+ min-width: 0;
12450
+ box-sizing: border-box;
12451
+ height: 22px;
12452
+ padding: 0 4px;
12453
+ background: var(--surface-1);
12454
+ border: 1px solid var(--twm-flow-focus);
12455
+ border-radius: 2px;
12456
+ color: var(--text-body);
12457
+ font: inherit;
12458
+ cursor: text;
12459
+ user-select: text;
12460
+ }
12461
+ .twm-flow-graph__node-title-input[aria-invalid="true"] { border-color: var(--twm-flow-error); }
12462
+ .twm-flow-graph__node-inline { box-sizing: border-box; display: grid; gap: 4px; padding: 4px 8px 6px; min-width: 0; cursor: default; }
12463
+ .twm-flow-graph__node--sized > .twm-flow-graph__node-inline { flex: none; }
12464
+ .twm-flow-graph__node[data-accent]::before {
12465
+ content: "";
12466
+ position: absolute;
12467
+ top: 0;
12468
+ left: 0;
12469
+ right: 0;
12470
+ height: 3px;
12471
+ border-radius: 3px 3px 0 0;
12472
+ pointer-events: none;
12473
+ }
12474
+ .twm-flow-graph__node[data-accent="violet"]::before { background: var(--twm-flow-violet-fg); }
12475
+ .twm-flow-graph__node[data-accent="teal"]::before { background: var(--twm-flow-teal-fg); }
12476
+ .twm-flow-graph__node[data-accent="amber"]::before { background: var(--twm-flow-amber-fg); }
12477
+ .twm-flow-graph__node[data-accent="grey"]::before { background: var(--twm-flow-grey-fg); }
12478
+ .twm-flow-graph__node[data-accent="blue"]::before { background: var(--twm-flow-blue-fg); }
12479
+ .twm-flow-graph__node[data-accent="indigo"]::before { background: var(--twm-flow-indigo-fg); }
12480
+
12481
+ /* ── a group and a note (`groups`, `notes`) ─────────────────────────── */
12482
+ /* A GROUP IS A FRAME BEHIND ITS NODES (first in the node layer). Its body
12483
+ * takes no pointer, so a press inside it between the nodes is the canvas's
12484
+ * (a marquee, a pan); its title bar is the handle that drags the members. */
12485
+ .twm-flow-graph__group {
12486
+ position: absolute;
12487
+ box-sizing: border-box;
12488
+ border: 1px dashed var(--border-strong);
12489
+ border-radius: 6px;
12490
+ background: rgba(255, 255, 255, 0.03);
12491
+ pointer-events: none;
12492
+ }
12493
+ .twm-flow-graph__group-head {
12494
+ box-sizing: border-box;
12495
+ height: 26px;
12496
+ display: flex;
12497
+ align-items: center;
12498
+ padding: 0 8px;
12499
+ border-radius: 6px 6px 0 0;
12500
+ background: var(--twm-flow-grey-bg);
12501
+ color: var(--twm-flow-grey-fg);
12502
+ font-size: var(--font-size-xs);
12503
+ font-weight: 600;
12504
+ cursor: move;
12505
+ pointer-events: auto;
12506
+ user-select: none;
12507
+ }
12508
+ .twm-flow-graph__group-label { flex: 1 1 auto; min-width: 0; overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
12509
+ .twm-flow-graph__group-label--untitled { font-style: italic; font-weight: 400; }
12510
+ .twm-flow-graph__group-input { flex: 1 1 auto; min-width: 0; font: inherit; }
12511
+ .twm-flow-graph__group[data-tone="violet"] { border-color: var(--twm-flow-violet-fg); }
12512
+ .twm-flow-graph__group[data-tone="teal"] { border-color: var(--twm-flow-teal-fg); }
12513
+ .twm-flow-graph__group[data-tone="amber"] { border-color: var(--twm-flow-amber-fg); }
12514
+ .twm-flow-graph__group[data-tone="grey"] { border-color: var(--twm-flow-grey-fg); }
12515
+ .twm-flow-graph__group[data-tone="blue"] { border-color: var(--twm-flow-blue-fg); }
12516
+ .twm-flow-graph__group[data-tone="indigo"] { border-color: var(--twm-flow-indigo-fg); }
12517
+ .twm-flow-graph__group[data-tone="violet"] > .twm-flow-graph__group-head { background: var(--twm-flow-violet-bg); color: var(--twm-flow-violet-fg); }
12518
+ .twm-flow-graph__group[data-tone="teal"] > .twm-flow-graph__group-head { background: var(--twm-flow-teal-bg); color: var(--twm-flow-teal-fg); }
12519
+ .twm-flow-graph__group[data-tone="amber"] > .twm-flow-graph__group-head { background: var(--twm-flow-amber-bg); color: var(--twm-flow-amber-fg); }
12520
+ .twm-flow-graph__group[data-tone="blue"] > .twm-flow-graph__group-head { background: var(--twm-flow-blue-bg); color: var(--twm-flow-blue-fg); }
12521
+ .twm-flow-graph__group[data-tone="indigo"] > .twm-flow-graph__group-head { background: var(--twm-flow-indigo-bg); color: var(--twm-flow-indigo-fg); }
12522
+ /* A NOTE is free text on the canvas, its height its text's unless it has a size. */
12523
+ .twm-flow-graph__note {
12524
+ position: absolute;
12525
+ box-sizing: border-box;
12526
+ padding: 8px 10px;
12527
+ background: var(--twm-flow-amber-bg);
12528
+ border: 1px solid var(--border-strong);
12529
+ border-radius: 4px;
12530
+ color: var(--text-body);
12531
+ font-size: var(--font-size-xs);
12532
+ cursor: move;
12533
+ overflow: hidden;
12534
+ outline: none;
12535
+ }
12536
+ .twm-flow-graph__note:focus-visible { box-shadow: 0 0 0 2px var(--twm-flow-focus); }
12537
+ .twm-flow-graph__note--picked { border-color: var(--twm-flow-focus); box-shadow: 0 0 0 1px var(--twm-flow-focus); }
12538
+ .twm-flow-graph__note-text { margin: 0; white-space: pre-wrap; overflow-wrap: anywhere; }
12539
+ .twm-flow-graph__note-text--empty { color: var(--text-muted); font-style: italic; }
12540
+ .twm-flow-graph__note-input {
12541
+ box-sizing: border-box;
12542
+ width: 100%;
12543
+ min-height: 4em;
12544
+ resize: vertical;
12545
+ font: inherit;
12546
+ cursor: text;
12547
+ }
12548
+ /* LIVE VALUES (`setNodeValues`): a value on the header, before the step's id
12549
+ * tag, and one beside a port. Neither element exists until there is a value. */
12550
+ .twm-flow-graph__node-badge {
12551
+ flex: none;
12552
+ max-width: 40%;
12553
+ padding: 1px 5px;
12554
+ overflow: hidden;
12555
+ text-overflow: ellipsis;
12556
+ white-space: nowrap;
12557
+ font-size: var(--font-size-xs);
12558
+ font-weight: 600;
12559
+ font-variant-numeric: tabular-nums;
12560
+ color: var(--text-body);
12561
+ background: rgba(127, 127, 127, 0.18);
12562
+ border-radius: 2px;
12563
+ }
12564
+ .twm-flow-graph__node-badge[data-tone="violet"] { background: var(--twm-flow-violet-bg); color: var(--twm-flow-violet-fg); }
12565
+ .twm-flow-graph__node-badge[data-tone="teal"] { background: var(--twm-flow-teal-bg); color: var(--twm-flow-teal-fg); }
12566
+ .twm-flow-graph__node-badge[data-tone="amber"] { background: var(--twm-flow-amber-bg); color: var(--twm-flow-amber-fg); }
12567
+ .twm-flow-graph__node-badge[data-tone="grey"] { background: var(--twm-flow-grey-bg); color: var(--twm-flow-grey-fg); }
12568
+ .twm-flow-graph__node-badge[data-tone="blue"] { background: var(--twm-flow-blue-bg); color: var(--twm-flow-blue-fg); }
12569
+ .twm-flow-graph__node-badge[data-tone="indigo"] { background: var(--twm-flow-indigo-bg); color: var(--twm-flow-indigo-fg); }
12570
+ .twm-flow-graph__port-value { color: var(--twm-flow-muted); font-variant-numeric: tabular-nums; white-space: nowrap; }
12571
+
12572
+ /* FOLDED TO ITS HEADER (`collapsible`): everything under the header is hidden,
12573
+ * and the lines meet the header's middle. */
12574
+ .twm-flow-graph__node-fold {
12575
+ flex: none;
12576
+ display: inline-flex;
12577
+ align-items: center;
12578
+ justify-content: center;
12579
+ width: 20px;
12580
+ height: 20px;
12581
+ padding: 0;
12582
+ background: none;
12583
+ border: 0;
12584
+ border-radius: 3px;
12585
+ color: var(--twm-flow-muted);
12586
+ cursor: pointer;
12587
+ }
12588
+ .twm-flow-graph__node-fold .material-symbols-outlined { font-size: 16px; }
12589
+ .twm-flow-graph__node-fold:hover:not(:disabled) { color: var(--text-body); background: rgba(127, 127, 127, 0.18); }
12590
+ .twm-flow-graph__node-fold:focus-visible { outline: 1px solid var(--twm-flow-focus); }
12591
+ .twm-flow-graph__node-fold:disabled { cursor: default; opacity: 0.5; }
12592
+ .twm-flow-graph__node--collapsed > :not(.twm-flow-graph__node-head) { display: none; }
12593
+ .twm-flow-graph__node--collapsed > .twm-flow-graph__node-head { border-bottom: 0; border-radius: 3px; }
9674
12594
 
9675
12595
  /* ── a port ─────────────────────────────────────────────────────────── */
9676
12596
  .twm-flow-graph__port {
@@ -9712,16 +12632,53 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
9712
12632
  .twm-flow-graph__port--taken .twm-flow-graph__port-dot { border-color: var(--twm-flow-ok); background: var(--twm-flow-ok); }
9713
12633
  .twm-flow-graph__port--taken .twm-flow-graph__port-count { color: var(--twm-flow-ok); }
9714
12634
  .twm-flow-graph__port--untaken { opacity: 0.5; }
12635
+ /* Under `connections` only the inputs the line may end on are lit. */
12636
+ .twm-flow-graph--linking.twm-flow-graph--connecting .twm-flow-graph__port--in:not(.twm-flow-graph__port--can-drop) .twm-flow-graph__port-dot {
12637
+ background: transparent;
12638
+ box-shadow: none;
12639
+ }
12640
+ .twm-flow-graph__port--can-drop .twm-flow-graph__port-dot {
12641
+ background: var(--twm-flow-focus);
12642
+ box-shadow: 0 0 0 3px rgba(0, 122, 204, 0.35);
12643
+ }
9715
12644
 
9716
12645
  /* ── a line ─────────────────────────────────────────────────────────── */
9717
- .twm-flow-graph__edge { fill: none; stroke: #8fb4d8; stroke-width: 1.6; }
9718
- .twm-flow-graph__edge--picked { stroke: var(--twm-flow-focus); stroke-width: 2.4; }
12646
+ /* A consumer's `edgeStyle` sets a line's tone and width as the two custom
12647
+ * properties below (unset, the line is drawn as it always was); a picked or
12648
+ * highlighted line still wins over them. */
12649
+ .twm-flow-graph__edge { fill: none; stroke: var(--twm-canvas-edge-stroke, #8fb4d8); stroke-width: var(--twm-canvas-edge-width, 1.6); }
12650
+ .twm-flow-graph__edge--picked { stroke: var(--twm-flow-focus); stroke-width: calc(var(--twm-canvas-edge-width, 1.6) + 0.8); }
9719
12651
  /* A loop's body handing back to the loop: DASHED, and routed under both steps
9720
12652
  * by the kernel's `returnEdge`, so it reads as "round again", not as one more
9721
12653
  * step forward. */
9722
12654
  .twm-flow-graph__edge--back { stroke-dasharray: 6 4; }
9723
12655
  .twm-flow-graph__edge--taken { stroke: var(--twm-flow-ok); }
9724
12656
  .twm-flow-graph__edge--untaken { opacity: 0.35; }
12657
+ /* The line a drag is drawing: dashed, and never what the release lands on. */
12658
+ .twm-flow-graph__edge--draft { stroke: var(--twm-flow-focus); stroke-dasharray: 4 3; pointer-events: none; }
12659
+ /* `edgeStyle`'s dash, and the lines of a hovered step or line (`hoverHighlight`). */
12660
+ .twm-flow-graph__edge--dashed { stroke-dasharray: 6 4; }
12661
+ .twm-flow-graph__edge--highlighted { stroke: var(--twm-flow-focus); stroke-width: calc(var(--twm-canvas-edge-width, 1.6) + 0.8); }
12662
+ /* `edgeStyle`'s marker at the input end: an arrowhead, or the kernel's crow's
12663
+ * foot in the line's own tone. */
12664
+ .twm-flow-graph__edge-arrow { fill: var(--twm-canvas-edge-stroke, #8fb4d8); stroke: none; }
12665
+ .twm-flow-graph__edge-foot line, .twm-flow-graph__edge-foot circle { stroke: var(--twm-canvas-edge-stroke, #8fb4d8); }
12666
+ /* `edgeStyle`'s label, centred on the line's midpoint. Its layer sits in the
12667
+ * node layer and is stacked ABOVE every node, so a step never hides it. */
12668
+ .twm-flow-graph__edge-labels { position: absolute; left: 0; top: 0; z-index: 1; }
12669
+ .twm-flow-graph__edge-label {
12670
+ position: absolute;
12671
+ transform: translate(-50%, -50%);
12672
+ padding: 1px 6px;
12673
+ border: 1px solid var(--twm-canvas-edge-stroke, #8fb4d8);
12674
+ border-radius: 8px;
12675
+ background: var(--surface-2);
12676
+ color: var(--text-body);
12677
+ font-size: var(--font-size-xs);
12678
+ line-height: 1.4;
12679
+ white-space: nowrap;
12680
+ cursor: pointer;
12681
+ }
9725
12682
  /* The 14px target a 1.6px line cannot be. It opts back into the pointer the
9726
12683
  * edge layer turns off. */
9727
12684
  .twm-flow-graph__edge-hit { fill: none; stroke: transparent; stroke-width: 14; pointer-events: stroke; cursor: pointer; }
@@ -9748,6 +12705,26 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
9748
12705
  }
9749
12706
  .twm-flow-graph__ghost .material-symbols-outlined { font-size: 16px; }
9750
12707
  .twm-flow-graph__ghost-label { white-space: nowrap; }
12708
+ /* ── the minimap (`minimap: true`) ──────────────────────────────────── */
12709
+ /* Over the canvas's lower right corner, a sibling of the kernel's surface, so a
12710
+ * press on it starts no pan and no marquee. The steps are drawn as boxes and
12711
+ * the view as a frame; a click centres the view there. */
12712
+ .twm-flow-graph__minimap {
12713
+ position: absolute;
12714
+ right: 12px;
12715
+ bottom: 12px;
12716
+ z-index: 1;
12717
+ line-height: 0;
12718
+ background: var(--surface-1);
12719
+ border: 1px solid var(--border-default);
12720
+ border-radius: 4px;
12721
+ box-shadow: var(--shadow-md);
12722
+ cursor: pointer;
12723
+ opacity: 0.92;
12724
+ }
12725
+ .twm-flow-graph__minimap-map { display: block; }
12726
+ .twm-flow-graph__minimap-node { fill: var(--border-strong); }
12727
+ .twm-flow-graph__minimap-view { fill: none; stroke: var(--twm-flow-focus); stroke-width: 1.5px; }
9751
12728
  /* ══ end FLOW · CANVAS ══ */
9752
12729
 
9753
12730
 
@@ -11331,4 +14308,33 @@ button.twm-alerts__ref:hover { text-decoration: underline; }
11331
14308
  font-size: 16px;
11332
14309
  line-height: 1;
11333
14310
  }
14311
+ .twm-flow-lanes__menu-item.twm-flow-lanes__menu-item--refused {
14312
+ height: auto;
14313
+ min-height: 28px;
14314
+ padding-top: 4px;
14315
+ padding-bottom: 4px;
14316
+ }
14317
+ .twm-flow-lanes__menu-words {
14318
+ display: flex;
14319
+ flex-direction: column;
14320
+ min-width: 0;
14321
+ }
14322
+ .twm-flow-lanes__menu-label {
14323
+ white-space: nowrap;
14324
+ }
14325
+ .twm-flow-lanes__menu-reason {
14326
+ max-width: 280px;
14327
+ font-size: var(--font-size-xs);
14328
+ white-space: normal;
14329
+ }
14330
+ .twm-flow-lanes__menu-sep {
14331
+ height: 1px;
14332
+ margin: 4px 6px;
14333
+ background: var(--border-subtle);
14334
+ }
14335
+ .twm-flow-lanes__notice {
14336
+ margin-left: 8px;
14337
+ color: var(--twm-flow-warning);
14338
+ font-size: var(--font-size-xs);
14339
+ }
11334
14340
  /* ══ end FLOW · LANES ══ */