mbeditor 0.13.0 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +157 -1
  3. data/app/assets/javascripts/mbeditor/application.js +3 -1
  4. data/app/assets/javascripts/mbeditor/audit_log.js +165 -0
  5. data/app/assets/javascripts/mbeditor/collaboration_service.js +264 -22
  6. data/app/assets/javascripts/mbeditor/components/ChangelogView.js +89 -94
  7. data/app/assets/javascripts/mbeditor/components/CodeReviewPanel.js +6 -9
  8. data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +12 -7
  9. data/app/assets/javascripts/mbeditor/components/CombinedDiffViewer.js +20 -0
  10. data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
  11. data/app/assets/javascripts/mbeditor/components/EditorPanel.js +440 -334
  12. data/app/assets/javascripts/mbeditor/components/FileHistoryPanel.js +6 -9
  13. data/app/assets/javascripts/mbeditor/components/FileTree.js +26 -12
  14. data/app/assets/javascripts/mbeditor/components/GitPanel.js +3 -0
  15. data/app/assets/javascripts/mbeditor/components/Gutter.js +51 -0
  16. data/app/assets/javascripts/mbeditor/components/ImportDialog.js +9 -1
  17. data/app/assets/javascripts/mbeditor/components/LogPanel.js +3 -44
  18. data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +1322 -1431
  19. data/app/assets/javascripts/mbeditor/components/ModelGraph.js +94 -37
  20. data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +82 -72
  21. data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +121 -81
  22. data/app/assets/javascripts/mbeditor/components/SettingsModal.js +342 -0
  23. data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +2 -1
  24. data/app/assets/javascripts/mbeditor/components/TabBar.js +67 -98
  25. data/app/assets/javascripts/mbeditor/editor_plugins.js +694 -306
  26. data/app/assets/javascripts/mbeditor/file_import.js +13 -15
  27. data/app/assets/javascripts/mbeditor/file_service.js +46 -57
  28. data/app/assets/javascripts/mbeditor/git_service.js +15 -1
  29. data/app/assets/javascripts/mbeditor/history_service.js +5 -13
  30. data/app/assets/javascripts/mbeditor/search_service.js +29 -2
  31. data/app/assets/javascripts/mbeditor/tab_manager.js +135 -54
  32. data/app/assets/javascripts/mbeditor/websocket_service.js +13 -5
  33. data/app/assets/stylesheets/mbeditor/application.css +6 -1
  34. data/app/assets/stylesheets/mbeditor/editor.css +762 -297
  35. data/app/assets/stylesheets/mbeditor/glass.css +163 -0
  36. data/app/assets/stylesheets/mbeditor/themes.css +90 -30
  37. data/app/channels/mbeditor/collaboration_channel.rb +25 -6
  38. data/app/controllers/mbeditor/application_controller.rb +26 -3
  39. data/app/controllers/mbeditor/editors_controller.rb +125 -465
  40. data/app/services/mbeditor/archive_service.rb +137 -0
  41. data/app/services/mbeditor/collaboration_doc_store.rb +180 -12
  42. data/app/services/mbeditor/duplicate_content_scanner.rb +105 -0
  43. data/app/services/mbeditor/editor_state_service.rb +14 -51
  44. data/app/services/mbeditor/file_history_service.rb +222 -0
  45. data/app/services/mbeditor/git_info_service.rb +6 -0
  46. data/app/services/mbeditor/js_globals_service.rb +12 -1
  47. data/app/services/mbeditor/js_syntax_check_service.rb +42 -16
  48. data/app/services/mbeditor/lint_service.rb +137 -0
  49. data/app/services/mbeditor/locked_json_file.rb +67 -0
  50. data/app/services/mbeditor/process_runner.rb +32 -0
  51. data/app/services/mbeditor/rubocop_run_service.rb +17 -5
  52. data/app/services/mbeditor/ruby_lsp_result_translator.rb +226 -0
  53. data/app/services/mbeditor/schema_service.rb +8 -2
  54. data/app/services/mbeditor/search_replace_service.rb +19 -2
  55. data/app/services/mbeditor/test_runner_service.rb +3 -56
  56. data/app/views/layouts/mbeditor/application.html.erb +1 -1
  57. data/lib/mbeditor/audit_log.rb +203 -0
  58. data/lib/mbeditor/configuration.rb +6 -9
  59. data/lib/mbeditor/rack/pending_migration_bypass.rb +15 -9
  60. data/lib/mbeditor/route_map.rb +4 -1
  61. data/lib/mbeditor/ruby_lsp_client.rb +82 -14
  62. data/lib/mbeditor/version.rb +1 -1
  63. data/lib/mbeditor.rb +1 -0
  64. data/lib/tasks/mbeditor.rake +23 -0
  65. metadata +14 -3
  66. data/app/assets/javascripts/mbeditor/components/TestRunPanel.js +0 -312
@@ -1,13 +1,24 @@
1
1
  /* ── Mini Browser Editor Global Styles ──────────────────── */
2
2
  *, *::before, *::after { box-sizing: border-box; }
3
3
 
4
- /* Suppress all focus outlines / coloured glows inside the IDE shell */
4
+ /* Suppress all focus outlines / coloured glows inside the IDE shell, except
5
+ a keyboard-only ring on interactive elements (not Monaco's own DOM, which
6
+ draws its own focus treatment). */
5
7
  #mbeditor-root *:focus,
6
8
  #mbeditor-root *:focus-visible {
7
9
  outline: none !important;
8
10
  box-shadow: none !important;
9
11
  }
10
12
 
13
+ #mbeditor-root :is(button, [role="button"], [tabindex], input, select, textarea, a, .tab-item, .ide-activity-btn, .tree-item):focus-visible {
14
+ outline: 1px solid var(--ide-accent-fg) !important;
15
+ outline-offset: -1px;
16
+ }
17
+
18
+ #mbeditor-root .monaco-editor *:focus-visible {
19
+ outline: none !important;
20
+ }
21
+
11
22
  /* ── Pico containment ──────────────────────────────────────────────────────
12
23
  *
13
24
  * The host app's stylesheet loads alongside this one, and when it is Pico (the
@@ -121,23 +132,23 @@ html, body, #mbeditor-root {
121
132
  align-items: center;
122
133
  background: var(--ide-panel-bg);
123
134
  border-bottom: 1px solid var(--ide-border);
124
- height: 32px;
125
- padding: 0 12px;
135
+ height: 35px;
136
+ padding: 0 8px;
126
137
  flex-shrink: 0;
127
138
  user-select: none;
128
139
  }
129
140
 
130
141
  .ide-titlebar-icon {
131
- color: var(--ide-accent-fg);
142
+ color: var(--ide-text-muted);
132
143
  margin-right: 8px;
133
- font-size: 14px;
144
+ font-size: 13px;
134
145
  }
135
146
 
136
147
  .ide-titlebar-title {
137
- font-size: 13px;
138
- color: var(--ide-text);
139
- font-weight: 500;
140
- /* Never wrap: the titlebar is a fixed 32px row, so a second line pushed the
148
+ font-size: 12px;
149
+ color: var(--ide-text-muted);
150
+ font-weight: 400;
151
+ /* Never wrap: the titlebar is a fixed 35px row, so a second line pushed the
141
152
  toolbar buttons out of it. */
142
153
  white-space: nowrap;
143
154
  }
@@ -157,30 +168,30 @@ html, body, #mbeditor-root {
157
168
  flex: 1 1 auto;
158
169
  justify-content: center;
159
170
  min-width: 0;
160
- margin: 0 12px;
171
+ margin: 0 24px;
161
172
  }
162
173
 
163
174
  .ide-titlebar-search {
164
175
  display: flex;
165
176
  align-items: center;
166
177
  justify-content: center;
167
- /* 75% of the slot, i.e. of the space between the title and the buttons.
168
- The floor keeps the placeholder readable once 75% of a shrinking gap
169
- stops being enough, and caps at 100% so it can never outgrow its slot
170
- and shove the buttons off the edge. */
171
- width: 75%;
172
- min-width: min(220px, 100%);
173
- padding: 3px 12px;
174
- background: rgba(255, 255, 255, 0.06);
175
- border: 1px solid rgba(255, 255, 255, 0.12);
176
- border-radius: 999px; /* pill */
177
- color: #aaa;
178
+ /* The command centre. Capped so it stays a centred pill rather than a
179
+ stretched field, floored so the placeholder survives a narrow window. */
180
+ width: 100%;
181
+ max-width: 720px;
182
+ min-width: min(180px, 100%);
183
+ height: 24px;
184
+ padding: 0 10px;
185
+ background: color-mix(in srgb, var(--ide-text) 6%, transparent);
186
+ border: 1px solid var(--ide-border);
187
+ border-radius: 6px;
188
+ color: var(--ide-text-muted);
178
189
  font-size: 12px;
179
190
  cursor: pointer;
180
191
  }
181
- .ide-titlebar-search:hover { background: rgba(255, 255, 255, 0.10); }
192
+ .ide-titlebar-search:hover { background: color-mix(in srgb, var(--ide-text) 11%, transparent); }
182
193
  .ide-titlebar-search-text {
183
- opacity: 0.8;
194
+ opacity: 1;
184
195
  overflow: hidden;
185
196
  text-overflow: ellipsis;
186
197
  white-space: nowrap;
@@ -205,8 +216,8 @@ html, body, #mbeditor-root {
205
216
 
206
217
  /* ── Activity Bar (VSCode-style, always visible) ───────── */
207
218
  .ide-activity-bar {
208
- width: 60px;
209
- min-width: 60px;
219
+ width: 48px;
220
+ min-width: 48px;
210
221
  flex-shrink: 0;
211
222
  background: var(--ide-panel-bg);
212
223
  border-right: 1px solid var(--ide-border);
@@ -240,69 +251,105 @@ html, body, #mbeditor-root {
240
251
  than a bare class and was silently winning. */
241
252
  .ide-activity-bar .ide-activity-btn {
242
253
  width: 100%;
243
- height: 60px;
254
+ height: 48px;
244
255
  background: transparent;
245
256
  border: none;
246
257
  /* Always reserve the indicator's width so the glyph does not shift sideways
247
258
  by a pixel the moment a panel is opened. */
248
259
  border-left: 2px solid transparent;
249
260
  border-radius: 0;
250
- color: var(--ide-text-muted);
261
+ /* One glyph colour throughout; opacity carries the state, which is what
262
+ stops the icons reading as three different palettes at once. */
263
+ color: var(--ide-text);
264
+ opacity: 0.6;
251
265
  display: flex;
252
266
  align-items: center;
253
267
  justify-content: center;
254
268
  cursor: pointer;
255
- font-size: 25px;
256
- transition: color 0.12s ease;
269
+ font-size: 16px;
270
+ transition: opacity 0.1s ease, border-color 0.1s ease;
257
271
  }
258
272
 
259
273
  /* The glyph carries the state, not a filled box behind it. */
260
274
  .ide-activity-bar .ide-activity-btn:hover {
261
275
  background: transparent;
262
- color: var(--ide-text);
276
+ opacity: 1;
263
277
  }
264
278
 
265
279
  .ide-activity-bar .ide-activity-btn.active {
280
+ opacity: 1;
281
+ background: transparent;
266
282
  color: var(--ide-accent-fg);
267
- border-left-color: var(--ide-accent-fg);
283
+ border-left-color: var(--ide-accent);
268
284
  }
269
285
 
270
286
  .sidebar-panel-title {
271
287
  font-size: 11px;
272
288
  font-weight: 700;
273
289
  text-transform: uppercase;
274
- letter-spacing: 0.08em;
275
- color: var(--ide-fg-muted, var(--ide-text-muted, #888));
276
- padding: 10px 12px 6px;
290
+ letter-spacing: 0.06em;
291
+ color: var(--ide-text);
292
+ padding: 9px 20px 7px;
277
293
  flex-shrink: 0;
278
- border-bottom: 1px solid var(--ide-border);
279
294
  }
280
295
 
281
296
 
282
- .panel-divider {
283
- flex-shrink: 0;
284
- cursor: col-resize;
297
+ /* ── The gutter ──────────────────────────────────────────────
298
+ One draggable strip, always in the gap between two cards, never inside
299
+ either of them. 6px in its thin dimension and transparent: wider reads as a
300
+ gap in the layout rather than a handle.
301
+
302
+ The three dots are a ::before on the gutter itself. A flat strip gives no
303
+ sign it can be dragged, and the cursor only says so once you are already on
304
+ it. One tiled radial gradient rather than three elements: the gutter has no
305
+ children and adding some would put a hit-testable node inside the drag
306
+ target. */
307
+ .ide-gutter {
308
+ flex: 0 0 auto;
309
+ position: relative;
310
+ background: transparent;
285
311
  transition: background 0.12s ease;
286
312
  }
287
313
 
288
- .sidebar-divider {
289
- width: 6px;
290
- background: var(--ide-panel-bg-alt);
291
- border-right: 1px solid var(--ide-border);
292
- }
314
+ .ide-gutter-v { width: 6px; cursor: col-resize; align-self: stretch; }
315
+ .ide-gutter-h { height: 6px; cursor: row-resize; align-self: stretch; width: 100%; }
293
316
 
317
+ .ide-gutter::before {
318
+ content: '';
319
+ position: absolute;
320
+ left: 50%;
321
+ top: 50%;
322
+ transform: translate(-50%, -50%);
323
+ opacity: 0.55;
324
+ transition: opacity 0.12s ease;
325
+ pointer-events: none;
326
+ }
294
327
 
295
- .pane-divider {
296
- width: 4px;
297
- background: var(--ide-panel-bg-alt);
298
- z-index: 10;
328
+ /* Dots run top-to-bottom on a vertical gutter, left-to-right on a horizontal
329
+ one — the same tile, rotated. */
330
+ .ide-gutter-v::before {
331
+ width: 3px;
332
+ height: 15px;
333
+ background: radial-gradient(circle 1.5px at 1.5px 1.5px, var(--ide-text-muted) 99%, transparent) 0 0 / 3px 6px repeat-y;
299
334
  }
300
335
 
301
- .panel-divider:hover,
302
- .panel-divider.active {
303
- background: var(--ide-accent);
336
+ .ide-gutter-h::before {
337
+ width: 15px;
338
+ height: 3px;
339
+ background: radial-gradient(circle 1.5px at 1.5px 1.5px, var(--ide-text-muted) 99%, transparent) 0 0 / 6px 3px repeat-x;
304
340
  }
305
341
 
342
+ .ide-gutter:hover { background: var(--ide-hover-bg); }
343
+ .ide-gutter:hover::before,
344
+ .ide-gutter.active::before { opacity: 1; }
345
+
346
+ /* The split-pane gutter sits over the pane drop overlays. */
347
+ .ide-gutter-pane { z-index: 10; }
348
+
349
+ /* Modern: the seam beside the git panel card is drawn on the gutter, so the
350
+ panel itself carries no left border and there is only ever one line. */
351
+ .ide-gutter-gitpanel { border-left: 1px solid var(--ide-border); }
352
+
306
353
  .ide-sidebar-header {
307
354
  padding: 6px 12px;
308
355
  font-size: 11px;
@@ -359,8 +406,8 @@ html, body, #mbeditor-root {
359
406
 
360
407
  .tree-item:hover { background: var(--ide-hover-bg); }
361
408
  .tree-item.active { background: var(--ide-active-bg); }
362
- .tree-item.selected { box-shadow: inset 0 0 0 1px rgba(120, 180, 224, 0.5); background: rgba(120, 180, 224, 0.08); }
363
- .tree-item.active.selected { box-shadow: inset 0 0 0 1px rgba(140, 210, 255, 0.8); }
409
+ .tree-item.selected { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ide-accent) 35%, transparent); background: color-mix(in srgb, var(--ide-accent) 8%, transparent); }
410
+ .tree-item.active.selected { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ide-accent) 55%, transparent); }
364
411
  .tree-item.drag-over { background: rgba(100, 160, 220, 0.25); box-shadow: inset 0 0 0 1px rgba(100, 160, 220, 0.7); }
365
412
  /* External file drop (import) — green, to read as "add" rather than "move" */
366
413
  .tree-item.drag-over-external { background: rgba(120, 200, 140, 0.22); box-shadow: inset 0 0 0 1px rgba(120, 200, 140, 0.75); }
@@ -463,6 +510,7 @@ html, body, #mbeditor-root {
463
510
  .tab-bar {
464
511
  display: flex;
465
512
  align-items: center;
513
+ padding-left: 4px;
466
514
  background: var(--ide-panel-bg);
467
515
  border-bottom: 1px solid var(--ide-border);
468
516
  height: 35px;
@@ -504,15 +552,15 @@ html, body, #mbeditor-root {
504
552
  .tab-item {
505
553
  display: flex;
506
554
  align-items: center;
507
- padding: 0 12px;
508
- height: 35px;
509
- min-width: 100px;
510
- max-width: 200px;
555
+ padding: 0 8px 0 10px;
556
+ height: 27px;
557
+ margin: 4px 0 4px 4px;
558
+ max-width: 220px;
511
559
  cursor: pointer;
512
- border-right: 1px solid var(--ide-border);
513
- background: var(--ide-input-bg);
560
+ border-radius: 6px;
561
+ background: transparent;
514
562
  color: var(--ide-text-muted);
515
- font-size: 12px;
563
+ font-size: 13px;
516
564
  transition: background 0.1s, color 0.1s;
517
565
  white-space: nowrap;
518
566
  overflow: hidden;
@@ -528,30 +576,22 @@ html, body, #mbeditor-root {
528
576
  opacity: 0.55;
529
577
  }
530
578
 
531
- .tab-item:hover { background: var(--ide-panel-bg-alt); color: var(--ide-text); }
532
- .tab-item.active { background: var(--ide-bg); color: var(--ide-text); border-top: 1px solid var(--ide-accent-fg); }
533
- .tab-item.tab-soft .tab-item-name { font-style: italic; }
534
-
535
- .tab-has-error {
536
- border-bottom: 2px solid var(--ide-danger) !important;
537
- }
538
- .tab-item.active.tab-has-error {
539
- border-top: 2px solid var(--ide-danger) !important;
540
- border-bottom: none !important;
541
- }
542
-
543
- .tab-has-warning {
544
- border-bottom: 2px solid var(--ide-warning) !important;
579
+ .tab-item:hover { background: var(--ide-hover-bg); color: var(--ide-text); }
580
+ /* The active tab is a lifted rounded chip on the tab strip, not a seamless
581
+ join with the editor: the strip keeps its own bottom border. */
582
+ .tab-item.active {
583
+ background: var(--ide-hover-bg);
584
+ color: var(--ide-text);
545
585
  }
586
+ .tab-item.tab-soft .tab-item-name { font-style: italic; }
546
587
 
547
- .tab-item.active.tab-has-warning {
548
- border-top: 2px solid var(--ide-warning) !important;
549
- border-bottom: none !important;
550
- }
588
+ /* Diagnostics colour the label, as VS Code does; a border would break the chip. */
589
+ .tab-has-error .tab-item-name { color: var(--ide-danger); }
590
+ .tab-has-warning .tab-item-name { color: var(--ide-warning); }
551
591
 
552
592
  .tab-item-icon {
553
593
  width: 14px;
554
- margin-right: 8px;
594
+ margin-right: 6px;
555
595
  font-size: 12px;
556
596
  text-align: center;
557
597
  flex-shrink: 0;
@@ -572,8 +612,8 @@ html, body, #mbeditor-root {
572
612
 
573
613
  .tab-close {
574
614
  margin-left: 6px;
575
- width: 16px;
576
- height: 16px;
615
+ width: 20px;
616
+ height: 20px;
577
617
  display: flex;
578
618
  align-items: center;
579
619
  justify-content: center;
@@ -582,8 +622,14 @@ html, body, #mbeditor-root {
582
622
  color: var(--ide-text-muted);
583
623
  flex-shrink: 0;
584
624
  transition: background 0.1s, color 0.1s;
625
+ background: transparent;
626
+ border: none;
627
+ padding: 0;
628
+ cursor: pointer;
629
+ opacity: 0.6;
585
630
  }
586
631
 
632
+ .tab-item:hover .tab-close { opacity: 1; }
587
633
  .tab-close:hover { background: var(--ide-hover-bg); color: var(--ide-text); }
588
634
 
589
635
  .tab-welcome {
@@ -876,23 +922,34 @@ html, body, #mbeditor-root {
876
922
  }
877
923
 
878
924
  /* ── Status Bar ──────────────────────────────────────────── */
925
+ /* line-height: 1 is what keeps an icon and the number beside it on the same
926
+ * centre line. Pico's inherited 1.5 gave every text span a 16.5px line box
927
+ * inside a 22px bar while the icons' box was 11px, so the two were centred on
928
+ * different half-leadings; with 1 both boxes are the font's own content box
929
+ * and flex centres them together. */
879
930
  .ide-statusbar {
880
931
  display: flex;
881
932
  align-items: center;
882
- background: var(--ide-accent);
933
+ background: var(--ide-statusbar-bg);
934
+ border-top: 1px solid var(--ide-border);
883
935
  height: 22px;
884
- padding: 0 8px;
885
- gap: 4px;
936
+ padding: 0;
937
+ gap: 0;
886
938
  flex-shrink: 0;
887
- font-size: 11px;
939
+ font-size: 12px;
940
+ line-height: 20px; /* glyphs centre in the row; line-height 1 sat them high */
888
941
  color: var(--ide-accent-text);
889
942
  }
890
943
 
944
+ /* One cell rhythm for every item, button or not. The branch, the message and
945
+ * the chips each carried their own padding on top of the bar's, so the gaps
946
+ * between them were all different widths. */
947
+ .ide-statusbar > * { padding: 0 6px; }
948
+
891
949
  .statusbar-branch {
892
950
  display: flex;
893
951
  align-items: center;
894
- gap: 6px;
895
- font-size: 13px;
952
+ gap: 4px;
896
953
  font-weight: 600;
897
954
  color: var(--ide-accent-text);
898
955
  }
@@ -919,6 +976,29 @@ html, body, #mbeditor-root {
919
976
  cursor: default;
920
977
  }
921
978
 
979
+ /* Pending migrations: the whole status bar turns amber, rather than a banner
980
+ * that sat above everything and shoved the editor down the page every time it
981
+ * appeared.
982
+ *
983
+ * The bar's own variables are redefined rather than background and color,
984
+ * because the bar and every child — buttons, separators, the hover mix — are
985
+ * already painted from them. Setting the background alone left unreadable
986
+ * text on amber. */
987
+ body.mbeditor-migration-pending .ide-statusbar {
988
+ --ide-statusbar-bg: var(--ide-statusbar-warn-bg);
989
+ --ide-accent-text: var(--ide-statusbar-warn-text);
990
+ --ide-text-muted: var(--ide-statusbar-warn-text);
991
+ }
992
+
993
+ .statusbar-migration {
994
+ display: flex;
995
+ align-items: center;
996
+ gap: 6px;
997
+ font-weight: 600;
998
+ white-space: nowrap;
999
+ flex-shrink: 0;
1000
+ }
1001
+
922
1002
  .statusbar-offline {
923
1003
  display: flex;
924
1004
  align-items: center;
@@ -926,7 +1006,6 @@ html, body, #mbeditor-root {
926
1006
  font-size: 12px;
927
1007
  font-weight: 600;
928
1008
  color: var(--ide-danger);
929
- padding: 0 10px;
930
1009
  animation: statusbar-pulse 2s ease-in-out infinite;
931
1010
  }
932
1011
 
@@ -939,19 +1018,32 @@ html, body, #mbeditor-root {
939
1018
  display: flex;
940
1019
  align-items: center;
941
1020
  gap: 4px;
942
- padding: 0 8px;
943
- height: 22px;
1021
+ padding: 0 6px;
1022
+ height: 20px;
1023
+ margin: 0 1px;
944
1024
  flex-shrink: 0;
945
1025
  background: transparent;
946
1026
  border: none;
947
1027
  color: var(--ide-accent-text);
948
1028
  cursor: pointer;
949
- font-size: 11px;
950
- border-radius: 2px;
1029
+ font-size: 12px;
1030
+ border-radius: 5px;
951
1031
  transition: background 0.1s;
952
1032
  white-space: nowrap;
953
1033
  }
954
1034
 
1035
+ /* Cursor position readout. Matches the buttons' metrics so the bar keeps one
1036
+ rhythm, but it is a span with no hover: nothing happens when you click it. */
1037
+ .statusbar-cursor {
1038
+ display: flex;
1039
+ align-items: center;
1040
+ padding: 0 6px;
1041
+ height: 20px;
1042
+ flex-shrink: 0;
1043
+ color: var(--ide-accent-text);
1044
+ font-variant-numeric: tabular-nums;
1045
+ }
1046
+
955
1047
  .statusbar-btn:hover { background: color-mix(in srgb, var(--ide-accent-text) 15%, transparent); }
956
1048
  .statusbar-btn:disabled { opacity: 0.5; cursor: not-allowed; }
957
1049
 
@@ -1009,6 +1101,77 @@ html, body, #mbeditor-root {
1009
1101
  .collab-hovercard-row { color: var(--ide-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1010
1102
  .collab-hovercard-hint { margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--ide-border); color: var(--ide-text-muted); }
1011
1103
 
1104
+ /* Overlapping avatar stack: fixed width regardless of headcount, which is the
1105
+ point — a chip per peer is what broke past ~4 people. The ring is a
1106
+ box-shadow, not a border, so it doesn't perturb the circle's own layout
1107
+ before the negative margin overlaps it; it paints over the sibling behind it
1108
+ because later avatars are later in DOM order, no z-index needed. Its colour
1109
+ comes from --ide-panel-bg rather than an opaque literal, since glass chrome
1110
+ tints that same token instead of replacing it. */
1111
+ .collab-avatar-stack { display: flex; align-items: center; }
1112
+ .collab-avatar {
1113
+ width: 9px;
1114
+ height: 9px;
1115
+ flex-shrink: 0;
1116
+ border: none;
1117
+ border-radius: 50%;
1118
+ padding: 0;
1119
+ cursor: pointer;
1120
+ box-shadow: 0 0 0 1.5px var(--ide-panel-bg);
1121
+ }
1122
+ .collab-avatar:not(:first-child) { margin-left: -4.5px; }
1123
+ /* Dimmed rather than re-shaped: a peer in another file still reads as "here"
1124
+ at a glance, just fainter, instead of needing a second glyph on a 9px dot. */
1125
+ .collab-avatar-elsewhere { opacity: 0.55; }
1126
+ .collab-avatar-following { outline: 1.5px solid var(--ide-accent-fg); outline-offset: 1px; }
1127
+ .collab-avatar-more {
1128
+ width: auto;
1129
+ height: auto;
1130
+ margin-left: 6px;
1131
+ padding: 0;
1132
+ font-size: 10px;
1133
+ color: var(--ide-text-muted);
1134
+ background: none;
1135
+ border-radius: 0;
1136
+ box-shadow: none;
1137
+ }
1138
+ .collab-avatar-overflow { position: relative; }
1139
+ /* Fixed and anchored from the button's rect, like .collab-hovercard: inside the
1140
+ title bar it was painted under the editor whatever its z-index. */
1141
+ .collab-overflow-popover {
1142
+ position: fixed;
1143
+ z-index: 4000;
1144
+ min-width: 180px;
1145
+ max-width: 260px;
1146
+ max-height: 240px;
1147
+ overflow-y: auto;
1148
+ padding: 4px;
1149
+ background: var(--ide-panel-bg);
1150
+ border: 1px solid var(--ide-border);
1151
+ border-radius: 6px;
1152
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
1153
+ }
1154
+ .collab-overflow-row {
1155
+ display: flex;
1156
+ align-items: center;
1157
+ gap: 6px;
1158
+ width: 100%;
1159
+ padding: 4px 6px;
1160
+ background: transparent;
1161
+ border: none;
1162
+ border-radius: 4px;
1163
+ cursor: pointer;
1164
+ color: var(--ide-text);
1165
+ font-size: 11px;
1166
+ text-align: left;
1167
+ }
1168
+ .collab-overflow-row:hover { background: var(--ide-hover-bg); }
1169
+ .collab-overflow-row.is-following { background: color-mix(in srgb, var(--ide-accent) 15%, transparent); }
1170
+ .collab-overflow-swatch { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
1171
+ .collab-overflow-name { flex-shrink: 0; }
1172
+ .collab-overflow-file { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ide-text-muted); }
1173
+ .collab-overflow-eye { margin-left: auto; color: var(--ide-accent-fg); font-size: 10px; }
1174
+
1012
1175
  .statusbar-msg {
1013
1176
  margin-left: auto;
1014
1177
  color: var(--ide-accent-text);
@@ -1023,10 +1186,9 @@ html, body, #mbeditor-root {
1023
1186
  .statusbar-msg.success { color: var(--ide-success); }
1024
1187
 
1025
1188
  .statusbar-version {
1026
- color: color-mix(in srgb, var(--ide-accent-text) 45%, transparent);
1189
+ color: color-mix(in srgb, var(--ide-accent-text) 80%, transparent);
1027
1190
  font-size: 10px;
1028
1191
  white-space: nowrap;
1029
- padding-left: 8px;
1030
1192
  cursor: pointer;
1031
1193
  }
1032
1194
  .statusbar-version:hover {
@@ -1039,7 +1201,6 @@ html, body, #mbeditor-root {
1039
1201
  font-weight: 600;
1040
1202
  letter-spacing: 0.04em;
1041
1203
  opacity: 0.85;
1042
- padding: 0 6px;
1043
1204
  }
1044
1205
 
1045
1206
  .statusbar-zen-btn {
@@ -1173,13 +1334,20 @@ html, body, #mbeditor-root {
1173
1334
  background: var(--ide-input-bg);
1174
1335
  color: var(--ide-text);
1175
1336
  border: 1px solid var(--ide-border-input);
1176
- border-radius: 4px;
1337
+ border-radius: 8px;
1177
1338
  font-size: 12px;
1178
1339
  outline: none;
1179
1340
  box-sizing: border-box;
1180
1341
  }
1181
1342
  .search-input::placeholder { color: var(--ide-text-muted); }
1182
- .search-input:focus { border-color: var(--ide-border-input); }
1343
+ .search-input:focus { border-color: var(--ide-accent); }
1344
+
1345
+ /* 8px on every text field, so the search box, the rename box and the import
1346
+ destination box are one shape rather than three. Pico's own 0.25rem applies
1347
+ to bare elements, hence the element selectors rather than a class list. */
1348
+ #mbeditor-root :is(input[type="text"], input[type="search"], input[type="number"], textarea, select) {
1349
+ border-radius: 8px;
1350
+ }
1183
1351
 
1184
1352
  /* ── Results area wrapper (positions the loading overlay) ── */
1185
1353
  .search-results-area {
@@ -1225,52 +1393,88 @@ html, body, #mbeditor-root {
1225
1393
  to { transform: rotate(360deg); }
1226
1394
  }
1227
1395
 
1396
+ /* Tree rows. Both kinds are pinned to the same 22px because MbeditorApp
1397
+ windows them by index * SEARCH_ROW_HEIGHT — change one and change both. */
1398
+ .search-result-file-row,
1228
1399
  .search-result-item {
1229
- height: 40px;
1230
- padding: 4px 6px;
1400
+ height: 22px;
1231
1401
  cursor: pointer;
1232
- border-radius: 3px;
1233
1402
  transition: background 0.1s;
1234
- border-bottom: 1px solid var(--ide-input-bg);
1235
1403
  display: flex;
1236
1404
  align-items: center;
1237
- gap: 8px;
1405
+ gap: 6px;
1238
1406
  overflow: hidden;
1239
1407
  box-sizing: border-box;
1240
1408
  flex-shrink: 0;
1241
1409
  }
1242
1410
 
1411
+ .search-result-file-row { padding: 0 6px; }
1412
+ /* Indented under its file header, the way the explorer nests children. */
1413
+ .search-result-item { padding: 0 6px 0 24px; }
1414
+
1415
+ .search-result-file-row:hover,
1243
1416
  .search-result-item:hover { background: var(--ide-hover-bg); }
1244
1417
 
1418
+ .search-result-chevron {
1419
+ flex-shrink: 0;
1420
+ width: 12px;
1421
+ font-size: 12px;
1422
+ color: var(--ide-text-muted);
1423
+ }
1424
+
1245
1425
  .search-result-icon {
1246
1426
  flex-shrink: 0;
1247
1427
  width: 16px;
1248
- margin-top: 2px;
1249
1428
  text-align: center;
1250
1429
  color: #78b4e0;
1251
1430
  }
1252
1431
 
1253
- .search-result-body {
1254
- min-width: 0;
1255
- flex: 1;
1432
+ .search-result-file-name {
1433
+ font-size: 12px;
1434
+ color: var(--ide-text);
1435
+ white-space: nowrap;
1436
+ flex-shrink: 0;
1437
+ max-width: 60%;
1438
+ overflow: hidden;
1439
+ text-overflow: ellipsis;
1256
1440
  }
1257
1441
 
1258
- .search-result-file {
1259
- font-size: 11px;
1260
- color: var(--ide-accent-fg);
1261
- font-weight: 500;
1442
+ .search-result-file-dir {
1443
+ font-size: 10px;
1444
+ color: var(--ide-text-muted);
1262
1445
  white-space: nowrap;
1263
1446
  overflow: hidden;
1264
1447
  text-overflow: ellipsis;
1448
+ flex: 1;
1449
+ min-width: 0;
1450
+ }
1451
+
1452
+ /* Match count, VS Code's little pill on the right of the file row. */
1453
+ .search-result-count {
1454
+ flex-shrink: 0;
1455
+ margin-left: auto;
1456
+ min-width: 16px;
1457
+ padding: 0 5px;
1458
+ border-radius: 9px;
1459
+ background: var(--ide-input-bg);
1460
+ color: var(--ide-text-muted);
1461
+ font-size: 10px;
1462
+ line-height: 15px;
1463
+ text-align: center;
1265
1464
  }
1266
1465
 
1267
1466
  .search-result-line-num {
1467
+ flex-shrink: 0;
1268
1468
  font-size: 10px;
1269
1469
  color: var(--ide-text-muted);
1270
- margin-left: 4px;
1470
+ font-family: monospace;
1471
+ min-width: 28px;
1472
+ text-align: right;
1271
1473
  }
1272
1474
 
1273
1475
  .search-result-text {
1476
+ flex: 1;
1477
+ min-width: 0;
1274
1478
  font-size: 11px;
1275
1479
  color: var(--ide-text-muted);
1276
1480
  font-family: monospace;
@@ -1279,6 +1483,47 @@ html, body, #mbeditor-root {
1279
1483
  text-overflow: ellipsis;
1280
1484
  }
1281
1485
 
1486
+ /* The match has to stay on screen at any panel width, so a hit line is three
1487
+ boxes rather than one ellipsised string. Shrink order is what does the work:
1488
+ the text BEFORE the match gives way first, then the text after it, and the
1489
+ match itself only as a last resort — so it can never be the part that scrolls
1490
+ out of view. `direction: rtl` is what moves the leading ellipsis to the start
1491
+ of .search-result-pre; searchMatchParts() prefixes a U+200E so an all-symbol
1492
+ segment can't be reordered by it. */
1493
+ .search-result-text-split {
1494
+ display: flex;
1495
+ align-items: center;
1496
+ text-overflow: clip;
1497
+ }
1498
+
1499
+ .search-result-pre,
1500
+ .search-result-post,
1501
+ .search-result-match {
1502
+ overflow: hidden;
1503
+ text-overflow: ellipsis;
1504
+ white-space: pre;
1505
+ }
1506
+
1507
+ .search-result-pre {
1508
+ flex: 0 100 auto;
1509
+ direction: rtl;
1510
+ text-align: left;
1511
+ }
1512
+
1513
+ .search-result-post { flex: 0 10 auto; }
1514
+
1515
+ /* Never shrinks: flex distributes overflow across every shrinkable box in
1516
+ proportion to its size, so even shrink:1 against a long tail cost the match a
1517
+ character or two of ellipsis. max-width is what still bounds a match wider
1518
+ than the panel — it clips instead of overflowing. */
1519
+ .search-result-match {
1520
+ flex: 0 0 auto;
1521
+ max-width: 100%;
1522
+ background: color-mix(in srgb, var(--ide-accent) 40%, transparent);
1523
+ color: var(--ide-text);
1524
+ border-radius: 2px;
1525
+ }
1526
+
1282
1527
  .search-results-loading-more {
1283
1528
  padding: 8px 10px;
1284
1529
  font-size: 11px;
@@ -1807,8 +2052,9 @@ html, body, #mbeditor-root {
1807
2052
 
1808
2053
  /* ── Right-side Git Panel ───────────────────────────────── */
1809
2054
  .ide-git-right-panel {
1810
- background: var(--ide-panel-bg-alt);
1811
- border-left: 1px solid var(--ide-border);
2055
+ /* A card like the sidebar: the seam on its left is drawn by the gutter, not
2056
+ by a border here, so the two never double up. */
2057
+ background: var(--ide-panel-bg);
1812
2058
  display: flex;
1813
2059
  flex-direction: column;
1814
2060
  overflow: hidden;
@@ -1817,16 +2063,10 @@ html, body, #mbeditor-root {
1817
2063
  max-width: 600px;
1818
2064
  }
1819
2065
 
1820
- .gitpanel-divider {
1821
- width: 5px;
1822
- background: var(--ide-panel-bg-alt);
1823
- border-left: 1px solid var(--ide-border);
1824
- }
1825
-
1826
2066
  .ide-git-panel {
1827
2067
  width: 100%;
1828
2068
  height: 100%;
1829
- background: var(--ide-panel-bg-alt);
2069
+ background: var(--ide-panel-bg);
1830
2070
  display: flex;
1831
2071
  flex-direction: column;
1832
2072
  overflow: hidden;
@@ -2120,6 +2360,10 @@ html, body, #mbeditor-root {
2120
2360
  background: var(--ide-hover-bg);
2121
2361
  }
2122
2362
 
2363
+ /* Nothing to click, so nothing that looks clickable. */
2364
+ .collapsible-header-static { cursor: default; }
2365
+ .collapsible-header-static:hover { background: none; }
2366
+
2123
2367
 
2124
2368
  .collapsible-toggle {
2125
2369
  font-size: 8px;
@@ -2208,7 +2452,7 @@ html, body, #mbeditor-root {
2208
2452
 
2209
2453
  /* Open-editors panel: height is controlled by the --open-editors-height CSS variable
2210
2454
  set inline from React state (default 140px ≈ 5-6 items), drag-resizable via the
2211
- .open-editors-resize-handle strip below. */
2455
+ gutter below it. */
2212
2456
  .ide-sidebar-fixed .collapsible-content {
2213
2457
  max-height: var(--open-editors-height, 140px);
2214
2458
  overflow-y: auto;
@@ -2218,17 +2462,6 @@ html, body, #mbeditor-root {
2218
2462
  .ide-sidebar-fixed .collapsible-content::-webkit-scrollbar-track { background: transparent; }
2219
2463
  .ide-sidebar-fixed .collapsible-content::-webkit-scrollbar-thumb { background: var(--ide-scrollbar); border-radius: 3px; }
2220
2464
 
2221
- .open-editors-resize-handle {
2222
- height: 4px;
2223
- flex-shrink: 0;
2224
- cursor: row-resize;
2225
- background: transparent;
2226
- transition: background 0.15s;
2227
- }
2228
- .open-editors-resize-handle:hover {
2229
- background: var(--ide-border);
2230
- }
2231
-
2232
2465
  .open-editors-group-header {
2233
2466
  font-size: 10px;
2234
2467
  padding-left: 8px;
@@ -2245,17 +2478,50 @@ html, body, #mbeditor-root {
2245
2478
  transition: opacity 0.12s ease;
2246
2479
  }
2247
2480
 
2248
- .ide-icon-btn {
2249
- min-width: 24px;
2250
- height: 24px;
2251
- background: transparent;
2252
- border: none;
2253
- border-radius: 4px;
2481
+ /* VS Code-style Open Editors row: 22px, filename dims when the tab isn't the
2482
+ active one, the directory trails it in muted small text, and the
2483
+ move-to-group / close controls stay invisible until the row is hovered. */
2484
+ .open-editors-item { height: 22px; padding: 0 8px; }
2485
+ .open-editors-name { color: var(--ide-text-muted); }
2486
+ .open-editors-item.active .open-editors-name { color: var(--ide-text); }
2487
+ .open-editors-dir {
2488
+ margin-left: 6px;
2254
2489
  color: var(--ide-text-muted);
2255
- display: inline-flex;
2256
- align-items: center;
2257
- justify-content: center;
2258
- cursor: pointer;
2490
+ font-size: 12px;
2491
+ overflow: hidden;
2492
+ text-overflow: ellipsis;
2493
+ white-space: nowrap;
2494
+ min-width: 0;
2495
+ }
2496
+
2497
+ .open-editors-item .tab-split { opacity: 0; }
2498
+ .open-editors-item:hover .tab-split { opacity: 0.6; }
2499
+
2500
+ /* Dot and close button share one cell: the dot shows at rest for a dirty
2501
+ tab, the close button takes over on hover — same slot, same as VS Code's
2502
+ tab UI, so a hover never shifts the row's width. */
2503
+ .open-editors-close-slot {
2504
+ display: grid;
2505
+ width: 20px;
2506
+ height: 16px;
2507
+ }
2508
+ .open-editors-close-slot > * { grid-area: 1 / 1; place-self: center; }
2509
+ .open-editors-dirty-dot { font-size: 5px; color: #e3d286; opacity: 1; }
2510
+ .open-editors-item .tab-close { opacity: 0; }
2511
+ .open-editors-item:hover .open-editors-dirty-dot { opacity: 0; }
2512
+ .open-editors-item:hover .tab-close { opacity: 1; }
2513
+
2514
+ .ide-icon-btn {
2515
+ min-width: 24px;
2516
+ height: 24px;
2517
+ background: transparent;
2518
+ border: none;
2519
+ border-radius: 5px;
2520
+ color: var(--ide-text-muted);
2521
+ display: inline-flex;
2522
+ align-items: center;
2523
+ justify-content: center;
2524
+ cursor: pointer;
2259
2525
  font-size: 13px;
2260
2526
  transition: background 0.12s ease, color 0.12s ease;
2261
2527
  flex-shrink: 0;
@@ -2274,8 +2540,10 @@ html, body, #mbeditor-root {
2274
2540
  display: flex;
2275
2541
  justify-content: flex-start;
2276
2542
  align-items: center;
2277
- padding: 4px 8px;
2278
- background: var(--ide-panel-bg);
2543
+ padding: 0 12px;
2544
+ height: 22px;
2545
+ min-height: 22px;
2546
+ background: var(--ide-bg);
2279
2547
  border-bottom: 1px solid var(--ide-border);
2280
2548
  gap: 4px;
2281
2549
  overflow: hidden;
@@ -2289,14 +2557,18 @@ html, body, #mbeditor-root {
2289
2557
  white-space: nowrap;
2290
2558
  text-overflow: ellipsis;
2291
2559
  text-align: left;
2292
- font-size: 11px;
2560
+ font-size: 13px;
2293
2561
  color: var(--ide-text-muted);
2294
2562
  padding: 0 4px;
2295
2563
  cursor: default;
2296
2564
  user-select: text;
2297
- font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
2298
2565
  }
2299
2566
 
2567
+ .ide-breadcrumb-dir { color: var(--ide-text-muted); }
2568
+ .ide-breadcrumb-file { color: var(--ide-text); }
2569
+ .ide-breadcrumb-file-icon { margin-right: 5px; font-size: 12px; }
2570
+ .ide-breadcrumb-sep { font-size: 9px; color: var(--ide-text-muted); margin: 0 4px; }
2571
+
2300
2572
  .ide-editor-toolbar .ide-icon-btn {
2301
2573
  flex-shrink: 1;
2302
2574
  min-width: 24px;
@@ -2324,10 +2596,12 @@ html, body, #mbeditor-root {
2324
2596
  }
2325
2597
 
2326
2598
  .ide-methods-dropdown {
2327
- background: #252526;
2328
- border: 1px solid #454545;
2329
- border-radius: 4px;
2330
- box-shadow: 0 4px 14px rgba(0,0,0,0.55);
2599
+ backdrop-filter: blur(20px) saturate(180%);
2600
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
2601
+ background: color-mix(in srgb, var(--ide-panel-bg) 78%, transparent);
2602
+ border: 1px solid color-mix(in srgb, var(--ide-text) 16%, transparent);
2603
+ border-radius: 8px;
2604
+ box-shadow: 0 8px 24px rgba(0,0,0,0.45);
2331
2605
  min-width: 200px;
2332
2606
  max-width: 340px;
2333
2607
  max-height: 320px;
@@ -2356,13 +2630,8 @@ html, body, #mbeditor-root {
2356
2630
 
2357
2631
  .ide-methods-dropdown-item:hover,
2358
2632
  .ide-methods-dropdown-item:focus {
2359
- background: #094771;
2360
- color: #fff;
2361
- }
2362
-
2363
- .ide-methods-dropdown-item:focus {
2364
- outline: 1px solid #75beff;
2365
- outline-offset: -1px;
2633
+ background: var(--ide-active-bg);
2634
+ color: var(--ide-text);
2366
2635
  }
2367
2636
 
2368
2637
  .ide-methods-dropdown-line {
@@ -2423,6 +2692,12 @@ html, body, #mbeditor-root {
2423
2692
  .monaco-editor .line-numbers.mbeditor-gitline-deleted.mbeditor-gitline-added,
2424
2693
  .monaco-editor .line-numbers.mbeditor-gitline-deleted.mbeditor-gitline-modified { color: var(--ide-danger); }
2425
2694
 
2695
+ /* Monaco's own active-selection rule is `.monaco-editor .focused .selected-text`,
2696
+ which wants `focused` on a DESCENDANT. In this build the class lands on the
2697
+ `.monaco-editor` element itself, so that rule never matches and every selection
2698
+ paints with the dim inactive colour. Same theme variable, one less space. */
2699
+ .monaco-editor.focused .selected-text { background-color: var(--vscode-editor-selectionBackground); }
2700
+
2426
2701
  .ide-methods-dropdown-message {
2427
2702
  padding: 8px 12px;
2428
2703
  color: #858585;
@@ -2511,6 +2786,26 @@ html, body, #mbeditor-root {
2511
2786
  margin: 4px 0;
2512
2787
  }
2513
2788
 
2789
+ .ide-tab-context-menu-item {
2790
+ padding: 6px 14px;
2791
+ cursor: pointer;
2792
+ color: #ccc;
2793
+ font-size: 12px;
2794
+ display: flex;
2795
+ align-items: center;
2796
+ gap: 8px;
2797
+ background: transparent;
2798
+ }
2799
+
2800
+ .ide-tab-context-menu-item:hover {
2801
+ background: #094771;
2802
+ }
2803
+
2804
+ .ide-tab-context-menu-item i {
2805
+ width: 14px;
2806
+ text-align: center;
2807
+ }
2808
+
2514
2809
  .collapsible-content {
2515
2810
  animation: collapsible-open 0.15s ease;
2516
2811
  }
@@ -2526,41 +2821,59 @@ html, body, #mbeditor-root {
2526
2821
  }
2527
2822
  }
2528
2823
 
2529
- /* ── Quick Open Dialog ─────────────────────────────────────── */
2824
+ /* ── Quick Open Dialog ───────────────────────────────────────
2825
+ VS Code's command palette: a 600px sheet anchored near the top of the
2826
+ window, not a centred modal. The rows are 22px and single-line — the path
2827
+ sits beside the name rather than under it, which is what lets a full
2828
+ viewport of results be scanned without scrolling. */
2530
2829
  .quick-open-overlay {
2531
2830
  position: fixed;
2532
2831
  inset: 0;
2533
- background: rgba(0,0,0,0.6);
2832
+ background: rgba(0,0,0,0.35);
2534
2833
  display: flex;
2535
2834
  align-items: flex-start;
2536
2835
  justify-content: center;
2537
- padding-top: 15vh;
2836
+ padding-top: 6px;
2538
2837
  z-index: 9999;
2539
2838
  }
2540
2839
 
2840
+ /* Always a frosted panel — VS Code's Quick Open floats over the editor, not
2841
+ inside it, so it stays readable whether or not the glass theme is on. */
2541
2842
  .quick-open-box {
2542
- background: var(--ide-panel-bg);
2543
- border: 1px solid var(--ide-border-input);
2843
+ backdrop-filter: blur(20px) saturate(180%);
2844
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
2845
+ background: color-mix(in srgb, var(--ide-panel-bg) 78%, transparent);
2846
+ border: 1px solid color-mix(in srgb, var(--ide-text) 16%, transparent);
2544
2847
  border-radius: 8px;
2545
- width: 500px;
2546
- max-height: 400px;
2848
+ width: min(600px, 94vw);
2849
+ max-height: min(440px, 72vh);
2547
2850
  display: flex;
2548
2851
  flex-direction: column;
2549
- box-shadow: 0 16px 48px rgba(0,0,0,0.7);
2852
+ box-shadow: 0 8px 24px rgba(0,0,0,0.45);
2550
2853
  overflow: hidden;
2551
2854
  }
2552
2855
 
2856
+ @media (prefers-reduced-transparency: reduce) {
2857
+ .quick-open-box,
2858
+ .ide-methods-dropdown {
2859
+ backdrop-filter: none;
2860
+ -webkit-backdrop-filter: none;
2861
+ background: var(--ide-panel-bg);
2862
+ }
2863
+ }
2864
+
2553
2865
  .quick-open-input-wrap {
2554
2866
  position: relative;
2867
+ padding: 6px;
2868
+ flex-shrink: 0;
2555
2869
  }
2556
2870
 
2557
2871
  .quick-open-input {
2558
2872
  background: transparent;
2559
2873
  border: none;
2560
- border-bottom: 1px solid var(--ide-border);
2561
2874
  color: var(--ide-text);
2562
- padding: 12px 34px 12px 16px;
2563
- font-size: 14px;
2875
+ padding: 4px 30px 4px 8px;
2876
+ font-size: 13px;
2564
2877
  outline: none;
2565
2878
  width: 100%;
2566
2879
  box-sizing: border-box;
@@ -2569,13 +2882,13 @@ html, body, #mbeditor-root {
2569
2882
 
2570
2883
  .quick-open-clear-btn {
2571
2884
  position: absolute;
2572
- right: 6px;
2885
+ right: 10px;
2573
2886
  top: 50%;
2574
2887
  transform: translateY(-50%);
2575
2888
  width: 22px;
2576
2889
  height: 22px;
2577
2890
  border: none;
2578
- border-radius: 4px;
2891
+ border-radius: 5px;
2579
2892
  background: transparent;
2580
2893
  color: var(--ide-text-muted);
2581
2894
  cursor: pointer;
@@ -2586,51 +2899,62 @@ html, body, #mbeditor-root {
2586
2899
 
2587
2900
  .quick-open-clear-btn:hover {
2588
2901
  background: var(--ide-hover-bg);
2589
- color: var(--ide-text-dim);
2902
+ color: var(--ide-text);
2590
2903
  }
2591
2904
 
2592
2905
  .quick-open-results {
2593
2906
  overflow-y: auto;
2594
2907
  flex: 1;
2908
+ padding-bottom: 4px;
2595
2909
  }
2596
2910
 
2597
2911
  .quick-open-result {
2598
- padding: 6px 16px;
2912
+ padding: 0 10px;
2913
+ height: 22px;
2599
2914
  cursor: pointer;
2600
2915
  display: flex;
2601
2916
  align-items: center;
2602
2917
  gap: 8px;
2603
- font-size: 12px;
2604
- transition: background 0.1s;
2918
+ font-size: 13px;
2605
2919
  }
2606
2920
 
2607
2921
  .quick-open-result-icon {
2608
2922
  flex-shrink: 0;
2609
2923
  width: 16px;
2924
+ font-size: 12px;
2610
2925
  text-align: center;
2611
2926
  color: #78b4e0;
2612
2927
  }
2613
2928
 
2929
+ /* Name and path on one line: the name at its natural width, the path taking
2930
+ the slack and ellipsing. Two stacked lines would double the row height. */
2614
2931
  .quick-open-result-body {
2615
2932
  min-width: 0;
2616
2933
  flex: 1;
2934
+ display: flex;
2935
+ align-items: baseline;
2936
+ gap: 8px;
2937
+ overflow: hidden;
2617
2938
  }
2618
2939
 
2619
- .quick-open-result:hover,
2620
- .quick-open-result.selected { background: var(--ide-active-bg); }
2940
+ .quick-open-result:hover { background: var(--ide-hover-bg); }
2941
+ .quick-open-result.selected { background: var(--ide-active-bg); color: var(--ide-text); }
2621
2942
 
2622
- .quick-open-result-name { color: var(--ide-text); }
2623
- .quick-open-result-path { color: var(--ide-text-muted); font-size: 11px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2943
+ .quick-open-result-name { color: var(--ide-text); flex-shrink: 0; white-space: nowrap; }
2944
+ .quick-open-result-path { color: var(--ide-text-muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2624
2945
 
2625
2946
  .quick-open-section { margin-bottom: 4px; }
2626
- .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; }
2947
+ .quick-open-section-header { padding: 8px 10px 4px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--ide-text); font-weight: 700; }
2948
+ .quick-open-section-header-muted { color: var(--ide-text-muted); font-weight: 600; letter-spacing: 0.08em; }
2949
+
2950
+ .quick-open-recent-label { font-size: 12px; color: var(--ide-text-muted); flex-shrink: 0; }
2627
2951
 
2628
2952
  .quick-open-star-btn {
2629
2953
  flex-shrink: 0;
2630
2954
  background: none;
2631
2955
  border: none;
2632
2956
  cursor: pointer;
2633
- padding: 2px 6px;
2957
+ padding: 0 4px;
2634
2958
  color: var(--ide-text-muted);
2635
2959
  font-size: 12px;
2636
2960
  line-height: 1;
@@ -2646,9 +2970,9 @@ html, body, #mbeditor-root {
2646
2970
  .quick-open-result-dir .quick-open-result-name { color: #dcb67a; }
2647
2971
 
2648
2972
  .quick-open-empty-hint {
2649
- padding: 20px 16px;
2973
+ padding: 24px 18px;
2650
2974
  color: var(--ide-text-muted);
2651
- font-size: 12px;
2975
+ font-size: 13px;
2652
2976
  text-align: center;
2653
2977
  line-height: 1.6;
2654
2978
  }
@@ -2743,48 +3067,177 @@ html, body, #mbeditor-root {
2743
3067
  .ide-draft-restore-btn:hover { background: var(--ide-hover-bg); }
2744
3068
  .ide-draft-restore-btn-primary {
2745
3069
  background: var(--ide-accent);
2746
- color: var(--ide-accent-text);
3070
+ color: var(--ide-on-accent);
2747
3071
  border-color: var(--ide-accent);
2748
3072
  }
2749
3073
  .ide-draft-restore-btn-primary:hover { opacity: 0.9; }
2750
3074
 
2751
3075
  /* ── Settings panel ─────────────────────────────────────────── */
2752
- .ide-settings-panel { display: flex; flex-direction: column; height: 100%; overflow-y: auto; }
2753
- .ide-settings-body { padding: 8px 12px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; align-items: start; }
2754
- /* Full-width rows: label sits inline next to the control */
2755
- .ide-settings-row { display: flex; flex-direction: row; align-items: center; gap: 8px; grid-column: 1/-1; }
2756
- /* Half-width rows: label stacked above control, fits in one grid column */
2757
- .ide-settings-row-half { display: flex; flex-direction: column; gap: 2px; grid-column: auto; align-items: flex-start; }
2758
- /* Full-width rows with stacked label — for wide text inputs like font family */
2759
- .ide-settings-row-full { display: flex; flex-direction: column; gap: 2px; grid-column: 1/-1; align-items: flex-start; }
2760
- .ide-settings-row-full .ide-settings-input-wide { width: 100%; flex: none; box-sizing: border-box; }
2761
- /* Checkbox rows: full-width, label on left, checkbox on right */
2762
- .ide-settings-row-check { display: flex; flex-direction: row; align-items: center; justify-content: space-between; gap: 6px; grid-column: 1/-1; padding: 3px 0; }
2763
- .ide-settings-label { font-size: 11px; color: var(--ide-text-muted); white-space: nowrap; }
2764
- /* Inputs in full-width rows stay narrow */
2765
- .ide-settings-input { background: var(--ide-input-bg); color: var(--ide-text); border: 1px solid var(--ide-border-input); border-radius: 3px; padding: 4px 6px; font-size: 12px; width: 72px; box-sizing: border-box; }
2766
- .ide-settings-input-wide { font-family: monospace; flex: 1; width: auto; }
2767
- /* Selects in half-width rows use Pico styling (inherit theme vars); just cap size */
2768
- .ide-settings-row-half select { width: 100%; }
2769
- .ide-settings-checkbox { width: auto; cursor: pointer; flex-shrink: 0; }
2770
- .ide-settings-reset-btn { margin-top: 4px; grid-column: 1/-1; background: var(--ide-panel-bg-alt); color: var(--ide-text-muted); border: 1px solid var(--ide-border-input); border-radius: 3px; padding: 5px 10px; font-size: 11px; cursor: pointer; align-self: flex-start; }
2771
- .ide-settings-reset-btn:hover { background: var(--ide-border-input); color: var(--ide-text-dim); }
2772
- .ide-settings-section-header { font-size: 10px; font-weight: 700; color: var(--ide-text-muted); text-transform: uppercase; letter-spacing: 1px; padding: 8px 0 3px; border-bottom: 1px solid var(--ide-border); margin-top: 6px; grid-column: 1/-1; }
2773
- .ide-settings-section-header:first-child { margin-top: 0; }
2774
- .ide-settings-row-link { grid-column: 1/-1; flex-direction: row; align-items: center; justify-content: space-between; }
2775
- .ide-settings-config-link { background: none; border: none; color: var(--ide-accent-fg); font-size: 11px; cursor: pointer; padding: 0; display: flex; align-items: center; gap: 3px; text-decoration: underline; text-underline-offset: 2px; }
2776
- .ide-settings-config-link:hover { color: var(--ide-accent); }
3076
+ /* ══ SETTINGS MODAL ═════════════════════════════════════════════════════════
3077
+ * VS Code-style: left nav of sections, scrolling right pane of rows.
3078
+ * ─────────────────────────────────────────────────────────────────────────── */
3079
+ .ide-settings-overlay {
3080
+ position: fixed;
3081
+ inset: 0;
3082
+ background: rgba(0,0,0,0.45);
3083
+ z-index: 9100;
3084
+ display: flex;
3085
+ align-items: center;
3086
+ justify-content: center;
3087
+ }
2777
3088
 
2778
- /* Settings tab (pane view) */
2779
- .ide-settings-tab-content {
3089
+ /* Frosted like Quick Open — it floats over the editor, not inside it. */
3090
+ .ide-settings-modal {
3091
+ backdrop-filter: blur(20px) saturate(180%);
3092
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
3093
+ background: color-mix(in srgb, var(--ide-panel-bg) 82%, transparent);
3094
+ border: 1px solid color-mix(in srgb, var(--ide-text) 16%, transparent);
3095
+ border-radius: 8px;
3096
+ width: min(900px, 90vw);
3097
+ height: min(640px, 85vh);
2780
3098
  display: flex;
2781
3099
  flex-direction: column;
2782
- height: 100%;
3100
+ box-shadow: 0 8px 32px rgba(0,0,0,0.5);
3101
+ overflow: hidden;
3102
+ color: var(--ide-text);
3103
+ }
3104
+
3105
+ @media (prefers-reduced-transparency: reduce) {
3106
+ .ide-settings-modal {
3107
+ backdrop-filter: none;
3108
+ -webkit-backdrop-filter: none;
3109
+ background: var(--ide-panel-bg);
3110
+ }
3111
+ }
3112
+
3113
+ .ide-settings-modal-header {
3114
+ height: 44px;
3115
+ flex-shrink: 0;
3116
+ display: flex;
3117
+ align-items: center;
3118
+ gap: 12px;
3119
+ padding: 0 12px;
3120
+ border-bottom: 1px solid var(--ide-border);
3121
+ }
3122
+ .ide-settings-modal-title { font-size: 13px; font-weight: 600; color: var(--ide-text); flex-shrink: 0; }
3123
+ .ide-settings-search {
3124
+ flex: 1;
3125
+ min-width: 0;
3126
+ background: var(--ide-input-bg);
3127
+ color: var(--ide-text);
3128
+ border: 1px solid var(--ide-border-input);
3129
+ border-radius: 8px;
3130
+ padding: 5px 10px;
3131
+ font-size: 12px;
3132
+ margin-bottom: 0;
3133
+ }
3134
+ .ide-settings-search::placeholder { color: var(--ide-text-muted); }
3135
+ /* Scoped under the modal: a global `button:not(.pico-btn)` rule sets 12px
3136
+ and would otherwise shrink the glyph to a speck. */
3137
+ .ide-settings-modal .ide-settings-modal-close {
3138
+ width: 28px;
3139
+ height: 28px;
3140
+ display: inline-flex;
3141
+ align-items: center;
3142
+ justify-content: center;
3143
+ background: none;
3144
+ border: none;
3145
+ border-radius: 6px;
3146
+ color: var(--ide-text-muted);
3147
+ font-size: 16px;
3148
+ line-height: 1;
3149
+ cursor: pointer;
3150
+ padding: 0;
3151
+ margin-bottom: 0;
3152
+ }
3153
+ .ide-settings-modal .ide-settings-modal-close:hover { color: var(--ide-text); background: var(--ide-hover-bg); }
3154
+
3155
+ .ide-settings-modal-body { flex: 1; display: flex; min-height: 0; }
3156
+
3157
+ .ide-settings-nav {
3158
+ width: 180px;
3159
+ flex-shrink: 0;
3160
+ border-right: 1px solid var(--ide-border);
3161
+ padding: 10px 0;
3162
+ overflow-y: auto;
3163
+ display: flex;
3164
+ flex-direction: column;
3165
+ justify-content: flex-start; /* a shared nav rule sets space-between */
3166
+ }
3167
+ .ide-settings-nav-item {
3168
+ background: none;
3169
+ border: none;
3170
+ border-left: 2px solid transparent;
3171
+ color: var(--ide-text-muted);
3172
+ font-size: 11px;
3173
+ font-weight: 600;
3174
+ text-transform: uppercase;
3175
+ letter-spacing: 0.6px;
3176
+ text-align: left;
3177
+ padding: 6px 14px;
3178
+ cursor: pointer;
3179
+ margin-bottom: 0;
3180
+ width: 100%;
3181
+ }
3182
+ .ide-settings-nav-item:hover { color: var(--ide-text); }
3183
+ .ide-settings-nav-item.active { color: var(--ide-text); border-left-color: var(--ide-accent); }
3184
+
3185
+ .ide-settings-body {
3186
+ flex: 1;
3187
+ min-width: 0;
2783
3188
  overflow-y: auto;
2784
- background: var(--bg-panel, var(--ide-bg));
2785
- color: var(--text-main, var(--ide-text));
3189
+ padding: 12px 20px 20px;
3190
+ }
3191
+ .ide-settings-section { padding-top: 10px; }
3192
+ .ide-settings-section-header {
3193
+ font-size: 13px;
3194
+ font-weight: 600;
3195
+ color: var(--ide-text);
3196
+ margin: 0 0 10px;
3197
+ padding-bottom: 4px;
3198
+ border-bottom: 1px solid var(--ide-border);
2786
3199
  }
2787
3200
 
3201
+ .ide-settings-row {
3202
+ display: flex;
3203
+ flex-direction: column;
3204
+ align-items: flex-start;
3205
+ gap: 3px;
3206
+ margin-bottom: 12px;
3207
+ }
3208
+ .ide-settings-row-check {
3209
+ display: grid;
3210
+ grid-template-columns: auto 1fr;
3211
+ column-gap: 8px;
3212
+ align-items: start;
3213
+ }
3214
+ .ide-settings-row-check .ide-settings-checkbox { grid-column: 1; grid-row: 1; margin: 2px 0 0; }
3215
+ .ide-settings-row-check .ide-settings-label { grid-column: 2; grid-row: 1; }
3216
+ .ide-settings-row-check .ide-settings-desc { grid-column: 2; grid-row: 2; }
3217
+
3218
+ .ide-settings-label { font-size: 13px; color: var(--ide-text); }
3219
+ .ide-settings-desc { font-size: 12px; color: var(--ide-text-muted); line-height: 1.4; }
3220
+ .ide-settings-input {
3221
+ background: var(--ide-input-bg);
3222
+ color: var(--ide-text);
3223
+ border: 1px solid var(--ide-border-input);
3224
+ border-radius: 4px;
3225
+ padding: 4px 6px;
3226
+ font-size: 12px;
3227
+ width: 260px;
3228
+ max-width: 100%;
3229
+ box-sizing: border-box;
3230
+ margin-top: 2px;
3231
+ }
3232
+ .ide-settings-input-number { width: 100px; }
3233
+ .ide-settings-input-wide { font-family: monospace; width: 100%; }
3234
+ .ide-settings-checkbox { width: auto; cursor: pointer; flex-shrink: 0; }
3235
+
3236
+ .ide-settings-config-link { background: none; border: none; color: var(--ide-accent-fg); font-size: 12px; cursor: pointer; padding: 0; display: flex; align-items: center; gap: 3px; text-decoration: underline; text-underline-offset: 2px; margin-bottom: 0; }
3237
+ .ide-settings-config-link:hover { color: var(--ide-accent); }
3238
+ .ide-settings-empty { font-size: 12px; color: var(--ide-text-muted); padding: 20px 0; }
3239
+ .ide-settings-reset-btn { margin-top: 16px; background: var(--ide-panel-bg-alt); color: var(--ide-text-muted); border: 1px solid var(--ide-border-input); border-radius: 4px; padding: 5px 10px; font-size: 12px; cursor: pointer; }
3240
+ .ide-settings-reset-btn:hover { background: var(--ide-border-input); color: var(--ide-text-dim); }
2788
3241
 
2789
3242
  /* ══ PICO CSS COMPACT OVERRIDES ═════════════════════════════════════════════
2790
3243
  * Pico's classless variant inflates native elements with generous padding.
@@ -2881,15 +3334,20 @@ textarea {
2881
3334
  margin-bottom: 0;
2882
3335
  }
2883
3336
 
2884
- /* Status bar buttons override (22px tall, full-bleed) */
3337
+ /* Status bar buttons override (22px tall, full-bleed).
3338
+ * line-height must be set here too, not just on .ide-statusbar: Pico gives
3339
+ * <button> its own line-height, so it does not inherit the bar's, and the
3340
+ * count next to an icon ended up in a 16.5px line box beside the icon's 11px
3341
+ * one. */
2885
3342
  .ide-statusbar button,
2886
3343
  .statusbar-btn {
2887
3344
  --pico-form-element-spacing-vertical: 0;
2888
- --pico-form-element-spacing-horizontal: 8px;
2889
- height: 22px;
2890
- padding: 0 8px;
2891
- font-size: 11px;
2892
- border-radius: 2px;
3345
+ --pico-form-element-spacing-horizontal: 6px;
3346
+ height: 20px;
3347
+ padding: 0 6px;
3348
+ font-size: 12px;
3349
+ line-height: 1;
3350
+ border-radius: 5px;
2893
3351
  border: none;
2894
3352
  background: transparent;
2895
3353
  color: var(--ide-accent-text);
@@ -2986,7 +3444,19 @@ button:not(.pico-btn) { margin-bottom: 0; }
2986
3444
  border-radius: 0;
2987
3445
  }
2988
3446
  /* Re-apply border-radius for buttons that intentionally need it */
2989
- .ide-shell .ide-titlebar-search { border-radius: 999px; } /* pill */
3447
+ .ide-shell .ide-titlebar-search { border-radius: 6px; }
3448
+ .ide-shell .collab-avatar {
3449
+ width: 9px;
3450
+ height: 9px;
3451
+ padding: 0;
3452
+ border-radius: 50%;
3453
+ }
3454
+ .ide-shell .collab-avatar-more {
3455
+ width: auto;
3456
+ height: auto;
3457
+ padding: 0;
3458
+ border-radius: 0;
3459
+ }
2990
3460
  .ide-shell .project-action-btn,
2991
3461
  .ide-shell .git-header-btn,
2992
3462
  .ide-shell .git-section-action-btn,
@@ -3102,15 +3572,6 @@ button:not(.pico-btn) { margin-bottom: 0; }
3102
3572
  background: var(--ide-panel-bg, #1e1e1e);
3103
3573
  border-top: 1px solid var(--ide-border, #333);
3104
3574
  }
3105
- .ide-log-resize {
3106
- flex: 0 0 auto;
3107
- height: 6px;
3108
- cursor: ns-resize;
3109
- background: transparent;
3110
- }
3111
- .ide-log-resize:hover {
3112
- background: var(--ide-accent, #569cd6);
3113
- }
3114
3575
  .ide-log-header {
3115
3576
  display: flex;
3116
3577
  align-items: center;
@@ -3178,25 +3639,35 @@ button:not(.pico-btn) { margin-bottom: 0; }
3178
3639
  background: var(--ide-panel-bg, #1e1e1e);
3179
3640
  border-top: 1px solid var(--ide-border, #333);
3180
3641
  }
3181
- .ide-problems-resize {
3182
- flex: 0 0 auto;
3183
- height: 6px;
3184
- cursor: ns-resize;
3185
- background: transparent;
3186
- }
3187
- .ide-problems-resize:hover { background: var(--ide-accent, #569cd6); }
3188
3642
  .ide-problems-header {
3189
3643
  display: flex;
3190
3644
  align-items: center;
3191
3645
  gap: 8px;
3192
- padding: 4px 10px;
3646
+ height: 35px;
3647
+ padding: 0 10px;
3193
3648
  border-bottom: 1px solid var(--ide-border, #333);
3194
3649
  font-size: 12px;
3195
- color: var(--ide-fg-muted, #ccc);
3650
+ color: var(--ide-text, #ccc);
3196
3651
  }
3197
- .ide-problems-title { font-weight: 600; }
3198
- /* Severity toggles. Pushed to the right of the summary; the text filter that
3199
- follows keeps its own left margin off `auto` so the two sit together. */
3652
+ .ide-problems-title {
3653
+ font-size: 11px;
3654
+ font-weight: 600;
3655
+ text-transform: uppercase;
3656
+ letter-spacing: 0.06em;
3657
+ }
3658
+ .ide-problems-total-pill {
3659
+ display: inline-flex;
3660
+ align-items: center;
3661
+ height: 18px;
3662
+ padding: 0 7px;
3663
+ border-radius: 999px;
3664
+ background: var(--ide-hover-bg, #2a2a2a);
3665
+ color: var(--ide-text, #ccc);
3666
+ font-size: 11px;
3667
+ font-variant-numeric: tabular-nums;
3668
+ }
3669
+ /* Severity toggles. Pushed to the right of the title/count; the text filter
3670
+ that follows keeps its own left margin off `auto` so the two sit together. */
3200
3671
  .ide-problems-severity-filter {
3201
3672
  margin-left: auto;
3202
3673
  display: flex;
@@ -3207,63 +3678,42 @@ button:not(.pico-btn) { margin-bottom: 0; }
3207
3678
  align-items: center;
3208
3679
  gap: 4px;
3209
3680
  background: transparent;
3210
- border: 1px solid transparent;
3681
+ border: none;
3211
3682
  border-radius: 3px;
3212
3683
  padding: 1px 6px;
3213
3684
  font-size: 11px;
3214
3685
  font-variant-numeric: tabular-nums;
3215
3686
  cursor: pointer;
3216
- /* Off is legible but clearly recessive — a disabled-looking chip reads as
3217
- "broken" rather than "toggled off". */
3218
3687
  color: var(--ide-text-muted, #858585);
3219
- opacity: 0.55;
3220
3688
  }
3221
- .ide-problems-sev-chip:hover { opacity: 0.85; }
3222
- .ide-problems-sev-chip.is-on {
3223
- opacity: 1;
3224
- border-color: var(--ide-border, #333);
3225
- background: var(--ide-input-bg, #2d2d2d);
3226
- }
3227
- .ide-problems-sev-chip.is-on.ide-problems-sev-error { color: var(--ide-error, #f14c4c); }
3228
- .ide-problems-sev-chip.is-on.ide-problems-sev-warning { color: var(--ide-warning, #cca700); }
3229
- .ide-problems-sev-chip.is-on.ide-problems-sev-info { color: var(--ide-info, #3794ff); }
3689
+ .ide-problems-sev-chip:hover,
3690
+ .ide-problems-sev-chip.is-on { color: var(--ide-text, #ccc); }
3230
3691
 
3231
3692
  .ide-problems-filter {
3232
3693
  margin-left: 8px;
3233
3694
  background: var(--ide-input-bg, #2d2d2d);
3234
- color: var(--ide-fg, #eee);
3235
- border: 1px solid var(--ide-border, #333);
3236
- border-radius: 3px;
3237
- padding: 2px 6px;
3695
+ color: var(--ide-text, #eee);
3696
+ border: 1px solid var(--ide-border-input, #333);
3697
+ border-radius: 4px;
3698
+ padding: 0 6px;
3699
+ height: 22px;
3238
3700
  font-size: 12px;
3239
- width: 180px;
3701
+ width: 140px;
3240
3702
  }
3241
3703
  .ide-problems-btn {
3242
3704
  background: transparent;
3243
3705
  border: none;
3244
- color: var(--ide-fg-muted, #ccc);
3706
+ color: var(--ide-text-muted, #ccc);
3245
3707
  cursor: pointer;
3246
3708
  padding: 2px 6px;
3247
3709
  }
3248
- .ide-problems-btn:hover { color: var(--ide-fg, #fff); }
3710
+ .ide-problems-btn:hover { color: var(--ide-text, #fff); }
3249
3711
  .ide-problems-btn:disabled { opacity: 0.4; cursor: default; }
3250
- .ide-problems-btn:disabled:hover { color: var(--ide-fg-muted, #ccc); }
3712
+ .ide-problems-btn:disabled:hover { color: var(--ide-text-muted, #ccc); }
3251
3713
  .ide-problems-actions { display: flex; gap: 2px; }
3252
- .ide-problems-actions .ide-problems-btn { white-space: nowrap; }
3253
- .ide-problems-btn.is-on { color: var(--ide-accent, #0a84ff); }
3254
- .ide-testrun-stat { font-size: 11px; color: var(--ide-text-muted, #858585); }
3255
- .ide-testrun-pass { color: #4ec9b0; }
3256
- .ide-testrun-fail { color: #f14c4c; }
3257
- .ide-testrun-raw {
3258
- margin: 0;
3259
- padding: 8px 14px;
3260
- font-family: var(--ide-mono, monospace);
3261
- font-size: 11px;
3262
- line-height: 1.5;
3263
- white-space: pre;
3264
- overflow-x: auto;
3265
- color: var(--ide-fg, #eee);
3266
- }
3714
+ .ide-problems-actions .ide-icon-btn,
3715
+ .ide-problems-header > .ide-icon-btn { min-width: 22px; width: 22px; height: 22px; font-size: 13px; }
3716
+ .ide-problems-actions .ide-icon-btn:disabled { opacity: 0.4; cursor: default; pointer-events: none; }
3267
3717
  .ide-problems-body { flex: 1; overflow: auto; padding: 4px 0; font-size: 12px; }
3268
3718
  .ide-problems-empty {
3269
3719
  padding: 10px 14px;
@@ -3274,9 +3724,18 @@ button:not(.pico-btn) { margin-bottom: 0; }
3274
3724
  display: flex;
3275
3725
  align-items: center;
3276
3726
  gap: 6px;
3277
- padding: 5px 12px 3px;
3278
- color: var(--ide-fg-muted, #ccc);
3279
- font-weight: 600;
3727
+ height: 22px;
3728
+ padding: 0 12px;
3729
+ color: var(--ide-text, #ccc);
3730
+ }
3731
+ .ide-problems-file-name .fa-chevron-down {
3732
+ color: var(--ide-text-muted, #858585);
3733
+ font-size: 10px;
3734
+ width: 10px;
3735
+ }
3736
+ .ide-problems-file-name .ide-problems-dir {
3737
+ color: var(--ide-text-muted, #858585);
3738
+ font-size: 12px;
3280
3739
  }
3281
3740
  .ide-problems-file-count {
3282
3741
  background: var(--ide-hover-bg, #2a2a2a);
@@ -3291,22 +3750,23 @@ button:not(.pico-btn) { margin-bottom: 0; }
3291
3750
  align-items: baseline;
3292
3751
  gap: 8px;
3293
3752
  width: 100%;
3294
- padding: 3px 12px 3px 28px;
3753
+ height: 22px;
3754
+ padding: 0 12px 0 28px;
3295
3755
  background: transparent;
3296
3756
  border: none;
3297
- color: var(--ide-fg, #d4d4d4);
3757
+ color: var(--ide-text, #d4d4d4);
3298
3758
  font-size: 12px;
3299
3759
  font-family: inherit;
3300
3760
  text-align: left;
3301
3761
  cursor: pointer;
3302
3762
  }
3303
- .ide-problems-item:hover,
3304
- .ide-problems-item:focus-visible { background: var(--ide-hover-bg, #2a2a2a); }
3763
+ .ide-problems-item:hover { background: var(--ide-hover-bg, #2a2a2a); }
3764
+ .ide-problems-item:focus-visible { background: var(--ide-active-bg, #2a2a2a); }
3305
3765
  .ide-problems-icon { flex-shrink: 0; }
3306
3766
  .ide-problems-item-error .ide-problems-icon { color: var(--ide-danger); }
3307
3767
  .ide-problems-item-warning .ide-problems-icon { color: var(--ide-warning); }
3308
3768
  .ide-problems-item-info .ide-problems-icon { color: var(--ide-accent, #3794ff); }
3309
- .ide-problems-msg { flex-shrink: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
3769
+ .ide-problems-msg { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ide-text, #d4d4d4); }
3310
3770
  /* The offending source line, dimmed so it reads as context rather than as part
3311
3771
  of the message. It is the one part of the row allowed to shrink, so a long
3312
3772
  line gives way to the message and location instead of pushing them out. */
@@ -3316,7 +3776,6 @@ button:not(.pico-btn) { margin-bottom: 0; }
3316
3776
  text-overflow: ellipsis;
3317
3777
  white-space: nowrap;
3318
3778
  color: var(--ide-text-muted, #858585);
3319
- opacity: 0.8;
3320
3779
  font-family: var(--ide-mono, monospace);
3321
3780
  font-size: 11px;
3322
3781
  }
@@ -3345,12 +3804,24 @@ button:not(.pico-btn) { margin-bottom: 0; }
3345
3804
  margin-left: auto;
3346
3805
  color: var(--ide-text-muted, #858585);
3347
3806
  font-size: 11px;
3807
+ font-variant-numeric: tabular-nums;
3348
3808
  }
3349
3809
 
3350
3810
  /* ── Model graph ──────────────────────────────────────────────────────────
3351
3811
  An SVG ER diagram of the app's ActiveRecord models. The panel is narrow, so
3352
3812
  the SVG scrolls in both directions rather than being scaled down to
3353
3813
  illegibility. */
3814
+ /* The graph replaces the whole centre column when its view is open, so it
3815
+ takes the same box the panes and drawers would have. */
3816
+ .ide-model-graph-view {
3817
+ flex: 1;
3818
+ display: flex;
3819
+ flex-direction: column;
3820
+ min-width: 0;
3821
+ min-height: 0;
3822
+ overflow: hidden;
3823
+ }
3824
+
3354
3825
  .ide-model-graph { display: flex; flex-direction: column; height: 100%; min-height: 0; position: relative; }
3355
3826
  .ide-model-graph-empty {
3356
3827
  padding: 16px;
@@ -3496,18 +3967,11 @@ button:not(.pico-btn) { margin-bottom: 0; }
3496
3967
  .ide-problems-frame { padding-left: 28px; }
3497
3968
  .ide-problems-frame .ide-problems-code { opacity: 1; }
3498
3969
 
3499
- /* Status-bar ruby-lsp indicator. Healthy is a dim icon you can ignore;
3500
- degraded/off is amber with a label, because that one you should read. */
3501
- .statusbar-lsp { display: inline-flex; align-items: center; gap: 4px; }
3502
- .statusbar-lsp-ok { opacity: 0.45; }
3503
- .statusbar-lsp-ok:hover { opacity: 1; }
3504
- .statusbar-lsp-degraded,
3505
- .statusbar-lsp-off { color: var(--ide-warning); }
3506
-
3507
- /* Status-bar problems indicator: bug count then warning count, VS Code style. */
3508
- .statusbar-problems { display: inline-flex; align-items: center; gap: 4px; }
3509
- .statusbar-problems-error-icon { color: var(--ide-danger); }
3510
- .statusbar-problems-warning-icon { color: var(--ide-warning); }
3970
+ /* Status-bar problems indicator: bug count then warning count, VS Code style.
3971
+ The gap is the icon-to-its-own-number spacing; the warning icon opens the
3972
+ second pair, so it gets the wider separator. */
3973
+ .statusbar-problems { gap: 4px; }
3974
+ .statusbar-problems-warning-icon { margin-left: 8px; }
3511
3975
  .statusbar-problems-count { font-variant-numeric: tabular-nums; }
3512
3976
 
3513
3977
  /* Search decorations off across engines — Chrome/Safari draw their own
@@ -3646,6 +4110,7 @@ input.ide-model-graph-search[type="search"] {
3646
4110
  /* Pair-programming diagnostics. The chip only appears when a condition pairing
3647
4111
  needs has actually failed — an empty room is silence, not a warning. */
3648
4112
  .mbeditor-collab-trouble { color: var(--ide-warning, #cca700); }
4113
+ .mbeditor-conflict-chip { color: var(--ide-error, #f48771); }
3649
4114
  .mbeditor-modal-backdrop {
3650
4115
  position: fixed; inset: 0; z-index: 5000;
3651
4116
  background: rgba(0, 0, 0, 0.45);