mbeditor 0.13.1 → 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 (58) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +91 -0
  3. data/app/assets/javascripts/mbeditor/application.js +3 -0
  4. data/app/assets/javascripts/mbeditor/audit_log.js +165 -0
  5. data/app/assets/javascripts/mbeditor/collaboration_service.js +248 -26
  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 +429 -247
  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/LogPanel.js +3 -44
  17. data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +1168 -1243
  18. data/app/assets/javascripts/mbeditor/components/ModelGraph.js +94 -37
  19. data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +47 -65
  20. data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +26 -8
  21. data/app/assets/javascripts/mbeditor/components/SettingsModal.js +342 -0
  22. data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +2 -1
  23. data/app/assets/javascripts/mbeditor/components/TabBar.js +67 -98
  24. data/app/assets/javascripts/mbeditor/editor_plugins.js +687 -305
  25. data/app/assets/javascripts/mbeditor/file_import.js +13 -15
  26. data/app/assets/javascripts/mbeditor/file_service.js +38 -39
  27. data/app/assets/javascripts/mbeditor/git_service.js +15 -1
  28. data/app/assets/javascripts/mbeditor/history_service.js +5 -13
  29. data/app/assets/javascripts/mbeditor/search_service.js +9 -0
  30. data/app/assets/javascripts/mbeditor/tab_manager.js +127 -49
  31. data/app/assets/javascripts/mbeditor/websocket_service.js +13 -5
  32. data/app/assets/stylesheets/mbeditor/application.css +6 -1
  33. data/app/assets/stylesheets/mbeditor/editor.css +642 -248
  34. data/app/assets/stylesheets/mbeditor/glass.css +163 -0
  35. data/app/assets/stylesheets/mbeditor/themes.css +90 -30
  36. data/app/channels/mbeditor/collaboration_channel.rb +17 -4
  37. data/app/controllers/mbeditor/application_controller.rb +26 -3
  38. data/app/controllers/mbeditor/editors_controller.rb +117 -439
  39. data/app/services/mbeditor/archive_service.rb +137 -0
  40. data/app/services/mbeditor/collaboration_doc_store.rb +143 -14
  41. data/app/services/mbeditor/editor_state_service.rb +14 -51
  42. data/app/services/mbeditor/file_history_service.rb +222 -0
  43. data/app/services/mbeditor/git_info_service.rb +6 -0
  44. data/app/services/mbeditor/js_syntax_check_service.rb +42 -16
  45. data/app/services/mbeditor/lint_service.rb +137 -0
  46. data/app/services/mbeditor/locked_json_file.rb +67 -0
  47. data/app/services/mbeditor/process_runner.rb +32 -0
  48. data/app/services/mbeditor/ruby_lsp_result_translator.rb +226 -0
  49. data/app/services/mbeditor/search_replace_service.rb +8 -0
  50. data/app/views/layouts/mbeditor/application.html.erb +1 -1
  51. data/lib/mbeditor/audit_log.rb +203 -0
  52. data/lib/mbeditor/configuration.rb +6 -1
  53. data/lib/mbeditor/rack/pending_migration_bypass.rb +15 -9
  54. data/lib/mbeditor/route_map.rb +4 -0
  55. data/lib/mbeditor/ruby_lsp_client.rb +82 -14
  56. data/lib/mbeditor/version.rb +1 -1
  57. data/lib/mbeditor.rb +1 -0
  58. metadata +12 -2
@@ -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: 40px;
209
- min-width: 40px;
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: 40px;
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: 17px;
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 {
@@ -884,20 +930,21 @@ html, body, #mbeditor-root {
884
930
  .ide-statusbar {
885
931
  display: flex;
886
932
  align-items: center;
887
- background: var(--ide-accent);
933
+ background: var(--ide-statusbar-bg);
934
+ border-top: 1px solid var(--ide-border);
888
935
  height: 22px;
889
936
  padding: 0;
890
937
  gap: 0;
891
938
  flex-shrink: 0;
892
- font-size: 11px;
893
- line-height: 1;
939
+ font-size: 12px;
940
+ line-height: 20px; /* glyphs centre in the row; line-height 1 sat them high */
894
941
  color: var(--ide-accent-text);
895
942
  }
896
943
 
897
944
  /* One cell rhythm for every item, button or not. The branch, the message and
898
945
  * the chips each carried their own padding on top of the bar's, so the gaps
899
946
  * between them were all different widths. */
900
- .ide-statusbar > * { padding: 0 8px; }
947
+ .ide-statusbar > * { padding: 0 6px; }
901
948
 
902
949
  .statusbar-branch {
903
950
  display: flex;
@@ -929,6 +976,29 @@ html, body, #mbeditor-root {
929
976
  cursor: default;
930
977
  }
931
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
+
932
1002
  .statusbar-offline {
933
1003
  display: flex;
934
1004
  align-items: center;
@@ -948,19 +1018,32 @@ html, body, #mbeditor-root {
948
1018
  display: flex;
949
1019
  align-items: center;
950
1020
  gap: 4px;
951
- padding: 0 8px;
952
- height: 22px;
1021
+ padding: 0 6px;
1022
+ height: 20px;
1023
+ margin: 0 1px;
953
1024
  flex-shrink: 0;
954
1025
  background: transparent;
955
1026
  border: none;
956
1027
  color: var(--ide-accent-text);
957
1028
  cursor: pointer;
958
- font-size: 11px;
959
- border-radius: 2px;
1029
+ font-size: 12px;
1030
+ border-radius: 5px;
960
1031
  transition: background 0.1s;
961
1032
  white-space: nowrap;
962
1033
  }
963
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
+
964
1047
  .statusbar-btn:hover { background: color-mix(in srgb, var(--ide-accent-text) 15%, transparent); }
965
1048
  .statusbar-btn:disabled { opacity: 0.5; cursor: not-allowed; }
966
1049
 
@@ -1018,6 +1101,77 @@ html, body, #mbeditor-root {
1018
1101
  .collab-hovercard-row { color: var(--ide-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1019
1102
  .collab-hovercard-hint { margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--ide-border); color: var(--ide-text-muted); }
1020
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
+
1021
1175
  .statusbar-msg {
1022
1176
  margin-left: auto;
1023
1177
  color: var(--ide-accent-text);
@@ -1032,7 +1186,7 @@ html, body, #mbeditor-root {
1032
1186
  .statusbar-msg.success { color: var(--ide-success); }
1033
1187
 
1034
1188
  .statusbar-version {
1035
- color: color-mix(in srgb, var(--ide-accent-text) 45%, transparent);
1189
+ color: color-mix(in srgb, var(--ide-accent-text) 80%, transparent);
1036
1190
  font-size: 10px;
1037
1191
  white-space: nowrap;
1038
1192
  cursor: pointer;
@@ -1180,13 +1334,20 @@ html, body, #mbeditor-root {
1180
1334
  background: var(--ide-input-bg);
1181
1335
  color: var(--ide-text);
1182
1336
  border: 1px solid var(--ide-border-input);
1183
- border-radius: 4px;
1337
+ border-radius: 8px;
1184
1338
  font-size: 12px;
1185
1339
  outline: none;
1186
1340
  box-sizing: border-box;
1187
1341
  }
1188
1342
  .search-input::placeholder { color: var(--ide-text-muted); }
1189
- .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
+ }
1190
1351
 
1191
1352
  /* ── Results area wrapper (positions the loading overlay) ── */
1192
1353
  .search-results-area {
@@ -1891,8 +2052,9 @@ html, body, #mbeditor-root {
1891
2052
 
1892
2053
  /* ── Right-side Git Panel ───────────────────────────────── */
1893
2054
  .ide-git-right-panel {
1894
- background: var(--ide-panel-bg-alt);
1895
- 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);
1896
2058
  display: flex;
1897
2059
  flex-direction: column;
1898
2060
  overflow: hidden;
@@ -1901,16 +2063,10 @@ html, body, #mbeditor-root {
1901
2063
  max-width: 600px;
1902
2064
  }
1903
2065
 
1904
- .gitpanel-divider {
1905
- width: 5px;
1906
- background: var(--ide-panel-bg-alt);
1907
- border-left: 1px solid var(--ide-border);
1908
- }
1909
-
1910
2066
  .ide-git-panel {
1911
2067
  width: 100%;
1912
2068
  height: 100%;
1913
- background: var(--ide-panel-bg-alt);
2069
+ background: var(--ide-panel-bg);
1914
2070
  display: flex;
1915
2071
  flex-direction: column;
1916
2072
  overflow: hidden;
@@ -2204,6 +2360,10 @@ html, body, #mbeditor-root {
2204
2360
  background: var(--ide-hover-bg);
2205
2361
  }
2206
2362
 
2363
+ /* Nothing to click, so nothing that looks clickable. */
2364
+ .collapsible-header-static { cursor: default; }
2365
+ .collapsible-header-static:hover { background: none; }
2366
+
2207
2367
 
2208
2368
  .collapsible-toggle {
2209
2369
  font-size: 8px;
@@ -2292,7 +2452,7 @@ html, body, #mbeditor-root {
2292
2452
 
2293
2453
  /* Open-editors panel: height is controlled by the --open-editors-height CSS variable
2294
2454
  set inline from React state (default 140px ≈ 5-6 items), drag-resizable via the
2295
- .open-editors-resize-handle strip below. */
2455
+ gutter below it. */
2296
2456
  .ide-sidebar-fixed .collapsible-content {
2297
2457
  max-height: var(--open-editors-height, 140px);
2298
2458
  overflow-y: auto;
@@ -2302,17 +2462,6 @@ html, body, #mbeditor-root {
2302
2462
  .ide-sidebar-fixed .collapsible-content::-webkit-scrollbar-track { background: transparent; }
2303
2463
  .ide-sidebar-fixed .collapsible-content::-webkit-scrollbar-thumb { background: var(--ide-scrollbar); border-radius: 3px; }
2304
2464
 
2305
- .open-editors-resize-handle {
2306
- height: 4px;
2307
- flex-shrink: 0;
2308
- cursor: row-resize;
2309
- background: transparent;
2310
- transition: background 0.15s;
2311
- }
2312
- .open-editors-resize-handle:hover {
2313
- background: var(--ide-border);
2314
- }
2315
-
2316
2465
  .open-editors-group-header {
2317
2466
  font-size: 10px;
2318
2467
  padding-left: 8px;
@@ -2329,12 +2478,45 @@ html, body, #mbeditor-root {
2329
2478
  transition: opacity 0.12s ease;
2330
2479
  }
2331
2480
 
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;
2489
+ color: var(--ide-text-muted);
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
+
2332
2514
  .ide-icon-btn {
2333
2515
  min-width: 24px;
2334
2516
  height: 24px;
2335
2517
  background: transparent;
2336
2518
  border: none;
2337
- border-radius: 4px;
2519
+ border-radius: 5px;
2338
2520
  color: var(--ide-text-muted);
2339
2521
  display: inline-flex;
2340
2522
  align-items: center;
@@ -2358,8 +2540,10 @@ html, body, #mbeditor-root {
2358
2540
  display: flex;
2359
2541
  justify-content: flex-start;
2360
2542
  align-items: center;
2361
- padding: 4px 8px;
2362
- background: var(--ide-panel-bg);
2543
+ padding: 0 12px;
2544
+ height: 22px;
2545
+ min-height: 22px;
2546
+ background: var(--ide-bg);
2363
2547
  border-bottom: 1px solid var(--ide-border);
2364
2548
  gap: 4px;
2365
2549
  overflow: hidden;
@@ -2373,14 +2557,18 @@ html, body, #mbeditor-root {
2373
2557
  white-space: nowrap;
2374
2558
  text-overflow: ellipsis;
2375
2559
  text-align: left;
2376
- font-size: 11px;
2560
+ font-size: 13px;
2377
2561
  color: var(--ide-text-muted);
2378
2562
  padding: 0 4px;
2379
2563
  cursor: default;
2380
2564
  user-select: text;
2381
- font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
2382
2565
  }
2383
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
+
2384
2572
  .ide-editor-toolbar .ide-icon-btn {
2385
2573
  flex-shrink: 1;
2386
2574
  min-width: 24px;
@@ -2408,10 +2596,12 @@ html, body, #mbeditor-root {
2408
2596
  }
2409
2597
 
2410
2598
  .ide-methods-dropdown {
2411
- background: #252526;
2412
- border: 1px solid #454545;
2413
- border-radius: 4px;
2414
- 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);
2415
2605
  min-width: 200px;
2416
2606
  max-width: 340px;
2417
2607
  max-height: 320px;
@@ -2440,13 +2630,8 @@ html, body, #mbeditor-root {
2440
2630
 
2441
2631
  .ide-methods-dropdown-item:hover,
2442
2632
  .ide-methods-dropdown-item:focus {
2443
- background: #094771;
2444
- color: #fff;
2445
- }
2446
-
2447
- .ide-methods-dropdown-item:focus {
2448
- outline: 1px solid #75beff;
2449
- outline-offset: -1px;
2633
+ background: var(--ide-active-bg);
2634
+ color: var(--ide-text);
2450
2635
  }
2451
2636
 
2452
2637
  .ide-methods-dropdown-line {
@@ -2507,6 +2692,12 @@ html, body, #mbeditor-root {
2507
2692
  .monaco-editor .line-numbers.mbeditor-gitline-deleted.mbeditor-gitline-added,
2508
2693
  .monaco-editor .line-numbers.mbeditor-gitline-deleted.mbeditor-gitline-modified { color: var(--ide-danger); }
2509
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
+
2510
2701
  .ide-methods-dropdown-message {
2511
2702
  padding: 8px 12px;
2512
2703
  color: #858585;
@@ -2595,6 +2786,26 @@ html, body, #mbeditor-root {
2595
2786
  margin: 4px 0;
2596
2787
  }
2597
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
+
2598
2809
  .collapsible-content {
2599
2810
  animation: collapsible-open 0.15s ease;
2600
2811
  }
@@ -2610,42 +2821,59 @@ html, body, #mbeditor-root {
2610
2821
  }
2611
2822
  }
2612
2823
 
2613
- /* ── 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. */
2614
2829
  .quick-open-overlay {
2615
2830
  position: fixed;
2616
2831
  inset: 0;
2617
- background: rgba(0,0,0,0.6);
2832
+ background: rgba(0,0,0,0.35);
2618
2833
  display: flex;
2619
2834
  align-items: flex-start;
2620
2835
  justify-content: center;
2621
- padding-top: 12vh;
2836
+ padding-top: 6px;
2622
2837
  z-index: 9999;
2623
2838
  }
2624
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. */
2625
2842
  .quick-open-box {
2626
- background: var(--ide-panel-bg);
2627
- 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);
2628
2847
  border-radius: 8px;
2629
- /* vw/vh caps keep the larger dialog inside a phone-sized viewport */
2630
- width: min(760px, 94vw);
2631
- max-height: min(620px, 72vh);
2848
+ width: min(600px, 94vw);
2849
+ max-height: min(440px, 72vh);
2632
2850
  display: flex;
2633
2851
  flex-direction: column;
2634
- box-shadow: 0 16px 48px rgba(0,0,0,0.7);
2852
+ box-shadow: 0 8px 24px rgba(0,0,0,0.45);
2635
2853
  overflow: hidden;
2636
2854
  }
2637
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
+
2638
2865
  .quick-open-input-wrap {
2639
2866
  position: relative;
2867
+ padding: 6px;
2868
+ flex-shrink: 0;
2640
2869
  }
2641
2870
 
2642
2871
  .quick-open-input {
2643
2872
  background: transparent;
2644
2873
  border: none;
2645
- border-bottom: 1px solid var(--ide-border);
2646
2874
  color: var(--ide-text);
2647
- padding: 14px 40px 14px 18px;
2648
- font-size: 16px;
2875
+ padding: 4px 30px 4px 8px;
2876
+ font-size: 13px;
2649
2877
  outline: none;
2650
2878
  width: 100%;
2651
2879
  box-sizing: border-box;
@@ -2654,13 +2882,13 @@ html, body, #mbeditor-root {
2654
2882
 
2655
2883
  .quick-open-clear-btn {
2656
2884
  position: absolute;
2657
- right: 8px;
2885
+ right: 10px;
2658
2886
  top: 50%;
2659
2887
  transform: translateY(-50%);
2660
- width: 26px;
2661
- height: 26px;
2888
+ width: 22px;
2889
+ height: 22px;
2662
2890
  border: none;
2663
- border-radius: 4px;
2891
+ border-radius: 5px;
2664
2892
  background: transparent;
2665
2893
  color: var(--ide-text-muted);
2666
2894
  cursor: pointer;
@@ -2671,53 +2899,64 @@ html, body, #mbeditor-root {
2671
2899
 
2672
2900
  .quick-open-clear-btn:hover {
2673
2901
  background: var(--ide-hover-bg);
2674
- color: var(--ide-text-dim);
2902
+ color: var(--ide-text);
2675
2903
  }
2676
2904
 
2677
2905
  .quick-open-results {
2678
2906
  overflow-y: auto;
2679
2907
  flex: 1;
2908
+ padding-bottom: 4px;
2680
2909
  }
2681
2910
 
2682
2911
  .quick-open-result {
2683
- padding: 8px 18px;
2912
+ padding: 0 10px;
2913
+ height: 22px;
2684
2914
  cursor: pointer;
2685
2915
  display: flex;
2686
2916
  align-items: center;
2687
- gap: 10px;
2688
- font-size: 14px;
2689
- transition: background 0.1s;
2917
+ gap: 8px;
2918
+ font-size: 13px;
2690
2919
  }
2691
2920
 
2692
2921
  .quick-open-result-icon {
2693
2922
  flex-shrink: 0;
2694
- width: 18px;
2923
+ width: 16px;
2924
+ font-size: 12px;
2695
2925
  text-align: center;
2696
2926
  color: #78b4e0;
2697
2927
  }
2698
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. */
2699
2931
  .quick-open-result-body {
2700
2932
  min-width: 0;
2701
2933
  flex: 1;
2934
+ display: flex;
2935
+ align-items: baseline;
2936
+ gap: 8px;
2937
+ overflow: hidden;
2702
2938
  }
2703
2939
 
2704
- .quick-open-result:hover,
2705
- .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); }
2706
2942
 
2707
- .quick-open-result-name { color: var(--ide-text); }
2943
+ .quick-open-result-name { color: var(--ide-text); flex-shrink: 0; white-space: nowrap; }
2708
2944
  .quick-open-result-path { color: var(--ide-text-muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2709
2945
 
2710
2946
  .quick-open-section { margin-bottom: 4px; }
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; }
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; }
2712
2951
 
2713
2952
  .quick-open-star-btn {
2714
2953
  flex-shrink: 0;
2715
2954
  background: none;
2716
2955
  border: none;
2717
2956
  cursor: pointer;
2718
- padding: 2px 6px;
2957
+ padding: 0 4px;
2719
2958
  color: var(--ide-text-muted);
2720
- font-size: 14px;
2959
+ font-size: 12px;
2721
2960
  line-height: 1;
2722
2961
  opacity: 0;
2723
2962
  transition: opacity 0.1s, color 0.1s;
@@ -2828,48 +3067,177 @@ html, body, #mbeditor-root {
2828
3067
  .ide-draft-restore-btn:hover { background: var(--ide-hover-bg); }
2829
3068
  .ide-draft-restore-btn-primary {
2830
3069
  background: var(--ide-accent);
2831
- color: var(--ide-accent-text);
3070
+ color: var(--ide-on-accent);
2832
3071
  border-color: var(--ide-accent);
2833
3072
  }
2834
3073
  .ide-draft-restore-btn-primary:hover { opacity: 0.9; }
2835
3074
 
2836
3075
  /* ── Settings panel ─────────────────────────────────────────── */
2837
- .ide-settings-panel { display: flex; flex-direction: column; height: 100%; overflow-y: auto; }
2838
- .ide-settings-body { padding: 8px 12px; display: grid; grid-template-columns: 1fr 1fr; gap: 4px 10px; align-items: start; }
2839
- /* Full-width rows: label sits inline next to the control */
2840
- .ide-settings-row { display: flex; flex-direction: row; align-items: center; gap: 8px; grid-column: 1/-1; }
2841
- /* Half-width rows: label stacked above control, fits in one grid column */
2842
- .ide-settings-row-half { display: flex; flex-direction: column; gap: 2px; grid-column: auto; align-items: flex-start; }
2843
- /* Full-width rows with stacked label — for wide text inputs like font family */
2844
- .ide-settings-row-full { display: flex; flex-direction: column; gap: 2px; grid-column: 1/-1; align-items: flex-start; }
2845
- .ide-settings-row-full .ide-settings-input-wide { width: 100%; flex: none; box-sizing: border-box; }
2846
- /* Checkbox rows: full-width, label on left, checkbox on right */
2847
- .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; }
2848
- .ide-settings-label { font-size: 11px; color: var(--ide-text-muted); white-space: nowrap; }
2849
- /* Inputs in full-width rows stay narrow */
2850
- .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; }
2851
- .ide-settings-input-wide { font-family: monospace; flex: 1; width: auto; }
2852
- /* Selects in half-width rows use Pico styling (inherit theme vars); just cap size */
2853
- .ide-settings-row-half select { width: 100%; }
2854
- .ide-settings-checkbox { width: auto; cursor: pointer; flex-shrink: 0; }
2855
- .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; }
2856
- .ide-settings-reset-btn:hover { background: var(--ide-border-input); color: var(--ide-text-dim); }
2857
- .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; }
2858
- .ide-settings-section-header:first-child { margin-top: 0; }
2859
- .ide-settings-row-link { grid-column: 1/-1; flex-direction: row; align-items: center; justify-content: space-between; }
2860
- .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; }
2861
- .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
+ }
2862
3088
 
2863
- /* Settings tab (pane view) */
2864
- .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);
2865
3098
  display: flex;
2866
3099
  flex-direction: column;
2867
- 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;
2868
3162
  overflow-y: auto;
2869
- background: var(--bg-panel, var(--ide-bg));
2870
- color: var(--text-main, var(--ide-text));
3163
+ display: flex;
3164
+ flex-direction: column;
3165
+ justify-content: flex-start; /* a shared nav rule sets space-between */
2871
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); }
2872
3184
 
3185
+ .ide-settings-body {
3186
+ flex: 1;
3187
+ min-width: 0;
3188
+ overflow-y: auto;
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);
3199
+ }
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); }
2873
3241
 
2874
3242
  /* ══ PICO CSS COMPACT OVERRIDES ═════════════════════════════════════════════
2875
3243
  * Pico's classless variant inflates native elements with generous padding.
@@ -2974,12 +3342,12 @@ textarea {
2974
3342
  .ide-statusbar button,
2975
3343
  .statusbar-btn {
2976
3344
  --pico-form-element-spacing-vertical: 0;
2977
- --pico-form-element-spacing-horizontal: 8px;
2978
- height: 22px;
2979
- padding: 0 8px;
2980
- font-size: 11px;
3345
+ --pico-form-element-spacing-horizontal: 6px;
3346
+ height: 20px;
3347
+ padding: 0 6px;
3348
+ font-size: 12px;
2981
3349
  line-height: 1;
2982
- border-radius: 2px;
3350
+ border-radius: 5px;
2983
3351
  border: none;
2984
3352
  background: transparent;
2985
3353
  color: var(--ide-accent-text);
@@ -3076,7 +3444,19 @@ button:not(.pico-btn) { margin-bottom: 0; }
3076
3444
  border-radius: 0;
3077
3445
  }
3078
3446
  /* Re-apply border-radius for buttons that intentionally need it */
3079
- .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
+ }
3080
3460
  .ide-shell .project-action-btn,
3081
3461
  .ide-shell .git-header-btn,
3082
3462
  .ide-shell .git-section-action-btn,
@@ -3192,15 +3572,6 @@ button:not(.pico-btn) { margin-bottom: 0; }
3192
3572
  background: var(--ide-panel-bg, #1e1e1e);
3193
3573
  border-top: 1px solid var(--ide-border, #333);
3194
3574
  }
3195
- .ide-log-resize {
3196
- flex: 0 0 auto;
3197
- height: 6px;
3198
- cursor: ns-resize;
3199
- background: transparent;
3200
- }
3201
- .ide-log-resize:hover {
3202
- background: var(--ide-accent, #569cd6);
3203
- }
3204
3575
  .ide-log-header {
3205
3576
  display: flex;
3206
3577
  align-items: center;
@@ -3268,25 +3639,35 @@ button:not(.pico-btn) { margin-bottom: 0; }
3268
3639
  background: var(--ide-panel-bg, #1e1e1e);
3269
3640
  border-top: 1px solid var(--ide-border, #333);
3270
3641
  }
3271
- .ide-problems-resize {
3272
- flex: 0 0 auto;
3273
- height: 6px;
3274
- cursor: ns-resize;
3275
- background: transparent;
3276
- }
3277
- .ide-problems-resize:hover { background: var(--ide-accent, #569cd6); }
3278
3642
  .ide-problems-header {
3279
3643
  display: flex;
3280
3644
  align-items: center;
3281
3645
  gap: 8px;
3282
- padding: 4px 10px;
3646
+ height: 35px;
3647
+ padding: 0 10px;
3283
3648
  border-bottom: 1px solid var(--ide-border, #333);
3284
3649
  font-size: 12px;
3285
- color: var(--ide-fg-muted, #ccc);
3650
+ color: var(--ide-text, #ccc);
3651
+ }
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;
3286
3668
  }
3287
- .ide-problems-title { font-weight: 600; }
3288
- /* Severity toggles. Pushed to the right of the summary; the text filter that
3289
- follows keeps its own left margin off `auto` so the two sit together. */
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. */
3290
3671
  .ide-problems-severity-filter {
3291
3672
  margin-left: auto;
3292
3673
  display: flex;
@@ -3297,50 +3678,42 @@ button:not(.pico-btn) { margin-bottom: 0; }
3297
3678
  align-items: center;
3298
3679
  gap: 4px;
3299
3680
  background: transparent;
3300
- border: 1px solid transparent;
3681
+ border: none;
3301
3682
  border-radius: 3px;
3302
3683
  padding: 1px 6px;
3303
3684
  font-size: 11px;
3304
3685
  font-variant-numeric: tabular-nums;
3305
3686
  cursor: pointer;
3306
- /* Off is legible but clearly recessive — a disabled-looking chip reads as
3307
- "broken" rather than "toggled off". */
3308
3687
  color: var(--ide-text-muted, #858585);
3309
- opacity: 0.55;
3310
- }
3311
- .ide-problems-sev-chip:hover { opacity: 0.85; }
3312
- .ide-problems-sev-chip.is-on {
3313
- opacity: 1;
3314
- border-color: var(--ide-border, #333);
3315
- background: var(--ide-input-bg, #2d2d2d);
3316
3688
  }
3317
- .ide-problems-sev-chip.is-on.ide-problems-sev-error { color: var(--ide-error, #f14c4c); }
3318
- .ide-problems-sev-chip.is-on.ide-problems-sev-warning { color: var(--ide-warning, #cca700); }
3319
- .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); }
3320
3691
 
3321
3692
  .ide-problems-filter {
3322
3693
  margin-left: 8px;
3323
3694
  background: var(--ide-input-bg, #2d2d2d);
3324
- color: var(--ide-fg, #eee);
3325
- border: 1px solid var(--ide-border, #333);
3326
- border-radius: 3px;
3327
- 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;
3328
3700
  font-size: 12px;
3329
- width: 180px;
3701
+ width: 140px;
3330
3702
  }
3331
3703
  .ide-problems-btn {
3332
3704
  background: transparent;
3333
3705
  border: none;
3334
- color: var(--ide-fg-muted, #ccc);
3706
+ color: var(--ide-text-muted, #ccc);
3335
3707
  cursor: pointer;
3336
3708
  padding: 2px 6px;
3337
3709
  }
3338
- .ide-problems-btn:hover { color: var(--ide-fg, #fff); }
3710
+ .ide-problems-btn:hover { color: var(--ide-text, #fff); }
3339
3711
  .ide-problems-btn:disabled { opacity: 0.4; cursor: default; }
3340
- .ide-problems-btn:disabled:hover { color: var(--ide-fg-muted, #ccc); }
3712
+ .ide-problems-btn:disabled:hover { color: var(--ide-text-muted, #ccc); }
3341
3713
  .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); }
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; }
3344
3717
  .ide-problems-body { flex: 1; overflow: auto; padding: 4px 0; font-size: 12px; }
3345
3718
  .ide-problems-empty {
3346
3719
  padding: 10px 14px;
@@ -3351,9 +3724,18 @@ button:not(.pico-btn) { margin-bottom: 0; }
3351
3724
  display: flex;
3352
3725
  align-items: center;
3353
3726
  gap: 6px;
3354
- padding: 5px 12px 3px;
3355
- color: var(--ide-fg-muted, #ccc);
3356
- 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;
3357
3739
  }
3358
3740
  .ide-problems-file-count {
3359
3741
  background: var(--ide-hover-bg, #2a2a2a);
@@ -3368,22 +3750,23 @@ button:not(.pico-btn) { margin-bottom: 0; }
3368
3750
  align-items: baseline;
3369
3751
  gap: 8px;
3370
3752
  width: 100%;
3371
- padding: 3px 12px 3px 28px;
3753
+ height: 22px;
3754
+ padding: 0 12px 0 28px;
3372
3755
  background: transparent;
3373
3756
  border: none;
3374
- color: var(--ide-fg, #d4d4d4);
3757
+ color: var(--ide-text, #d4d4d4);
3375
3758
  font-size: 12px;
3376
3759
  font-family: inherit;
3377
3760
  text-align: left;
3378
3761
  cursor: pointer;
3379
3762
  }
3380
- .ide-problems-item:hover,
3381
- .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); }
3382
3765
  .ide-problems-icon { flex-shrink: 0; }
3383
3766
  .ide-problems-item-error .ide-problems-icon { color: var(--ide-danger); }
3384
3767
  .ide-problems-item-warning .ide-problems-icon { color: var(--ide-warning); }
3385
3768
  .ide-problems-item-info .ide-problems-icon { color: var(--ide-accent, #3794ff); }
3386
- .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); }
3387
3770
  /* The offending source line, dimmed so it reads as context rather than as part
3388
3771
  of the message. It is the one part of the row allowed to shrink, so a long
3389
3772
  line gives way to the message and location instead of pushing them out. */
@@ -3393,7 +3776,6 @@ button:not(.pico-btn) { margin-bottom: 0; }
3393
3776
  text-overflow: ellipsis;
3394
3777
  white-space: nowrap;
3395
3778
  color: var(--ide-text-muted, #858585);
3396
- opacity: 0.8;
3397
3779
  font-family: var(--ide-mono, monospace);
3398
3780
  font-size: 11px;
3399
3781
  }
@@ -3422,12 +3804,24 @@ button:not(.pico-btn) { margin-bottom: 0; }
3422
3804
  margin-left: auto;
3423
3805
  color: var(--ide-text-muted, #858585);
3424
3806
  font-size: 11px;
3807
+ font-variant-numeric: tabular-nums;
3425
3808
  }
3426
3809
 
3427
3810
  /* ── Model graph ──────────────────────────────────────────────────────────
3428
3811
  An SVG ER diagram of the app's ActiveRecord models. The panel is narrow, so
3429
3812
  the SVG scrolls in both directions rather than being scaled down to
3430
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
+
3431
3825
  .ide-model-graph { display: flex; flex-direction: column; height: 100%; min-height: 0; position: relative; }
3432
3826
  .ide-model-graph-empty {
3433
3827
  padding: 16px;
@@ -3577,8 +3971,7 @@ button:not(.pico-btn) { margin-bottom: 0; }
3577
3971
  The gap is the icon-to-its-own-number spacing; the warning icon opens the
3578
3972
  second pair, so it gets the wider separator. */
3579
3973
  .statusbar-problems { gap: 4px; }
3580
- .statusbar-problems-error-icon { color: var(--ide-danger); }
3581
- .statusbar-problems-warning-icon { color: var(--ide-warning); margin-left: 8px; }
3974
+ .statusbar-problems-warning-icon { margin-left: 8px; }
3582
3975
  .statusbar-problems-count { font-variant-numeric: tabular-nums; }
3583
3976
 
3584
3977
  /* Search decorations off across engines — Chrome/Safari draw their own
@@ -3717,6 +4110,7 @@ input.ide-model-graph-search[type="search"] {
3717
4110
  /* Pair-programming diagnostics. The chip only appears when a condition pairing
3718
4111
  needs has actually failed — an empty room is silence, not a warning. */
3719
4112
  .mbeditor-collab-trouble { color: var(--ide-warning, #cca700); }
4113
+ .mbeditor-conflict-chip { color: var(--ide-error, #f48771); }
3720
4114
  .mbeditor-modal-backdrop {
3721
4115
  position: fixed; inset: 0; z-index: 5000;
3722
4116
  background: rgba(0, 0, 0, 0.45);