mbeditor 0.12.9 → 0.13.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 (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +216 -1
  3. data/app/assets/javascripts/mbeditor/application.js +6 -1
  4. data/app/assets/javascripts/mbeditor/collaboration_service.js +52 -1
  5. data/app/assets/javascripts/mbeditor/color_provider.js +5 -0
  6. data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +3 -1
  7. data/app/assets/javascripts/mbeditor/components/EditorPanel.js +104 -103
  8. data/app/assets/javascripts/mbeditor/components/FileTree.js +6 -1
  9. data/app/assets/javascripts/mbeditor/components/GitPanel.js +10 -1
  10. data/app/assets/javascripts/mbeditor/components/ImportConflictModal.js +15 -8
  11. data/app/assets/javascripts/mbeditor/components/ImportDialog.js +224 -0
  12. data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +704 -270
  13. data/app/assets/javascripts/mbeditor/components/ModelGraph.js +25 -6
  14. data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +193 -9
  15. data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +105 -77
  16. data/app/assets/javascripts/mbeditor/components/TabBar.js +11 -2
  17. data/app/assets/javascripts/mbeditor/editor_plugins.js +434 -112
  18. data/app/assets/javascripts/mbeditor/file_import.js +78 -0
  19. data/app/assets/javascripts/mbeditor/file_service.js +112 -18
  20. data/app/assets/javascripts/mbeditor/git_service.js +130 -8
  21. data/app/assets/javascripts/mbeditor/history_service.js +33 -38
  22. data/app/assets/javascripts/mbeditor/log_service.js +4 -0
  23. data/app/assets/javascripts/mbeditor/search_service.js +50 -4
  24. data/app/assets/javascripts/mbeditor/tab_manager.js +66 -10
  25. data/app/assets/javascripts/mbeditor/websocket_service.js +88 -4
  26. data/app/assets/stylesheets/mbeditor/editor.css +389 -103
  27. data/app/channels/mbeditor/channel_authentication.rb +7 -0
  28. data/app/channels/mbeditor/collaboration_channel.rb +8 -2
  29. data/app/channels/mbeditor/editor_channel.rb +6 -3
  30. data/app/controllers/mbeditor/application_controller.rb +5 -0
  31. data/app/controllers/mbeditor/editors_controller.rb +155 -29
  32. data/app/controllers/mbeditor/git_controller.rb +3 -3
  33. data/app/controllers/mbeditor/logs_controller.rb +3 -1
  34. data/app/services/mbeditor/collaboration_doc_store.rb +49 -3
  35. data/app/services/mbeditor/duplicate_content_scanner.rb +105 -0
  36. data/app/services/mbeditor/editor_state_service.rb +36 -26
  37. data/app/services/mbeditor/exclusion_matcher.rb +12 -10
  38. data/app/services/mbeditor/file_operation_service.rb +38 -3
  39. data/app/services/mbeditor/file_tree_service.rb +30 -2
  40. data/app/services/mbeditor/git_combined_diff_service.rb +13 -3
  41. data/app/services/mbeditor/git_commit_detail_service.rb +20 -16
  42. data/app/services/mbeditor/git_diff_service.rb +5 -1
  43. data/app/services/mbeditor/git_info_service.rb +20 -8
  44. data/app/services/mbeditor/git_line_diff_service.rb +6 -2
  45. data/app/services/mbeditor/git_service.rb +65 -15
  46. data/app/services/mbeditor/js_definition_service.rb +3 -1
  47. data/app/services/mbeditor/js_globals_service.rb +18 -4
  48. data/app/services/mbeditor/js_members_service.rb +3 -2
  49. data/app/services/mbeditor/js_program_service.rb +15 -5
  50. data/app/services/mbeditor/js_syntax_check_service.rb +15 -4
  51. data/app/services/mbeditor/process_runner.rb +42 -12
  52. data/app/services/mbeditor/ri_definition_service.rb +8 -1
  53. data/app/services/mbeditor/route_service.rb +45 -8
  54. data/app/services/mbeditor/rubocop_run_service.rb +98 -0
  55. data/app/services/mbeditor/ruby_definition_service.rb +23 -4
  56. data/app/services/mbeditor/schema_service.rb +73 -66
  57. data/app/services/mbeditor/search_replace_service.rb +42 -7
  58. data/app/services/mbeditor/test_runner_service.rb +45 -10
  59. data/lib/mbeditor/cable_log_filter.rb +8 -2
  60. data/lib/mbeditor/configuration.rb +4 -1
  61. data/lib/mbeditor/editor_bootstrap.rb +18 -12
  62. data/lib/mbeditor/engine.rb +22 -3
  63. data/lib/mbeditor/exception_log.rb +2 -2
  64. data/lib/mbeditor/pending_migrations.rb +33 -0
  65. data/lib/mbeditor/rack/handle_pending_migrations.rb +25 -15
  66. data/lib/mbeditor/rack/pending_migration_bypass.rb +104 -0
  67. data/lib/mbeditor/rack/silence_ping_request.rb +10 -2
  68. data/lib/mbeditor/route_map.rb +1 -0
  69. data/lib/mbeditor/ruby_lsp_client.rb +71 -12
  70. data/lib/mbeditor/version.rb +1 -1
  71. data/lib/tasks/mbeditor.rake +23 -0
  72. metadata +8 -2
@@ -8,10 +8,86 @@
8
8
  box-shadow: none !important;
9
9
  }
10
10
 
11
- /* Prevent Pico CSS from applying its primary-colour focus/active state to IDE buttons.
12
- Pico sets --pico-background-color and --pico-border-color to its primary blue on
13
- button:focus, which bleeds through as a blue tint on any button not explicitly
14
- overriding its background with a literal value. */
11
+ /* ── Pico containment ──────────────────────────────────────────────────────
12
+ *
13
+ * The host app's stylesheet loads alongside this one, and when it is Pico (the
14
+ * dummy app's is) it styles bare element and ARIA selectors — `button`,
15
+ * `[role=button]`, `[role=group]`, `[disabled]` — every one of which matches
16
+ * this UI's markup. Two shipped bugs came from exactly that: `[role=button]`
17
+ * painted the tab close buttons as filled primary squares, and
18
+ * `[disabled] { pointer-events: none }` stopped disabled toolbar buttons from
19
+ * receiving hover at all, so their tooltips never appeared.
20
+ *
21
+ * The technique is to zero Pico's own custom properties: every Pico button
22
+ * declaration reads from them, so neutralising the variables disarms the whole
23
+ * ruleset at once, which beats fighting each declaration on specificity. Only
24
+ * the few properties Pico hard-codes need explicit overrides.
25
+ *
26
+ * Two things make this safe to widen, and both depend on where the block sits:
27
+ *
28
+ * - The root is wrapped in :where(), so these rules carry exactly Pico's own
29
+ * specificity, no more. They win over Pico only because the whole of
30
+ * editor.css is bundled after the whole of Pico.
31
+ * - The block lives at the TOP of this file, so every component rule below —
32
+ * .statusbar-btn, .project-actions, .tab-close — is later at equal or
33
+ * greater specificity and still wins.
34
+ *
35
+ * That combination is what makes this a floor rather than a new source of
36
+ * truth: it can only remove Pico's opinions, never override this file's own.
37
+ * Moving it further down would silently invert that.
38
+ *
39
+ * Deliberately NOT reset: Pico's `*` box-sizing and background-repeat rules
40
+ * (this UI assumes border-box, which is what Pico sets), and `position` on
41
+ * [role=group] (Pico's `relative` is the safer value for any absolutely
42
+ * positioned descendant, and nothing here needs it to be static).
43
+ *
44
+ * Monaco is listed separately because its widgets portal out of the shell to
45
+ * document.body (.context-view, .action-widget), so a #mbeditor-root scope
46
+ * would miss them. Monaco renders its own actions as <a role="button">.
47
+ */
48
+ :where(#mbeditor-root) :is(button, [type="button"], [type="submit"], [type="reset"], [role="button"], [role="group"]),
49
+ .monaco-hover [role=button],
50
+ .action-widget [role=button],
51
+ .context-view [role=button],
52
+ .monaco-editor [role=button],
53
+ .monaco-diff-editor [role=button] {
54
+ --pico-background-color: transparent;
55
+ --pico-border-color: transparent;
56
+ --pico-color: inherit;
57
+ --pico-box-shadow: none;
58
+ --pico-form-element-spacing-vertical: 0;
59
+ --pico-form-element-spacing-horizontal: 0;
60
+ }
61
+
62
+ /* Properties Pico hard-codes, which the variable reset above cannot reach. */
63
+
64
+ /* Pico's `[disabled] { pointer-events: none }` means a disabled control never
65
+ * receives hover, so neither a title attribute nor a CSS hover tooltip can
66
+ * fire on it — and a disabled button is exactly when a user most wants to be
67
+ * told what it is. */
68
+ :where(#mbeditor-root) [disabled],
69
+ :where(#mbeditor-root) [aria-disabled="true"] {
70
+ pointer-events: auto;
71
+ }
72
+
73
+ /* Pico lays [role=group] out as a full-width bordered input group. It is used
74
+ * here only to label toolbars for screen readers. */
75
+ :where(#mbeditor-root) [role="group"] {
76
+ display: inline-flex;
77
+ width: auto;
78
+ margin-bottom: 0;
79
+ }
80
+
81
+ /* Pico gives every button a bottom margin to space form controls. */
82
+ :where(#mbeditor-root) :is(button, [type="button"], [type="submit"], [type="reset"]) {
83
+ margin: 0;
84
+ }
85
+
86
+ /* Pico's focus/active state sets its primary blue on --pico-background-color
87
+ and --pico-border-color, which bleeds through as a blue tint on any button
88
+ not overriding its background with a literal value. Kept separate from the
89
+ block above: this one is intentionally more specific than a component rule,
90
+ because no component should be able to opt back into a Pico focus colour. */
15
91
  #mbeditor-root button:not(.pico-btn):focus,
16
92
  #mbeditor-root button:not(.pico-btn):focus-visible,
17
93
  #mbeditor-root button:not(.pico-btn):active {
@@ -129,57 +205,66 @@ html, body, #mbeditor-root {
129
205
 
130
206
  /* ── Activity Bar (VSCode-style, always visible) ───────── */
131
207
  .ide-activity-bar {
132
- width: 48px;
133
- min-width: 48px;
208
+ width: 40px;
209
+ min-width: 40px;
134
210
  flex-shrink: 0;
135
211
  background: var(--ide-panel-bg);
136
212
  border-right: 1px solid var(--ide-border);
137
213
  display: flex;
138
214
  flex-direction: column;
139
- align-items: center;
140
- padding: 4px 0;
215
+ /* stretch, not center: the buttons fill the bar edge to edge so the active
216
+ indicator can sit flush against the left of the screen. Centering them
217
+ left a gutter the indicator was inset by. */
218
+ align-items: stretch;
219
+ padding: 0;
141
220
  z-index: 10;
142
221
  }
143
222
 
144
223
  .ide-activity-bar-top {
145
224
  display: flex;
146
225
  flex-direction: column;
147
- align-items: center;
148
- gap: 2px;
226
+ align-items: stretch;
227
+ gap: 0;
149
228
  flex: 1;
150
229
  }
151
230
 
152
231
  .ide-activity-bar-bottom {
153
232
  display: flex;
154
233
  flex-direction: column;
155
- align-items: center;
156
- gap: 2px;
157
- padding-bottom: 4px;
234
+ align-items: stretch;
235
+ gap: 0;
158
236
  }
159
237
 
160
- .ide-activity-btn {
161
- width: 36px;
162
- height: 36px;
238
+ /* Scoped through the bar so the glyph size outranks the global
239
+ `button:not(.pico-btn) { font-size: 12px }` reset, which is more specific
240
+ than a bare class and was silently winning. */
241
+ .ide-activity-bar .ide-activity-btn {
242
+ width: 100%;
243
+ height: 40px;
163
244
  background: transparent;
164
245
  border: none;
246
+ /* Always reserve the indicator's width so the glyph does not shift sideways
247
+ by a pixel the moment a panel is opened. */
248
+ border-left: 2px solid transparent;
165
249
  border-radius: 0;
166
250
  color: var(--ide-text-muted);
167
251
  display: flex;
168
252
  align-items: center;
169
253
  justify-content: center;
170
254
  cursor: pointer;
171
- font-size: 15px;
172
- transition: background 0.12s ease, color 0.12s ease;
255
+ font-size: 17px;
256
+ transition: color 0.12s ease;
173
257
  }
174
258
 
175
- .ide-activity-btn:hover {
176
- background: var(--ide-hover-bg);
259
+ /* The glyph carries the state, not a filled box behind it. */
260
+ .ide-activity-bar .ide-activity-btn:hover {
261
+ background: transparent;
177
262
  color: var(--ide-text);
178
263
  }
179
264
 
180
- .ide-activity-btn.active {
265
+ .ide-activity-bar .ide-activity-btn.active {
181
266
  color: var(--ide-accent-fg);
182
- border-left: 2px solid var(--ide-accent-fg);
267
+ border-left-color: var(--ide-accent-fg);
183
268
  }
184
269
 
185
270
  .sidebar-panel-title {
@@ -300,25 +385,25 @@ html, body, #mbeditor-root {
300
385
  border: 1px solid var(--ide-accent);
301
386
  border-radius: 3px;
302
387
  color: var(--ide-text);
303
- font-size: 12px;
388
+ font-size: 14px;
304
389
  font-family: inherit;
305
390
  padding: 1px 4px;
306
391
  outline: none;
307
- height: 18px;
308
- line-height: 18px;
392
+ height: 21px;
393
+ line-height: 21px;
309
394
  }
310
395
 
311
396
  .tree-item-icon {
312
- width: 16px;
313
- margin-right: 5px;
314
- font-size: 12px;
397
+ width: 20px;
398
+ margin-right: 6px;
399
+ font-size: 14px;
315
400
  flex-shrink: 0;
316
401
  text-align: center;
317
402
  }
318
403
 
319
404
  .tree-folder-icon { color: #dcb67a; }
320
405
  .tree-file-icon { color: #78b4e0; }
321
- .tree-item-name { flex: 1; overflow: hidden; text-overflow: ellipsis; font-size: 12px; }
406
+ .tree-item-name { flex: 1; overflow: hidden; text-overflow: ellipsis; font-size: 14px; }
322
407
 
323
408
  /* ── Custom File Icons ───────────────────────────────────── */
324
409
  .ruby-icon { color: #f26262 !important; }
@@ -791,23 +876,33 @@ html, body, #mbeditor-root {
791
876
  }
792
877
 
793
878
  /* ── Status Bar ──────────────────────────────────────────── */
879
+ /* line-height: 1 is what keeps an icon and the number beside it on the same
880
+ * centre line. Pico's inherited 1.5 gave every text span a 16.5px line box
881
+ * inside a 22px bar while the icons' box was 11px, so the two were centred on
882
+ * different half-leadings; with 1 both boxes are the font's own content box
883
+ * and flex centres them together. */
794
884
  .ide-statusbar {
795
885
  display: flex;
796
886
  align-items: center;
797
887
  background: var(--ide-accent);
798
888
  height: 22px;
799
- padding: 0 8px;
800
- gap: 4px;
889
+ padding: 0;
890
+ gap: 0;
801
891
  flex-shrink: 0;
802
892
  font-size: 11px;
893
+ line-height: 1;
803
894
  color: var(--ide-accent-text);
804
895
  }
805
896
 
897
+ /* One cell rhythm for every item, button or not. The branch, the message and
898
+ * the chips each carried their own padding on top of the bar's, so the gaps
899
+ * between them were all different widths. */
900
+ .ide-statusbar > * { padding: 0 8px; }
901
+
806
902
  .statusbar-branch {
807
903
  display: flex;
808
904
  align-items: center;
809
- gap: 6px;
810
- font-size: 13px;
905
+ gap: 4px;
811
906
  font-weight: 600;
812
907
  color: var(--ide-accent-text);
813
908
  }
@@ -841,7 +936,6 @@ html, body, #mbeditor-root {
841
936
  font-size: 12px;
842
937
  font-weight: 600;
843
938
  color: var(--ide-danger);
844
- padding: 0 10px;
845
939
  animation: statusbar-pulse 2s ease-in-out infinite;
846
940
  }
847
941
 
@@ -941,7 +1035,6 @@ html, body, #mbeditor-root {
941
1035
  color: color-mix(in srgb, var(--ide-accent-text) 45%, transparent);
942
1036
  font-size: 10px;
943
1037
  white-space: nowrap;
944
- padding-left: 8px;
945
1038
  cursor: pointer;
946
1039
  }
947
1040
  .statusbar-version:hover {
@@ -954,7 +1047,6 @@ html, body, #mbeditor-root {
954
1047
  font-weight: 600;
955
1048
  letter-spacing: 0.04em;
956
1049
  opacity: 0.85;
957
- padding: 0 6px;
958
1050
  }
959
1051
 
960
1052
  .statusbar-zen-btn {
@@ -1140,52 +1232,88 @@ html, body, #mbeditor-root {
1140
1232
  to { transform: rotate(360deg); }
1141
1233
  }
1142
1234
 
1235
+ /* Tree rows. Both kinds are pinned to the same 22px because MbeditorApp
1236
+ windows them by index * SEARCH_ROW_HEIGHT — change one and change both. */
1237
+ .search-result-file-row,
1143
1238
  .search-result-item {
1144
- height: 40px;
1145
- padding: 4px 6px;
1239
+ height: 22px;
1146
1240
  cursor: pointer;
1147
- border-radius: 3px;
1148
1241
  transition: background 0.1s;
1149
- border-bottom: 1px solid var(--ide-input-bg);
1150
1242
  display: flex;
1151
1243
  align-items: center;
1152
- gap: 8px;
1244
+ gap: 6px;
1153
1245
  overflow: hidden;
1154
1246
  box-sizing: border-box;
1155
1247
  flex-shrink: 0;
1156
1248
  }
1157
1249
 
1250
+ .search-result-file-row { padding: 0 6px; }
1251
+ /* Indented under its file header, the way the explorer nests children. */
1252
+ .search-result-item { padding: 0 6px 0 24px; }
1253
+
1254
+ .search-result-file-row:hover,
1158
1255
  .search-result-item:hover { background: var(--ide-hover-bg); }
1159
1256
 
1257
+ .search-result-chevron {
1258
+ flex-shrink: 0;
1259
+ width: 12px;
1260
+ font-size: 12px;
1261
+ color: var(--ide-text-muted);
1262
+ }
1263
+
1160
1264
  .search-result-icon {
1161
1265
  flex-shrink: 0;
1162
1266
  width: 16px;
1163
- margin-top: 2px;
1164
1267
  text-align: center;
1165
1268
  color: #78b4e0;
1166
1269
  }
1167
1270
 
1168
- .search-result-body {
1169
- min-width: 0;
1170
- flex: 1;
1271
+ .search-result-file-name {
1272
+ font-size: 12px;
1273
+ color: var(--ide-text);
1274
+ white-space: nowrap;
1275
+ flex-shrink: 0;
1276
+ max-width: 60%;
1277
+ overflow: hidden;
1278
+ text-overflow: ellipsis;
1171
1279
  }
1172
1280
 
1173
- .search-result-file {
1174
- font-size: 11px;
1175
- color: var(--ide-accent-fg);
1176
- font-weight: 500;
1281
+ .search-result-file-dir {
1282
+ font-size: 10px;
1283
+ color: var(--ide-text-muted);
1177
1284
  white-space: nowrap;
1178
1285
  overflow: hidden;
1179
1286
  text-overflow: ellipsis;
1287
+ flex: 1;
1288
+ min-width: 0;
1289
+ }
1290
+
1291
+ /* Match count, VS Code's little pill on the right of the file row. */
1292
+ .search-result-count {
1293
+ flex-shrink: 0;
1294
+ margin-left: auto;
1295
+ min-width: 16px;
1296
+ padding: 0 5px;
1297
+ border-radius: 9px;
1298
+ background: var(--ide-input-bg);
1299
+ color: var(--ide-text-muted);
1300
+ font-size: 10px;
1301
+ line-height: 15px;
1302
+ text-align: center;
1180
1303
  }
1181
1304
 
1182
1305
  .search-result-line-num {
1306
+ flex-shrink: 0;
1183
1307
  font-size: 10px;
1184
1308
  color: var(--ide-text-muted);
1185
- margin-left: 4px;
1309
+ font-family: monospace;
1310
+ min-width: 28px;
1311
+ text-align: right;
1186
1312
  }
1187
1313
 
1188
1314
  .search-result-text {
1315
+ flex: 1;
1316
+ min-width: 0;
1189
1317
  font-size: 11px;
1190
1318
  color: var(--ide-text-muted);
1191
1319
  font-family: monospace;
@@ -1194,6 +1322,47 @@ html, body, #mbeditor-root {
1194
1322
  text-overflow: ellipsis;
1195
1323
  }
1196
1324
 
1325
+ /* The match has to stay on screen at any panel width, so a hit line is three
1326
+ boxes rather than one ellipsised string. Shrink order is what does the work:
1327
+ the text BEFORE the match gives way first, then the text after it, and the
1328
+ match itself only as a last resort — so it can never be the part that scrolls
1329
+ out of view. `direction: rtl` is what moves the leading ellipsis to the start
1330
+ of .search-result-pre; searchMatchParts() prefixes a U+200E so an all-symbol
1331
+ segment can't be reordered by it. */
1332
+ .search-result-text-split {
1333
+ display: flex;
1334
+ align-items: center;
1335
+ text-overflow: clip;
1336
+ }
1337
+
1338
+ .search-result-pre,
1339
+ .search-result-post,
1340
+ .search-result-match {
1341
+ overflow: hidden;
1342
+ text-overflow: ellipsis;
1343
+ white-space: pre;
1344
+ }
1345
+
1346
+ .search-result-pre {
1347
+ flex: 0 100 auto;
1348
+ direction: rtl;
1349
+ text-align: left;
1350
+ }
1351
+
1352
+ .search-result-post { flex: 0 10 auto; }
1353
+
1354
+ /* Never shrinks: flex distributes overflow across every shrinkable box in
1355
+ proportion to its size, so even shrink:1 against a long tail cost the match a
1356
+ character or two of ellipsis. max-width is what still bounds a match wider
1357
+ than the panel — it clips instead of overflowing. */
1358
+ .search-result-match {
1359
+ flex: 0 0 auto;
1360
+ max-width: 100%;
1361
+ background: color-mix(in srgb, var(--ide-accent) 40%, transparent);
1362
+ color: var(--ide-text);
1363
+ border-radius: 2px;
1364
+ }
1365
+
1197
1366
  .search-results-loading-more {
1198
1367
  padding: 8px 10px;
1199
1368
  font-size: 11px;
@@ -1412,7 +1581,90 @@ html, body, #mbeditor-root {
1412
1581
  cursor: pointer;
1413
1582
  }
1414
1583
  .import-conflict-btn:hover { background: var(--ide-hover-bg, #4a4a4a); }
1584
+ .import-conflict-btn:disabled { opacity: 0.45; cursor: default; }
1415
1585
  .import-conflict-btn-danger { border-color: var(--ide-error, #f48771); color: var(--ide-error, #f48771); }
1586
+ .import-conflict-btn-primary {
1587
+ background: var(--ide-accent, #0e639c);
1588
+ border-color: var(--ide-accent, #0e639c);
1589
+ color: #fff;
1590
+ }
1591
+ .import-conflict-btn-primary:hover:not(:disabled) { background: var(--ide-accent-hover, #1177bb); }
1592
+
1593
+ /* Upload dialog — the pointed-at half of drag-and-drop import */
1594
+ .import-dialog { min-width: 460px; max-width: 620px; }
1595
+ .import-dialog .schema-modal-body { padding: 12px 16px; }
1596
+ .import-pick-row { display: flex; gap: 8px; }
1597
+ .import-pick-btn {
1598
+ background: var(--ide-input-bg, #3c3c3c);
1599
+ color: var(--ide-text, #d4d4d4);
1600
+ border: 1px solid var(--ide-border, #3c3c3c);
1601
+ border-radius: 3px;
1602
+ padding: 6px 12px;
1603
+ font-size: 12px;
1604
+ cursor: pointer;
1605
+ }
1606
+ .import-pick-btn:hover { background: var(--ide-hover-bg, #4a4a4a); }
1607
+ .import-pick-btn i { margin-right: 6px; opacity: 0.75; }
1608
+ .import-dialog-empty,
1609
+ .import-dialog-warning {
1610
+ margin-top: 12px;
1611
+ font-size: 12px;
1612
+ color: var(--ide-text-muted, #888);
1613
+ }
1614
+ .import-dialog-warning { color: var(--ide-warning, #cca700); }
1615
+ .import-dialog-section-title {
1616
+ margin: 16px 0 6px;
1617
+ font-size: 10px;
1618
+ font-weight: 600;
1619
+ text-transform: uppercase;
1620
+ letter-spacing: 0.06em;
1621
+ color: var(--ide-text-muted, #888);
1622
+ }
1623
+ .import-dest-options { display: flex; flex-direction: column; gap: 2px; }
1624
+ .import-dest-option {
1625
+ display: flex;
1626
+ align-items: center;
1627
+ gap: 8px;
1628
+ width: 100%;
1629
+ text-align: left;
1630
+ background: none;
1631
+ border: none;
1632
+ border-radius: 3px;
1633
+ padding: 4px 6px;
1634
+ color: var(--ide-text, #d4d4d4);
1635
+ font-size: 12px;
1636
+ cursor: pointer;
1637
+ }
1638
+ .import-dest-option:hover { background: var(--ide-hover, rgba(255,255,255,0.06)); }
1639
+ .import-dest-option.active { background: var(--ide-selection-bg, rgba(14,99,156,0.35)); }
1640
+ .import-dest-radio { opacity: 0.7; font-size: 11px; }
1641
+ .import-dest-label {
1642
+ font-family: var(--ide-mono-font, monospace);
1643
+ overflow: hidden;
1644
+ text-overflow: ellipsis;
1645
+ white-space: nowrap;
1646
+ }
1647
+ .import-dest-hint { color: var(--ide-text-muted, #888); font-size: 11px; margin-left: auto; flex-shrink: 0; }
1648
+ .import-dest-input {
1649
+ width: 100%;
1650
+ margin-top: 8px;
1651
+ box-sizing: border-box;
1652
+ background: var(--ide-input-bg, #3c3c3c);
1653
+ color: var(--ide-text, #d4d4d4);
1654
+ border: 1px solid var(--ide-border, #3c3c3c);
1655
+ border-radius: 3px;
1656
+ padding: 5px 8px;
1657
+ font-family: var(--ide-mono-font, monospace);
1658
+ font-size: 12px;
1659
+ }
1660
+ .import-dest-input:focus { outline: 1px solid var(--ide-accent, #0e639c); }
1661
+ .import-target-replaces {
1662
+ margin-left: 8px;
1663
+ font-size: 10px;
1664
+ text-transform: uppercase;
1665
+ letter-spacing: 0.06em;
1666
+ color: var(--ide-warning, #cca700);
1667
+ }
1416
1668
  .schema-table {
1417
1669
  width: 100%;
1418
1670
  border-collapse: collapse;
@@ -1969,12 +2221,20 @@ html, body, #mbeditor-root {
1969
2221
  flex-shrink: 0;
1970
2222
  }
1971
2223
 
2224
+ /* The title yields the header to the action buttons rather than wrapping.
2225
+ *
2226
+ * It used to break-word onto three lines ("SAMPLE_ / WORKSPA / CE") because a
2227
+ * long workspace name plus six buttons genuinely does not fit a 300px sidebar.
2228
+ * flex-shrink against a `flex-shrink: 0` action group means the buttons always
2229
+ * win: full title, then ellipsis, then gone. No JS measuring, and it adapts on
2230
+ * its own to sections with fewer buttons — the Open Editors header keeps its
2231
+ * title at the same width where Explorer has already dropped it. */
1972
2232
  .collapsible-title {
1973
- flex: 1;
2233
+ flex: 1 1 auto;
1974
2234
  min-width: 0;
1975
- word-wrap: break-word;
1976
- overflow-wrap: break-word;
1977
- word-break: break-word;
2235
+ white-space: nowrap;
2236
+ overflow: hidden;
2237
+ text-overflow: ellipsis;
1978
2238
  }
1979
2239
 
1980
2240
  .collapsible-actions {
@@ -1991,6 +2251,45 @@ html, body, #mbeditor-root {
1991
2251
  gap: 0;
1992
2252
  }
1993
2253
 
2254
+ /* Tooltip for the icon-only sidebar action buttons. See SidebarActionButton
2255
+ * for why this is not a title attribute. Anchored to the button's right edge
2256
+ * because .ide-sidebar-scrollable is overflow-x: hidden — a left-anchored or
2257
+ * centred tooltip on the rightmost button would be clipped by the sidebar. */
2258
+ .sb-tip {
2259
+ position: relative;
2260
+ display: inline-flex;
2261
+ }
2262
+ .sb-tip::after {
2263
+ content: attr(data-tip);
2264
+ position: absolute;
2265
+ top: calc(100% + 4px);
2266
+ right: 0;
2267
+ z-index: 4000;
2268
+ padding: 4px 7px;
2269
+ background: var(--ide-panel-bg, #252526);
2270
+ border: 1px solid var(--ide-border, #3c3c3c);
2271
+ border-radius: 4px;
2272
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
2273
+ color: var(--ide-text, #d4d4d4);
2274
+ /* The section header sets uppercase, 600 weight and letter-spacing; a
2275
+ tooltip inheriting those reads as another heading. */
2276
+ font-size: 11px;
2277
+ font-weight: 400;
2278
+ letter-spacing: 0;
2279
+ text-transform: none;
2280
+ white-space: nowrap;
2281
+ opacity: 0;
2282
+ pointer-events: none;
2283
+ transition: opacity 0.08s ease 0.3s;
2284
+ }
2285
+ .sb-tip:hover::after,
2286
+ .sb-tip:focus-within::after {
2287
+ opacity: 1;
2288
+ }
2289
+ @media (prefers-reduced-motion: reduce) {
2290
+ .sb-tip::after { transition: none; }
2291
+ }
2292
+
1994
2293
  /* Open-editors panel: height is controlled by the --open-editors-height CSS variable
1995
2294
  set inline from React state (default 140px ≈ 5-6 items), drag-resizable via the
1996
2295
  .open-editors-resize-handle strip below. */
@@ -2319,7 +2618,7 @@ html, body, #mbeditor-root {
2319
2618
  display: flex;
2320
2619
  align-items: flex-start;
2321
2620
  justify-content: center;
2322
- padding-top: 15vh;
2621
+ padding-top: 12vh;
2323
2622
  z-index: 9999;
2324
2623
  }
2325
2624
 
@@ -2327,8 +2626,9 @@ html, body, #mbeditor-root {
2327
2626
  background: var(--ide-panel-bg);
2328
2627
  border: 1px solid var(--ide-border-input);
2329
2628
  border-radius: 8px;
2330
- width: 500px;
2331
- max-height: 400px;
2629
+ /* vw/vh caps keep the larger dialog inside a phone-sized viewport */
2630
+ width: min(760px, 94vw);
2631
+ max-height: min(620px, 72vh);
2332
2632
  display: flex;
2333
2633
  flex-direction: column;
2334
2634
  box-shadow: 0 16px 48px rgba(0,0,0,0.7);
@@ -2344,8 +2644,8 @@ html, body, #mbeditor-root {
2344
2644
  border: none;
2345
2645
  border-bottom: 1px solid var(--ide-border);
2346
2646
  color: var(--ide-text);
2347
- padding: 12px 34px 12px 16px;
2348
- font-size: 14px;
2647
+ padding: 14px 40px 14px 18px;
2648
+ font-size: 16px;
2349
2649
  outline: none;
2350
2650
  width: 100%;
2351
2651
  box-sizing: border-box;
@@ -2354,11 +2654,11 @@ html, body, #mbeditor-root {
2354
2654
 
2355
2655
  .quick-open-clear-btn {
2356
2656
  position: absolute;
2357
- right: 6px;
2657
+ right: 8px;
2358
2658
  top: 50%;
2359
2659
  transform: translateY(-50%);
2360
- width: 22px;
2361
- height: 22px;
2660
+ width: 26px;
2661
+ height: 26px;
2362
2662
  border: none;
2363
2663
  border-radius: 4px;
2364
2664
  background: transparent;
@@ -2380,18 +2680,18 @@ html, body, #mbeditor-root {
2380
2680
  }
2381
2681
 
2382
2682
  .quick-open-result {
2383
- padding: 6px 16px;
2683
+ padding: 8px 18px;
2384
2684
  cursor: pointer;
2385
2685
  display: flex;
2386
2686
  align-items: center;
2387
- gap: 8px;
2388
- font-size: 12px;
2687
+ gap: 10px;
2688
+ font-size: 14px;
2389
2689
  transition: background 0.1s;
2390
2690
  }
2391
2691
 
2392
2692
  .quick-open-result-icon {
2393
2693
  flex-shrink: 0;
2394
- width: 16px;
2694
+ width: 18px;
2395
2695
  text-align: center;
2396
2696
  color: #78b4e0;
2397
2697
  }
@@ -2405,10 +2705,10 @@ html, body, #mbeditor-root {
2405
2705
  .quick-open-result.selected { background: var(--ide-active-bg); }
2406
2706
 
2407
2707
  .quick-open-result-name { color: var(--ide-text); }
2408
- .quick-open-result-path { color: var(--ide-text-muted); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2708
+ .quick-open-result-path { color: var(--ide-text-muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2409
2709
 
2410
2710
  .quick-open-section { margin-bottom: 4px; }
2411
- .quick-open-section-header { padding: 6px 14px 4px; font-size: 10px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ide-text-muted); font-weight: 600; }
2711
+ .quick-open-section-header { padding: 8px 18px 4px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--ide-text-muted); font-weight: 600; }
2412
2712
 
2413
2713
  .quick-open-star-btn {
2414
2714
  flex-shrink: 0;
@@ -2417,7 +2717,7 @@ html, body, #mbeditor-root {
2417
2717
  cursor: pointer;
2418
2718
  padding: 2px 6px;
2419
2719
  color: var(--ide-text-muted);
2420
- font-size: 12px;
2720
+ font-size: 14px;
2421
2721
  line-height: 1;
2422
2722
  opacity: 0;
2423
2723
  transition: opacity 0.1s, color 0.1s;
@@ -2431,9 +2731,9 @@ html, body, #mbeditor-root {
2431
2731
  .quick-open-result-dir .quick-open-result-name { color: #dcb67a; }
2432
2732
 
2433
2733
  .quick-open-empty-hint {
2434
- padding: 20px 16px;
2734
+ padding: 24px 18px;
2435
2735
  color: var(--ide-text-muted);
2436
- font-size: 12px;
2736
+ font-size: 13px;
2437
2737
  text-align: center;
2438
2738
  line-height: 1.6;
2439
2739
  }
@@ -2652,26 +2952,8 @@ button:not(.pico-btn) {
2652
2952
  font-size: 12px;
2653
2953
  }
2654
2954
 
2655
- /* Monaco widget isolationPico's [role=button] selector matches Monaco's
2656
- * internal action elements (View Problem, Quick Fix…, lightbulb actions)
2657
- * because Monaco renders them as <a role="button">. Without this reset, Pico
2658
- * applies its primary-colour background, white text, and large form-element
2659
- * padding, making those buttons huge and unreadable on certain themes.
2660
- * Resetting the Pico CSS variables here is lower-impact than fighting
2661
- * per-property specificity battles with Monaco's --vscode-* rules.
2662
- */
2663
- .monaco-hover [role=button],
2664
- .action-widget [role=button],
2665
- .context-view [role=button],
2666
- .monaco-editor [role=button],
2667
- .monaco-diff-editor [role=button] {
2668
- --pico-background-color: transparent;
2669
- --pico-border-color: transparent;
2670
- --pico-color: inherit;
2671
- --pico-box-shadow: none;
2672
- --pico-form-element-spacing-vertical: 0;
2673
- --pico-form-element-spacing-horizontal: 0;
2674
- }
2955
+ /* Pico containment lives at the top of this file see the block there. It has
2956
+ * to sit above every component rule to stay a floor rather than an override. */
2675
2957
 
2676
2958
  /* Inputs and selects — keep compact but styled */
2677
2959
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
@@ -2684,7 +2966,11 @@ textarea {
2684
2966
  margin-bottom: 0;
2685
2967
  }
2686
2968
 
2687
- /* Status bar buttons override (22px tall, full-bleed) */
2969
+ /* Status bar buttons override (22px tall, full-bleed).
2970
+ * line-height must be set here too, not just on .ide-statusbar: Pico gives
2971
+ * <button> its own line-height, so it does not inherit the bar's, and the
2972
+ * count next to an icon ended up in a 16.5px line box beside the icon's 11px
2973
+ * one. */
2688
2974
  .ide-statusbar button,
2689
2975
  .statusbar-btn {
2690
2976
  --pico-form-element-spacing-vertical: 0;
@@ -2692,6 +2978,7 @@ textarea {
2692
2978
  height: 22px;
2693
2979
  padding: 0 8px;
2694
2980
  font-size: 11px;
2981
+ line-height: 1;
2695
2982
  border-radius: 2px;
2696
2983
  border: none;
2697
2984
  background: transparent;
@@ -3049,6 +3336,11 @@ button:not(.pico-btn) { margin-bottom: 0; }
3049
3336
  padding: 2px 6px;
3050
3337
  }
3051
3338
  .ide-problems-btn:hover { color: var(--ide-fg, #fff); }
3339
+ .ide-problems-btn:disabled { opacity: 0.4; cursor: default; }
3340
+ .ide-problems-btn:disabled:hover { color: var(--ide-fg-muted, #ccc); }
3341
+ .ide-problems-actions { display: flex; gap: 2px; }
3342
+ .ide-problems-actions .ide-problems-btn { white-space: nowrap; }
3343
+ .ide-problems-btn.is-on { color: var(--ide-accent, #0a84ff); }
3052
3344
  .ide-problems-body { flex: 1; overflow: auto; padding: 4px 0; font-size: 12px; }
3053
3345
  .ide-problems-empty {
3054
3346
  padding: 10px 14px;
@@ -3281,18 +3573,12 @@ button:not(.pico-btn) { margin-bottom: 0; }
3281
3573
  .ide-problems-frame { padding-left: 28px; }
3282
3574
  .ide-problems-frame .ide-problems-code { opacity: 1; }
3283
3575
 
3284
- /* Status-bar ruby-lsp indicator. Healthy is a dim icon you can ignore;
3285
- degraded/off is amber with a label, because that one you should read. */
3286
- .statusbar-lsp { display: inline-flex; align-items: center; gap: 4px; }
3287
- .statusbar-lsp-ok { opacity: 0.45; }
3288
- .statusbar-lsp-ok:hover { opacity: 1; }
3289
- .statusbar-lsp-degraded,
3290
- .statusbar-lsp-off { color: var(--ide-warning); }
3291
-
3292
- /* Status-bar problems indicator: bug count then warning count, VS Code style. */
3293
- .statusbar-problems { display: inline-flex; align-items: center; gap: 4px; }
3576
+ /* Status-bar problems indicator: bug count then warning count, VS Code style.
3577
+ The gap is the icon-to-its-own-number spacing; the warning icon opens the
3578
+ second pair, so it gets the wider separator. */
3579
+ .statusbar-problems { gap: 4px; }
3294
3580
  .statusbar-problems-error-icon { color: var(--ide-danger); }
3295
- .statusbar-problems-warning-icon { color: var(--ide-warning); }
3581
+ .statusbar-problems-warning-icon { color: var(--ide-warning); margin-left: 8px; }
3296
3582
  .statusbar-problems-count { font-variant-numeric: tabular-nums; }
3297
3583
 
3298
3584
  /* Search decorations off across engines — Chrome/Safari draw their own