mbeditor 0.13.1 → 0.14.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +132 -0
  3. data/app/assets/javascripts/mbeditor/application.js +5 -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/CombinedFileView.js +214 -0
  11. data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
  12. data/app/assets/javascripts/mbeditor/components/EditorPanel.js +442 -248
  13. data/app/assets/javascripts/mbeditor/components/FileHistoryPanel.js +6 -9
  14. data/app/assets/javascripts/mbeditor/components/FileTree.js +26 -12
  15. data/app/assets/javascripts/mbeditor/components/GitPanel.js +3 -0
  16. data/app/assets/javascripts/mbeditor/components/Gutter.js +51 -0
  17. data/app/assets/javascripts/mbeditor/components/ImportConflictModal.js +60 -5
  18. data/app/assets/javascripts/mbeditor/components/LogPanel.js +3 -44
  19. data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +1241 -1256
  20. data/app/assets/javascripts/mbeditor/components/ModelGraph.js +94 -37
  21. data/app/assets/javascripts/mbeditor/components/PastePlaceModal.js +329 -0
  22. data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +47 -65
  23. data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +26 -8
  24. data/app/assets/javascripts/mbeditor/components/SettingsModal.js +342 -0
  25. data/app/assets/javascripts/mbeditor/components/ShortcutHelp.js +18 -1
  26. data/app/assets/javascripts/mbeditor/components/TabBar.js +67 -98
  27. data/app/assets/javascripts/mbeditor/editor_plugins.js +705 -323
  28. data/app/assets/javascripts/mbeditor/file_import.js +13 -15
  29. data/app/assets/javascripts/mbeditor/file_service.js +48 -39
  30. data/app/assets/javascripts/mbeditor/git_service.js +15 -1
  31. data/app/assets/javascripts/mbeditor/history_service.js +5 -13
  32. data/app/assets/javascripts/mbeditor/search_service.js +9 -0
  33. data/app/assets/javascripts/mbeditor/tab_manager.js +127 -49
  34. data/app/assets/javascripts/mbeditor/websocket_service.js +13 -5
  35. data/app/assets/stylesheets/mbeditor/application.css +6 -1
  36. data/app/assets/stylesheets/mbeditor/editor.css +672 -249
  37. data/app/assets/stylesheets/mbeditor/glass.css +174 -0
  38. data/app/assets/stylesheets/mbeditor/themes.css +93 -33
  39. data/app/channels/mbeditor/collaboration_channel.rb +17 -4
  40. data/app/controllers/mbeditor/application_controller.rb +26 -3
  41. data/app/controllers/mbeditor/editors_controller.rb +124 -439
  42. data/app/services/mbeditor/archive_service.rb +137 -0
  43. data/app/services/mbeditor/collaboration_doc_store.rb +143 -14
  44. data/app/services/mbeditor/editor_state_service.rb +14 -51
  45. data/app/services/mbeditor/file_history_service.rb +222 -0
  46. data/app/services/mbeditor/git_info_service.rb +6 -0
  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/ruby_lsp_result_translator.rb +226 -0
  52. data/app/services/mbeditor/ruby_method_ranges_service.rb +120 -0
  53. data/app/services/mbeditor/search_replace_service.rb +8 -0
  54. data/app/views/layouts/mbeditor/application.html.erb +1 -1
  55. data/lib/mbeditor/audit_log.rb +203 -0
  56. data/lib/mbeditor/configuration.rb +6 -1
  57. data/lib/mbeditor/rack/pending_migration_bypass.rb +31 -10
  58. data/lib/mbeditor/route_map.rb +5 -0
  59. data/lib/mbeditor/ruby_lsp_client.rb +82 -14
  60. data/lib/mbeditor/version.rb +1 -1
  61. data/lib/mbeditor.rb +1 -0
  62. data/public/monaco-editor/VERSIONS +1 -1
  63. data/public/monaco-editor/codicon.ttf +0 -0
  64. data/public/monaco-editor/css.worker.js +41 -37
  65. data/public/monaco-editor/editor.worker.js +19 -15
  66. data/public/monaco-editor/html.worker.js +53 -49
  67. data/public/monaco-editor/json.worker.js +36 -32
  68. data/public/monaco-editor/monaco.css +1 -1
  69. data/public/monaco-editor/monaco.js +388 -301
  70. data/public/monaco-editor/ts.worker.js +226 -222
  71. data/vendor/assets/javascripts/axios.min.js +3 -3
  72. metadata +15 -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
@@ -117,27 +128,31 @@ html, body, #mbeditor-root {
117
128
  }
118
129
 
119
130
  .ide-titlebar {
131
+ /* .statusbar-btn paints from --ide-accent-text, which is "text on the accent
132
+ fill" in the legacy themes. The title bar is panel-coloured in every theme,
133
+ so its buttons read the ordinary text colour instead. */
134
+ --ide-accent-text: var(--ide-text);
120
135
  display: flex;
121
136
  align-items: center;
122
137
  background: var(--ide-panel-bg);
123
138
  border-bottom: 1px solid var(--ide-border);
124
- height: 32px;
125
- padding: 0 12px;
139
+ height: 35px;
140
+ padding: 0 8px;
126
141
  flex-shrink: 0;
127
142
  user-select: none;
128
143
  }
129
144
 
130
145
  .ide-titlebar-icon {
131
- color: var(--ide-accent-fg);
146
+ color: var(--ide-text-muted);
132
147
  margin-right: 8px;
133
- font-size: 14px;
148
+ font-size: 13px;
134
149
  }
135
150
 
136
151
  .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
152
+ font-size: 12px;
153
+ color: var(--ide-text-muted);
154
+ font-weight: 400;
155
+ /* Never wrap: the titlebar is a fixed 35px row, so a second line pushed the
141
156
  toolbar buttons out of it. */
142
157
  white-space: nowrap;
143
158
  }
@@ -157,30 +172,30 @@ html, body, #mbeditor-root {
157
172
  flex: 1 1 auto;
158
173
  justify-content: center;
159
174
  min-width: 0;
160
- margin: 0 12px;
175
+ margin: 0 24px;
161
176
  }
162
177
 
163
178
  .ide-titlebar-search {
164
179
  display: flex;
165
180
  align-items: center;
166
181
  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;
182
+ /* The command centre. Capped so it stays a centred pill rather than a
183
+ stretched field, floored so the placeholder survives a narrow window. */
184
+ width: 100%;
185
+ max-width: 720px;
186
+ min-width: min(180px, 100%);
187
+ height: 24px;
188
+ padding: 0 10px;
189
+ background: color-mix(in srgb, var(--ide-text) 6%, transparent);
190
+ border: 1px solid var(--ide-border);
191
+ border-radius: 6px;
192
+ color: var(--ide-text-muted);
178
193
  font-size: 12px;
179
194
  cursor: pointer;
180
195
  }
181
- .ide-titlebar-search:hover { background: rgba(255, 255, 255, 0.10); }
196
+ .ide-titlebar-search:hover { background: color-mix(in srgb, var(--ide-text) 11%, transparent); }
182
197
  .ide-titlebar-search-text {
183
- opacity: 0.8;
198
+ opacity: 1;
184
199
  overflow: hidden;
185
200
  text-overflow: ellipsis;
186
201
  white-space: nowrap;
@@ -205,8 +220,8 @@ html, body, #mbeditor-root {
205
220
 
206
221
  /* ── Activity Bar (VSCode-style, always visible) ───────── */
207
222
  .ide-activity-bar {
208
- width: 40px;
209
- min-width: 40px;
223
+ width: 48px;
224
+ min-width: 48px;
210
225
  flex-shrink: 0;
211
226
  background: var(--ide-panel-bg);
212
227
  border-right: 1px solid var(--ide-border);
@@ -240,69 +255,105 @@ html, body, #mbeditor-root {
240
255
  than a bare class and was silently winning. */
241
256
  .ide-activity-bar .ide-activity-btn {
242
257
  width: 100%;
243
- height: 40px;
258
+ height: 48px;
244
259
  background: transparent;
245
260
  border: none;
246
261
  /* Always reserve the indicator's width so the glyph does not shift sideways
247
262
  by a pixel the moment a panel is opened. */
248
263
  border-left: 2px solid transparent;
249
264
  border-radius: 0;
250
- color: var(--ide-text-muted);
265
+ /* One glyph colour throughout; opacity carries the state, which is what
266
+ stops the icons reading as three different palettes at once. */
267
+ color: var(--ide-text);
268
+ opacity: 0.6;
251
269
  display: flex;
252
270
  align-items: center;
253
271
  justify-content: center;
254
272
  cursor: pointer;
255
- font-size: 17px;
256
- transition: color 0.12s ease;
273
+ font-size: 16px;
274
+ transition: opacity 0.1s ease, border-color 0.1s ease;
257
275
  }
258
276
 
259
277
  /* The glyph carries the state, not a filled box behind it. */
260
278
  .ide-activity-bar .ide-activity-btn:hover {
261
279
  background: transparent;
262
- color: var(--ide-text);
280
+ opacity: 1;
263
281
  }
264
282
 
265
283
  .ide-activity-bar .ide-activity-btn.active {
284
+ opacity: 1;
285
+ background: transparent;
266
286
  color: var(--ide-accent-fg);
267
- border-left-color: var(--ide-accent-fg);
287
+ border-left-color: var(--ide-accent);
268
288
  }
269
289
 
270
290
  .sidebar-panel-title {
271
291
  font-size: 11px;
272
292
  font-weight: 700;
273
293
  text-transform: uppercase;
274
- letter-spacing: 0.08em;
275
- color: var(--ide-fg-muted, var(--ide-text-muted, #888));
276
- padding: 10px 12px 6px;
294
+ letter-spacing: 0.06em;
295
+ color: var(--ide-text);
296
+ padding: 9px 20px 7px;
277
297
  flex-shrink: 0;
278
- border-bottom: 1px solid var(--ide-border);
279
298
  }
280
299
 
281
300
 
282
- .panel-divider {
283
- flex-shrink: 0;
284
- cursor: col-resize;
301
+ /* ── The gutter ──────────────────────────────────────────────
302
+ One draggable strip, always in the gap between two cards, never inside
303
+ either of them. 6px in its thin dimension and transparent: wider reads as a
304
+ gap in the layout rather than a handle.
305
+
306
+ The three dots are a ::before on the gutter itself. A flat strip gives no
307
+ sign it can be dragged, and the cursor only says so once you are already on
308
+ it. One tiled radial gradient rather than three elements: the gutter has no
309
+ children and adding some would put a hit-testable node inside the drag
310
+ target. */
311
+ .ide-gutter {
312
+ flex: 0 0 auto;
313
+ position: relative;
314
+ background: transparent;
285
315
  transition: background 0.12s ease;
286
316
  }
287
317
 
288
- .sidebar-divider {
289
- width: 6px;
290
- background: var(--ide-panel-bg-alt);
291
- border-right: 1px solid var(--ide-border);
292
- }
318
+ .ide-gutter-v { width: 6px; cursor: col-resize; align-self: stretch; }
319
+ .ide-gutter-h { height: 6px; cursor: row-resize; align-self: stretch; width: 100%; }
293
320
 
321
+ .ide-gutter::before {
322
+ content: '';
323
+ position: absolute;
324
+ left: 50%;
325
+ top: 50%;
326
+ transform: translate(-50%, -50%);
327
+ opacity: 0.55;
328
+ transition: opacity 0.12s ease;
329
+ pointer-events: none;
330
+ }
294
331
 
295
- .pane-divider {
296
- width: 4px;
297
- background: var(--ide-panel-bg-alt);
298
- z-index: 10;
332
+ /* Dots run top-to-bottom on a vertical gutter, left-to-right on a horizontal
333
+ one — the same tile, rotated. */
334
+ .ide-gutter-v::before {
335
+ width: 3px;
336
+ height: 15px;
337
+ background: radial-gradient(circle 1.5px at 1.5px 1.5px, var(--ide-text-muted) 99%, transparent) 0 0 / 3px 6px repeat-y;
299
338
  }
300
339
 
301
- .panel-divider:hover,
302
- .panel-divider.active {
303
- background: var(--ide-accent);
340
+ .ide-gutter-h::before {
341
+ width: 15px;
342
+ height: 3px;
343
+ background: radial-gradient(circle 1.5px at 1.5px 1.5px, var(--ide-text-muted) 99%, transparent) 0 0 / 6px 3px repeat-x;
304
344
  }
305
345
 
346
+ .ide-gutter:hover { background: var(--ide-hover-bg); }
347
+ .ide-gutter:hover::before,
348
+ .ide-gutter.active::before { opacity: 1; }
349
+
350
+ /* The split-pane gutter sits over the pane drop overlays. */
351
+ .ide-gutter-pane { z-index: 10; }
352
+
353
+ /* Modern: the seam beside the git panel card is drawn on the gutter, so the
354
+ panel itself carries no left border and there is only ever one line. */
355
+ .ide-gutter-gitpanel { border-left: 1px solid var(--ide-border); }
356
+
306
357
  .ide-sidebar-header {
307
358
  padding: 6px 12px;
308
359
  font-size: 11px;
@@ -359,8 +410,8 @@ html, body, #mbeditor-root {
359
410
 
360
411
  .tree-item:hover { background: var(--ide-hover-bg); }
361
412
  .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); }
413
+ .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); }
414
+ .tree-item.active.selected { box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--ide-accent) 55%, transparent); }
364
415
  .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
416
  /* External file drop (import) — green, to read as "add" rather than "move" */
366
417
  .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 +514,7 @@ html, body, #mbeditor-root {
463
514
  .tab-bar {
464
515
  display: flex;
465
516
  align-items: center;
517
+ padding-left: 4px;
466
518
  background: var(--ide-panel-bg);
467
519
  border-bottom: 1px solid var(--ide-border);
468
520
  height: 35px;
@@ -504,15 +556,15 @@ html, body, #mbeditor-root {
504
556
  .tab-item {
505
557
  display: flex;
506
558
  align-items: center;
507
- padding: 0 12px;
508
- height: 35px;
509
- min-width: 100px;
510
- max-width: 200px;
559
+ padding: 0 8px 0 10px;
560
+ height: 27px;
561
+ margin: 4px 0 4px 4px;
562
+ max-width: 220px;
511
563
  cursor: pointer;
512
- border-right: 1px solid var(--ide-border);
513
- background: var(--ide-input-bg);
564
+ border-radius: 6px;
565
+ background: transparent;
514
566
  color: var(--ide-text-muted);
515
- font-size: 12px;
567
+ font-size: 13px;
516
568
  transition: background 0.1s, color 0.1s;
517
569
  white-space: nowrap;
518
570
  overflow: hidden;
@@ -528,30 +580,22 @@ html, body, #mbeditor-root {
528
580
  opacity: 0.55;
529
581
  }
530
582
 
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;
583
+ .tab-item:hover { background: var(--ide-hover-bg); color: var(--ide-text); }
584
+ /* The active tab is a lifted rounded chip on the tab strip, not a seamless
585
+ join with the editor: the strip keeps its own bottom border. */
586
+ .tab-item.active {
587
+ background: var(--ide-hover-bg);
588
+ color: var(--ide-text);
545
589
  }
590
+ .tab-item.tab-soft .tab-item-name { font-style: italic; }
546
591
 
547
- .tab-item.active.tab-has-warning {
548
- border-top: 2px solid var(--ide-warning) !important;
549
- border-bottom: none !important;
550
- }
592
+ /* Diagnostics colour the label, as VS Code does; a border would break the chip. */
593
+ .tab-has-error .tab-item-name { color: var(--ide-danger); }
594
+ .tab-has-warning .tab-item-name { color: var(--ide-warning); }
551
595
 
552
596
  .tab-item-icon {
553
597
  width: 14px;
554
- margin-right: 8px;
598
+ margin-right: 6px;
555
599
  font-size: 12px;
556
600
  text-align: center;
557
601
  flex-shrink: 0;
@@ -572,8 +616,8 @@ html, body, #mbeditor-root {
572
616
 
573
617
  .tab-close {
574
618
  margin-left: 6px;
575
- width: 16px;
576
- height: 16px;
619
+ width: 20px;
620
+ height: 20px;
577
621
  display: flex;
578
622
  align-items: center;
579
623
  justify-content: center;
@@ -582,8 +626,14 @@ html, body, #mbeditor-root {
582
626
  color: var(--ide-text-muted);
583
627
  flex-shrink: 0;
584
628
  transition: background 0.1s, color 0.1s;
629
+ background: transparent;
630
+ border: none;
631
+ padding: 0;
632
+ cursor: pointer;
633
+ opacity: 0.6;
585
634
  }
586
635
 
636
+ .tab-item:hover .tab-close { opacity: 1; }
587
637
  .tab-close:hover { background: var(--ide-hover-bg); color: var(--ide-text); }
588
638
 
589
639
  .tab-welcome {
@@ -884,20 +934,21 @@ html, body, #mbeditor-root {
884
934
  .ide-statusbar {
885
935
  display: flex;
886
936
  align-items: center;
887
- background: var(--ide-accent);
937
+ background: var(--ide-statusbar-bg);
938
+ border-top: 1px solid var(--ide-border);
888
939
  height: 22px;
889
940
  padding: 0;
890
941
  gap: 0;
891
942
  flex-shrink: 0;
892
- font-size: 11px;
893
- line-height: 1;
943
+ font-size: 12px;
944
+ line-height: 20px; /* glyphs centre in the row; line-height 1 sat them high */
894
945
  color: var(--ide-accent-text);
895
946
  }
896
947
 
897
948
  /* One cell rhythm for every item, button or not. The branch, the message and
898
949
  * the chips each carried their own padding on top of the bar's, so the gaps
899
950
  * between them were all different widths. */
900
- .ide-statusbar > * { padding: 0 8px; }
951
+ .ide-statusbar > * { padding: 0 6px; }
901
952
 
902
953
  .statusbar-branch {
903
954
  display: flex;
@@ -929,6 +980,29 @@ html, body, #mbeditor-root {
929
980
  cursor: default;
930
981
  }
931
982
 
983
+ /* Pending migrations: the whole status bar turns amber, rather than a banner
984
+ * that sat above everything and shoved the editor down the page every time it
985
+ * appeared.
986
+ *
987
+ * The bar's own variables are redefined rather than background and color,
988
+ * because the bar and every child — buttons, separators, the hover mix — are
989
+ * already painted from them. Setting the background alone left unreadable
990
+ * text on amber. */
991
+ body.mbeditor-migration-pending .ide-statusbar {
992
+ --ide-statusbar-bg: var(--ide-statusbar-warn-bg);
993
+ --ide-accent-text: var(--ide-statusbar-warn-text);
994
+ --ide-text-muted: var(--ide-statusbar-warn-text);
995
+ }
996
+
997
+ .statusbar-migration {
998
+ display: flex;
999
+ align-items: center;
1000
+ gap: 6px;
1001
+ font-weight: 600;
1002
+ white-space: nowrap;
1003
+ flex-shrink: 0;
1004
+ }
1005
+
932
1006
  .statusbar-offline {
933
1007
  display: flex;
934
1008
  align-items: center;
@@ -948,19 +1022,32 @@ html, body, #mbeditor-root {
948
1022
  display: flex;
949
1023
  align-items: center;
950
1024
  gap: 4px;
951
- padding: 0 8px;
952
- height: 22px;
1025
+ padding: 0 6px;
1026
+ height: 20px;
1027
+ margin: 0 1px;
953
1028
  flex-shrink: 0;
954
1029
  background: transparent;
955
1030
  border: none;
956
1031
  color: var(--ide-accent-text);
957
1032
  cursor: pointer;
958
- font-size: 11px;
959
- border-radius: 2px;
1033
+ font-size: 12px;
1034
+ border-radius: 5px;
960
1035
  transition: background 0.1s;
961
1036
  white-space: nowrap;
962
1037
  }
963
1038
 
1039
+ /* Cursor position readout. Matches the buttons' metrics so the bar keeps one
1040
+ rhythm, but it is a span with no hover: nothing happens when you click it. */
1041
+ .statusbar-cursor {
1042
+ display: flex;
1043
+ align-items: center;
1044
+ padding: 0 6px;
1045
+ height: 20px;
1046
+ flex-shrink: 0;
1047
+ color: var(--ide-accent-text);
1048
+ font-variant-numeric: tabular-nums;
1049
+ }
1050
+
964
1051
  .statusbar-btn:hover { background: color-mix(in srgb, var(--ide-accent-text) 15%, transparent); }
965
1052
  .statusbar-btn:disabled { opacity: 0.5; cursor: not-allowed; }
966
1053
 
@@ -1018,6 +1105,77 @@ html, body, #mbeditor-root {
1018
1105
  .collab-hovercard-row { color: var(--ide-text-muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
1019
1106
  .collab-hovercard-hint { margin-top: 5px; padding-top: 5px; border-top: 1px solid var(--ide-border); color: var(--ide-text-muted); }
1020
1107
 
1108
+ /* Overlapping avatar stack: fixed width regardless of headcount, which is the
1109
+ point — a chip per peer is what broke past ~4 people. The ring is a
1110
+ box-shadow, not a border, so it doesn't perturb the circle's own layout
1111
+ before the negative margin overlaps it; it paints over the sibling behind it
1112
+ because later avatars are later in DOM order, no z-index needed. Its colour
1113
+ comes from --ide-panel-bg rather than an opaque literal, since glass chrome
1114
+ tints that same token instead of replacing it. */
1115
+ .collab-avatar-stack { display: flex; align-items: center; }
1116
+ .collab-avatar {
1117
+ width: 9px;
1118
+ height: 9px;
1119
+ flex-shrink: 0;
1120
+ border: none;
1121
+ border-radius: 50%;
1122
+ padding: 0;
1123
+ cursor: pointer;
1124
+ box-shadow: 0 0 0 1.5px var(--ide-panel-bg);
1125
+ }
1126
+ .collab-avatar:not(:first-child) { margin-left: -4.5px; }
1127
+ /* Dimmed rather than re-shaped: a peer in another file still reads as "here"
1128
+ at a glance, just fainter, instead of needing a second glyph on a 9px dot. */
1129
+ .collab-avatar-elsewhere { opacity: 0.55; }
1130
+ .collab-avatar-following { outline: 1.5px solid var(--ide-accent-fg); outline-offset: 1px; }
1131
+ .collab-avatar-more {
1132
+ width: auto;
1133
+ height: auto;
1134
+ margin-left: 6px;
1135
+ padding: 0;
1136
+ font-size: 10px;
1137
+ color: var(--ide-text-muted);
1138
+ background: none;
1139
+ border-radius: 0;
1140
+ box-shadow: none;
1141
+ }
1142
+ .collab-avatar-overflow { position: relative; }
1143
+ /* Fixed and anchored from the button's rect, like .collab-hovercard: inside the
1144
+ title bar it was painted under the editor whatever its z-index. */
1145
+ .collab-overflow-popover {
1146
+ position: fixed;
1147
+ z-index: 4000;
1148
+ min-width: 180px;
1149
+ max-width: 260px;
1150
+ max-height: 240px;
1151
+ overflow-y: auto;
1152
+ padding: 4px;
1153
+ background: var(--ide-panel-bg);
1154
+ border: 1px solid var(--ide-border);
1155
+ border-radius: 6px;
1156
+ box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
1157
+ }
1158
+ .collab-overflow-row {
1159
+ display: flex;
1160
+ align-items: center;
1161
+ gap: 6px;
1162
+ width: 100%;
1163
+ padding: 4px 6px;
1164
+ background: transparent;
1165
+ border: none;
1166
+ border-radius: 4px;
1167
+ cursor: pointer;
1168
+ color: var(--ide-text);
1169
+ font-size: 11px;
1170
+ text-align: left;
1171
+ }
1172
+ .collab-overflow-row:hover { background: var(--ide-hover-bg); }
1173
+ .collab-overflow-row.is-following { background: color-mix(in srgb, var(--ide-accent) 15%, transparent); }
1174
+ .collab-overflow-swatch { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
1175
+ .collab-overflow-name { flex-shrink: 0; }
1176
+ .collab-overflow-file { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ide-text-muted); }
1177
+ .collab-overflow-eye { margin-left: auto; color: var(--ide-accent-fg); font-size: 10px; }
1178
+
1021
1179
  .statusbar-msg {
1022
1180
  margin-left: auto;
1023
1181
  color: var(--ide-accent-text);
@@ -1032,7 +1190,7 @@ html, body, #mbeditor-root {
1032
1190
  .statusbar-msg.success { color: var(--ide-success); }
1033
1191
 
1034
1192
  .statusbar-version {
1035
- color: color-mix(in srgb, var(--ide-accent-text) 45%, transparent);
1193
+ color: color-mix(in srgb, var(--ide-accent-text) 80%, transparent);
1036
1194
  font-size: 10px;
1037
1195
  white-space: nowrap;
1038
1196
  cursor: pointer;
@@ -1180,13 +1338,20 @@ html, body, #mbeditor-root {
1180
1338
  background: var(--ide-input-bg);
1181
1339
  color: var(--ide-text);
1182
1340
  border: 1px solid var(--ide-border-input);
1183
- border-radius: 4px;
1341
+ border-radius: 8px;
1184
1342
  font-size: 12px;
1185
1343
  outline: none;
1186
1344
  box-sizing: border-box;
1187
1345
  }
1188
1346
  .search-input::placeholder { color: var(--ide-text-muted); }
1189
- .search-input:focus { border-color: var(--ide-border-input); }
1347
+ .search-input:focus { border-color: var(--ide-accent); }
1348
+
1349
+ /* 8px on every text field, so the search box, the rename box and the import
1350
+ destination box are one shape rather than three. Pico's own 0.25rem applies
1351
+ to bare elements, hence the element selectors rather than a class list. */
1352
+ #mbeditor-root :is(input[type="text"], input[type="search"], input[type="number"], textarea, select) {
1353
+ border-radius: 8px;
1354
+ }
1190
1355
 
1191
1356
  /* ── Results area wrapper (positions the loading overlay) ── */
1192
1357
  .search-results-area {
@@ -1800,7 +1965,7 @@ html, body, #mbeditor-root {
1800
1965
  .changelog-version-badge {
1801
1966
  font-size: 15px;
1802
1967
  font-weight: 700;
1803
- color: var(--ide-accent-text, #4fc1ff);
1968
+ color: var(--ide-accent-fg);
1804
1969
  letter-spacing: -0.01em;
1805
1970
  }
1806
1971
  .changelog-version-date {
@@ -1891,8 +2056,9 @@ html, body, #mbeditor-root {
1891
2056
 
1892
2057
  /* ── Right-side Git Panel ───────────────────────────────── */
1893
2058
  .ide-git-right-panel {
1894
- background: var(--ide-panel-bg-alt);
1895
- border-left: 1px solid var(--ide-border);
2059
+ /* A card like the sidebar: the seam on its left is drawn by the gutter, not
2060
+ by a border here, so the two never double up. */
2061
+ background: var(--ide-panel-bg);
1896
2062
  display: flex;
1897
2063
  flex-direction: column;
1898
2064
  overflow: hidden;
@@ -1901,16 +2067,10 @@ html, body, #mbeditor-root {
1901
2067
  max-width: 600px;
1902
2068
  }
1903
2069
 
1904
- .gitpanel-divider {
1905
- width: 5px;
1906
- background: var(--ide-panel-bg-alt);
1907
- border-left: 1px solid var(--ide-border);
1908
- }
1909
-
1910
2070
  .ide-git-panel {
1911
2071
  width: 100%;
1912
2072
  height: 100%;
1913
- background: var(--ide-panel-bg-alt);
2073
+ background: var(--ide-panel-bg);
1914
2074
  display: flex;
1915
2075
  flex-direction: column;
1916
2076
  overflow: hidden;
@@ -2204,6 +2364,10 @@ html, body, #mbeditor-root {
2204
2364
  background: var(--ide-hover-bg);
2205
2365
  }
2206
2366
 
2367
+ /* Nothing to click, so nothing that looks clickable. */
2368
+ .collapsible-header-static { cursor: default; }
2369
+ .collapsible-header-static:hover { background: none; }
2370
+
2207
2371
 
2208
2372
  .collapsible-toggle {
2209
2373
  font-size: 8px;
@@ -2292,7 +2456,7 @@ html, body, #mbeditor-root {
2292
2456
 
2293
2457
  /* Open-editors panel: height is controlled by the --open-editors-height CSS variable
2294
2458
  set inline from React state (default 140px ≈ 5-6 items), drag-resizable via the
2295
- .open-editors-resize-handle strip below. */
2459
+ gutter below it. */
2296
2460
  .ide-sidebar-fixed .collapsible-content {
2297
2461
  max-height: var(--open-editors-height, 140px);
2298
2462
  overflow-y: auto;
@@ -2302,17 +2466,6 @@ html, body, #mbeditor-root {
2302
2466
  .ide-sidebar-fixed .collapsible-content::-webkit-scrollbar-track { background: transparent; }
2303
2467
  .ide-sidebar-fixed .collapsible-content::-webkit-scrollbar-thumb { background: var(--ide-scrollbar); border-radius: 3px; }
2304
2468
 
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
2469
  .open-editors-group-header {
2317
2470
  font-size: 10px;
2318
2471
  padding-left: 8px;
@@ -2329,12 +2482,45 @@ html, body, #mbeditor-root {
2329
2482
  transition: opacity 0.12s ease;
2330
2483
  }
2331
2484
 
2485
+ /* VS Code-style Open Editors row: 22px, filename dims when the tab isn't the
2486
+ active one, the directory trails it in muted small text, and the
2487
+ move-to-group / close controls stay invisible until the row is hovered. */
2488
+ .open-editors-item { height: 22px; padding: 0 8px; }
2489
+ .open-editors-name { color: var(--ide-text-muted); }
2490
+ .open-editors-item.active .open-editors-name { color: var(--ide-text); }
2491
+ .open-editors-dir {
2492
+ margin-left: 6px;
2493
+ color: var(--ide-text-muted);
2494
+ font-size: 12px;
2495
+ overflow: hidden;
2496
+ text-overflow: ellipsis;
2497
+ white-space: nowrap;
2498
+ min-width: 0;
2499
+ }
2500
+
2501
+ .open-editors-item .tab-split { opacity: 0; }
2502
+ .open-editors-item:hover .tab-split { opacity: 0.6; }
2503
+
2504
+ /* Dot and close button share one cell: the dot shows at rest for a dirty
2505
+ tab, the close button takes over on hover — same slot, same as VS Code's
2506
+ tab UI, so a hover never shifts the row's width. */
2507
+ .open-editors-close-slot {
2508
+ display: grid;
2509
+ width: 20px;
2510
+ height: 16px;
2511
+ }
2512
+ .open-editors-close-slot > * { grid-area: 1 / 1; place-self: center; }
2513
+ .open-editors-dirty-dot { font-size: 5px; color: #e3d286; opacity: 1; }
2514
+ .open-editors-item .tab-close { opacity: 0; }
2515
+ .open-editors-item:hover .open-editors-dirty-dot { opacity: 0; }
2516
+ .open-editors-item:hover .tab-close { opacity: 1; }
2517
+
2332
2518
  .ide-icon-btn {
2333
2519
  min-width: 24px;
2334
2520
  height: 24px;
2335
2521
  background: transparent;
2336
2522
  border: none;
2337
- border-radius: 4px;
2523
+ border-radius: 5px;
2338
2524
  color: var(--ide-text-muted);
2339
2525
  display: inline-flex;
2340
2526
  align-items: center;
@@ -2358,8 +2544,10 @@ html, body, #mbeditor-root {
2358
2544
  display: flex;
2359
2545
  justify-content: flex-start;
2360
2546
  align-items: center;
2361
- padding: 4px 8px;
2362
- background: var(--ide-panel-bg);
2547
+ padding: 0 12px;
2548
+ height: 22px;
2549
+ min-height: 22px;
2550
+ background: var(--ide-bg);
2363
2551
  border-bottom: 1px solid var(--ide-border);
2364
2552
  gap: 4px;
2365
2553
  overflow: hidden;
@@ -2373,14 +2561,18 @@ html, body, #mbeditor-root {
2373
2561
  white-space: nowrap;
2374
2562
  text-overflow: ellipsis;
2375
2563
  text-align: left;
2376
- font-size: 11px;
2564
+ font-size: 13px;
2377
2565
  color: var(--ide-text-muted);
2378
2566
  padding: 0 4px;
2379
2567
  cursor: default;
2380
2568
  user-select: text;
2381
- font-family: 'JetBrains Mono', 'Fira Code', Consolas, monospace;
2382
2569
  }
2383
2570
 
2571
+ .ide-breadcrumb-dir { color: var(--ide-text-muted); }
2572
+ .ide-breadcrumb-file { color: var(--ide-text); }
2573
+ .ide-breadcrumb-file-icon { margin-right: 5px; font-size: 12px; }
2574
+ .ide-breadcrumb-sep { font-size: 9px; color: var(--ide-text-muted); margin: 0 4px; }
2575
+
2384
2576
  .ide-editor-toolbar .ide-icon-btn {
2385
2577
  flex-shrink: 1;
2386
2578
  min-width: 24px;
@@ -2408,10 +2600,12 @@ html, body, #mbeditor-root {
2408
2600
  }
2409
2601
 
2410
2602
  .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);
2603
+ backdrop-filter: blur(20px) saturate(180%);
2604
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
2605
+ background: color-mix(in srgb, var(--ide-panel-bg) 78%, transparent);
2606
+ border: 1px solid color-mix(in srgb, var(--ide-text) 16%, transparent);
2607
+ border-radius: 8px;
2608
+ box-shadow: 0 8px 24px rgba(0,0,0,0.45);
2415
2609
  min-width: 200px;
2416
2610
  max-width: 340px;
2417
2611
  max-height: 320px;
@@ -2440,13 +2634,8 @@ html, body, #mbeditor-root {
2440
2634
 
2441
2635
  .ide-methods-dropdown-item:hover,
2442
2636
  .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;
2637
+ background: var(--ide-active-bg);
2638
+ color: var(--ide-text);
2450
2639
  }
2451
2640
 
2452
2641
  .ide-methods-dropdown-line {
@@ -2507,6 +2696,12 @@ html, body, #mbeditor-root {
2507
2696
  .monaco-editor .line-numbers.mbeditor-gitline-deleted.mbeditor-gitline-added,
2508
2697
  .monaco-editor .line-numbers.mbeditor-gitline-deleted.mbeditor-gitline-modified { color: var(--ide-danger); }
2509
2698
 
2699
+ /* Monaco's own active-selection rule is `.monaco-editor .focused .selected-text`,
2700
+ which wants `focused` on a DESCENDANT. In this build the class lands on the
2701
+ `.monaco-editor` element itself, so that rule never matches and every selection
2702
+ paints with the dim inactive colour. Same theme variable, one less space. */
2703
+ .monaco-editor.focused .selected-text { background-color: var(--vscode-editor-selectionBackground); }
2704
+
2510
2705
  .ide-methods-dropdown-message {
2511
2706
  padding: 8px 12px;
2512
2707
  color: #858585;
@@ -2595,6 +2790,26 @@ html, body, #mbeditor-root {
2595
2790
  margin: 4px 0;
2596
2791
  }
2597
2792
 
2793
+ .ide-tab-context-menu-item {
2794
+ padding: 6px 14px;
2795
+ cursor: pointer;
2796
+ color: #ccc;
2797
+ font-size: 12px;
2798
+ display: flex;
2799
+ align-items: center;
2800
+ gap: 8px;
2801
+ background: transparent;
2802
+ }
2803
+
2804
+ .ide-tab-context-menu-item:hover {
2805
+ background: #094771;
2806
+ }
2807
+
2808
+ .ide-tab-context-menu-item i {
2809
+ width: 14px;
2810
+ text-align: center;
2811
+ }
2812
+
2598
2813
  .collapsible-content {
2599
2814
  animation: collapsible-open 0.15s ease;
2600
2815
  }
@@ -2610,42 +2825,59 @@ html, body, #mbeditor-root {
2610
2825
  }
2611
2826
  }
2612
2827
 
2613
- /* ── Quick Open Dialog ─────────────────────────────────────── */
2828
+ /* ── Quick Open Dialog ───────────────────────────────────────
2829
+ VS Code's command palette: a 600px sheet anchored near the top of the
2830
+ window, not a centred modal. The rows are 22px and single-line — the path
2831
+ sits beside the name rather than under it, which is what lets a full
2832
+ viewport of results be scanned without scrolling. */
2614
2833
  .quick-open-overlay {
2615
2834
  position: fixed;
2616
2835
  inset: 0;
2617
- background: rgba(0,0,0,0.6);
2836
+ background: rgba(0,0,0,0.35);
2618
2837
  display: flex;
2619
2838
  align-items: flex-start;
2620
2839
  justify-content: center;
2621
- padding-top: 12vh;
2840
+ padding-top: 6px;
2622
2841
  z-index: 9999;
2623
2842
  }
2624
2843
 
2844
+ /* Always a frosted panel — VS Code's Quick Open floats over the editor, not
2845
+ inside it, so it stays readable whether or not the glass theme is on. */
2625
2846
  .quick-open-box {
2626
- background: var(--ide-panel-bg);
2627
- border: 1px solid var(--ide-border-input);
2847
+ backdrop-filter: blur(20px) saturate(180%);
2848
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
2849
+ background: color-mix(in srgb, var(--ide-panel-bg) 78%, transparent);
2850
+ border: 1px solid color-mix(in srgb, var(--ide-text) 16%, transparent);
2628
2851
  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);
2852
+ width: min(600px, 94vw);
2853
+ max-height: min(440px, 72vh);
2632
2854
  display: flex;
2633
2855
  flex-direction: column;
2634
- box-shadow: 0 16px 48px rgba(0,0,0,0.7);
2856
+ box-shadow: 0 8px 24px rgba(0,0,0,0.45);
2635
2857
  overflow: hidden;
2636
2858
  }
2637
2859
 
2860
+ @media (prefers-reduced-transparency: reduce) {
2861
+ .quick-open-box,
2862
+ .ide-methods-dropdown {
2863
+ backdrop-filter: none;
2864
+ -webkit-backdrop-filter: none;
2865
+ background: var(--ide-panel-bg);
2866
+ }
2867
+ }
2868
+
2638
2869
  .quick-open-input-wrap {
2639
2870
  position: relative;
2871
+ padding: 6px;
2872
+ flex-shrink: 0;
2640
2873
  }
2641
2874
 
2642
2875
  .quick-open-input {
2643
2876
  background: transparent;
2644
2877
  border: none;
2645
- border-bottom: 1px solid var(--ide-border);
2646
2878
  color: var(--ide-text);
2647
- padding: 14px 40px 14px 18px;
2648
- font-size: 16px;
2879
+ padding: 4px 30px 4px 8px;
2880
+ font-size: 13px;
2649
2881
  outline: none;
2650
2882
  width: 100%;
2651
2883
  box-sizing: border-box;
@@ -2654,13 +2886,13 @@ html, body, #mbeditor-root {
2654
2886
 
2655
2887
  .quick-open-clear-btn {
2656
2888
  position: absolute;
2657
- right: 8px;
2889
+ right: 10px;
2658
2890
  top: 50%;
2659
2891
  transform: translateY(-50%);
2660
- width: 26px;
2661
- height: 26px;
2892
+ width: 22px;
2893
+ height: 22px;
2662
2894
  border: none;
2663
- border-radius: 4px;
2895
+ border-radius: 5px;
2664
2896
  background: transparent;
2665
2897
  color: var(--ide-text-muted);
2666
2898
  cursor: pointer;
@@ -2671,53 +2903,64 @@ html, body, #mbeditor-root {
2671
2903
 
2672
2904
  .quick-open-clear-btn:hover {
2673
2905
  background: var(--ide-hover-bg);
2674
- color: var(--ide-text-dim);
2906
+ color: var(--ide-text);
2675
2907
  }
2676
2908
 
2677
2909
  .quick-open-results {
2678
2910
  overflow-y: auto;
2679
2911
  flex: 1;
2912
+ padding-bottom: 4px;
2680
2913
  }
2681
2914
 
2682
2915
  .quick-open-result {
2683
- padding: 8px 18px;
2916
+ padding: 0 10px;
2917
+ height: 22px;
2684
2918
  cursor: pointer;
2685
2919
  display: flex;
2686
2920
  align-items: center;
2687
- gap: 10px;
2688
- font-size: 14px;
2689
- transition: background 0.1s;
2921
+ gap: 8px;
2922
+ font-size: 13px;
2690
2923
  }
2691
2924
 
2692
2925
  .quick-open-result-icon {
2693
2926
  flex-shrink: 0;
2694
- width: 18px;
2927
+ width: 16px;
2928
+ font-size: 12px;
2695
2929
  text-align: center;
2696
2930
  color: #78b4e0;
2697
2931
  }
2698
2932
 
2933
+ /* Name and path on one line: the name at its natural width, the path taking
2934
+ the slack and ellipsing. Two stacked lines would double the row height. */
2699
2935
  .quick-open-result-body {
2700
2936
  min-width: 0;
2701
2937
  flex: 1;
2938
+ display: flex;
2939
+ align-items: baseline;
2940
+ gap: 8px;
2941
+ overflow: hidden;
2702
2942
  }
2703
2943
 
2704
- .quick-open-result:hover,
2705
- .quick-open-result.selected { background: var(--ide-active-bg); }
2944
+ .quick-open-result:hover { background: var(--ide-hover-bg); }
2945
+ .quick-open-result.selected { background: var(--ide-active-bg); color: var(--ide-text); }
2706
2946
 
2707
- .quick-open-result-name { color: var(--ide-text); }
2947
+ .quick-open-result-name { color: var(--ide-text); flex-shrink: 0; white-space: nowrap; }
2708
2948
  .quick-open-result-path { color: var(--ide-text-muted); font-size: 12px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
2709
2949
 
2710
2950
  .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; }
2951
+ .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; }
2952
+ .quick-open-section-header-muted { color: var(--ide-text-muted); font-weight: 600; letter-spacing: 0.08em; }
2953
+
2954
+ .quick-open-recent-label { font-size: 12px; color: var(--ide-text-muted); flex-shrink: 0; }
2712
2955
 
2713
2956
  .quick-open-star-btn {
2714
2957
  flex-shrink: 0;
2715
2958
  background: none;
2716
2959
  border: none;
2717
2960
  cursor: pointer;
2718
- padding: 2px 6px;
2961
+ padding: 0 4px;
2719
2962
  color: var(--ide-text-muted);
2720
- font-size: 14px;
2963
+ font-size: 12px;
2721
2964
  line-height: 1;
2722
2965
  opacity: 0;
2723
2966
  transition: opacity 0.1s, color 0.1s;
@@ -2828,48 +3071,177 @@ html, body, #mbeditor-root {
2828
3071
  .ide-draft-restore-btn:hover { background: var(--ide-hover-bg); }
2829
3072
  .ide-draft-restore-btn-primary {
2830
3073
  background: var(--ide-accent);
2831
- color: var(--ide-accent-text);
3074
+ color: var(--ide-on-accent);
2832
3075
  border-color: var(--ide-accent);
2833
3076
  }
2834
3077
  .ide-draft-restore-btn-primary:hover { opacity: 0.9; }
2835
3078
 
2836
3079
  /* ── 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); }
3080
+ /* ══ SETTINGS MODAL ═════════════════════════════════════════════════════════
3081
+ * VS Code-style: left nav of sections, scrolling right pane of rows.
3082
+ * ─────────────────────────────────────────────────────────────────────────── */
3083
+ .ide-settings-overlay {
3084
+ position: fixed;
3085
+ inset: 0;
3086
+ background: rgba(0,0,0,0.45);
3087
+ z-index: 9100;
3088
+ display: flex;
3089
+ align-items: center;
3090
+ justify-content: center;
3091
+ }
2862
3092
 
2863
- /* Settings tab (pane view) */
2864
- .ide-settings-tab-content {
3093
+ /* Frosted like Quick Open — it floats over the editor, not inside it. */
3094
+ .ide-settings-modal {
3095
+ backdrop-filter: blur(20px) saturate(180%);
3096
+ -webkit-backdrop-filter: blur(20px) saturate(180%);
3097
+ background: color-mix(in srgb, var(--ide-panel-bg) 82%, transparent);
3098
+ border: 1px solid color-mix(in srgb, var(--ide-text) 16%, transparent);
3099
+ border-radius: 8px;
3100
+ width: min(900px, 90vw);
3101
+ height: min(640px, 85vh);
2865
3102
  display: flex;
2866
3103
  flex-direction: column;
2867
- height: 100%;
3104
+ box-shadow: 0 8px 32px rgba(0,0,0,0.5);
3105
+ overflow: hidden;
3106
+ color: var(--ide-text);
3107
+ }
3108
+
3109
+ @media (prefers-reduced-transparency: reduce) {
3110
+ .ide-settings-modal {
3111
+ backdrop-filter: none;
3112
+ -webkit-backdrop-filter: none;
3113
+ background: var(--ide-panel-bg);
3114
+ }
3115
+ }
3116
+
3117
+ .ide-settings-modal-header {
3118
+ height: 44px;
3119
+ flex-shrink: 0;
3120
+ display: flex;
3121
+ align-items: center;
3122
+ gap: 12px;
3123
+ padding: 0 12px;
3124
+ border-bottom: 1px solid var(--ide-border);
3125
+ }
3126
+ .ide-settings-modal-title { font-size: 13px; font-weight: 600; color: var(--ide-text); flex-shrink: 0; }
3127
+ .ide-settings-search {
3128
+ flex: 1;
3129
+ min-width: 0;
3130
+ background: var(--ide-input-bg);
3131
+ color: var(--ide-text);
3132
+ border: 1px solid var(--ide-border-input);
3133
+ border-radius: 8px;
3134
+ padding: 5px 10px;
3135
+ font-size: 12px;
3136
+ margin-bottom: 0;
3137
+ }
3138
+ .ide-settings-search::placeholder { color: var(--ide-text-muted); }
3139
+ /* Scoped under the modal: a global `button:not(.pico-btn)` rule sets 12px
3140
+ and would otherwise shrink the glyph to a speck. */
3141
+ .ide-settings-modal .ide-settings-modal-close {
3142
+ width: 28px;
3143
+ height: 28px;
3144
+ display: inline-flex;
3145
+ align-items: center;
3146
+ justify-content: center;
3147
+ background: none;
3148
+ border: none;
3149
+ border-radius: 6px;
3150
+ color: var(--ide-text-muted);
3151
+ font-size: 16px;
3152
+ line-height: 1;
3153
+ cursor: pointer;
3154
+ padding: 0;
3155
+ margin-bottom: 0;
3156
+ }
3157
+ .ide-settings-modal .ide-settings-modal-close:hover { color: var(--ide-text); background: var(--ide-hover-bg); }
3158
+
3159
+ .ide-settings-modal-body { flex: 1; display: flex; min-height: 0; }
3160
+
3161
+ .ide-settings-nav {
3162
+ width: 180px;
3163
+ flex-shrink: 0;
3164
+ border-right: 1px solid var(--ide-border);
3165
+ padding: 10px 0;
3166
+ overflow-y: auto;
3167
+ display: flex;
3168
+ flex-direction: column;
3169
+ justify-content: flex-start; /* a shared nav rule sets space-between */
3170
+ }
3171
+ .ide-settings-nav-item {
3172
+ background: none;
3173
+ border: none;
3174
+ border-left: 2px solid transparent;
3175
+ color: var(--ide-text-muted);
3176
+ font-size: 11px;
3177
+ font-weight: 600;
3178
+ text-transform: uppercase;
3179
+ letter-spacing: 0.6px;
3180
+ text-align: left;
3181
+ padding: 6px 14px;
3182
+ cursor: pointer;
3183
+ margin-bottom: 0;
3184
+ width: 100%;
3185
+ }
3186
+ .ide-settings-nav-item:hover { color: var(--ide-text); }
3187
+ .ide-settings-nav-item.active { color: var(--ide-text); border-left-color: var(--ide-accent); }
3188
+
3189
+ .ide-settings-body {
3190
+ flex: 1;
3191
+ min-width: 0;
2868
3192
  overflow-y: auto;
2869
- background: var(--bg-panel, var(--ide-bg));
2870
- color: var(--text-main, var(--ide-text));
3193
+ padding: 12px 20px 20px;
3194
+ }
3195
+ .ide-settings-section { padding-top: 10px; }
3196
+ .ide-settings-section-header {
3197
+ font-size: 13px;
3198
+ font-weight: 600;
3199
+ color: var(--ide-text);
3200
+ margin: 0 0 10px;
3201
+ padding-bottom: 4px;
3202
+ border-bottom: 1px solid var(--ide-border);
3203
+ }
3204
+
3205
+ .ide-settings-row {
3206
+ display: flex;
3207
+ flex-direction: column;
3208
+ align-items: flex-start;
3209
+ gap: 3px;
3210
+ margin-bottom: 12px;
3211
+ }
3212
+ .ide-settings-row-check {
3213
+ display: grid;
3214
+ grid-template-columns: auto 1fr;
3215
+ column-gap: 8px;
3216
+ align-items: start;
3217
+ }
3218
+ .ide-settings-row-check .ide-settings-checkbox { grid-column: 1; grid-row: 1; margin: 2px 0 0; }
3219
+ .ide-settings-row-check .ide-settings-label { grid-column: 2; grid-row: 1; }
3220
+ .ide-settings-row-check .ide-settings-desc { grid-column: 2; grid-row: 2; }
3221
+
3222
+ .ide-settings-label { font-size: 13px; color: var(--ide-text); }
3223
+ .ide-settings-desc { font-size: 12px; color: var(--ide-text-muted); line-height: 1.4; }
3224
+ .ide-settings-input {
3225
+ background: var(--ide-input-bg);
3226
+ color: var(--ide-text);
3227
+ border: 1px solid var(--ide-border-input);
3228
+ border-radius: 4px;
3229
+ padding: 4px 6px;
3230
+ font-size: 12px;
3231
+ width: 260px;
3232
+ max-width: 100%;
3233
+ box-sizing: border-box;
3234
+ margin-top: 2px;
2871
3235
  }
3236
+ .ide-settings-input-number { width: 100px; }
3237
+ .ide-settings-input-wide { font-family: monospace; width: 100%; }
3238
+ .ide-settings-checkbox { width: auto; cursor: pointer; flex-shrink: 0; }
2872
3239
 
3240
+ .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; }
3241
+ .ide-settings-config-link:hover { color: var(--ide-accent); }
3242
+ .ide-settings-empty { font-size: 12px; color: var(--ide-text-muted); padding: 20px 0; }
3243
+ .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; }
3244
+ .ide-settings-reset-btn:hover { background: var(--ide-border-input); color: var(--ide-text-dim); }
2873
3245
 
2874
3246
  /* ══ PICO CSS COMPACT OVERRIDES ═════════════════════════════════════════════
2875
3247
  * Pico's classless variant inflates native elements with generous padding.
@@ -2974,12 +3346,12 @@ textarea {
2974
3346
  .ide-statusbar button,
2975
3347
  .statusbar-btn {
2976
3348
  --pico-form-element-spacing-vertical: 0;
2977
- --pico-form-element-spacing-horizontal: 8px;
2978
- height: 22px;
2979
- padding: 0 8px;
2980
- font-size: 11px;
3349
+ --pico-form-element-spacing-horizontal: 6px;
3350
+ height: 20px;
3351
+ padding: 0 6px;
3352
+ font-size: 12px;
2981
3353
  line-height: 1;
2982
- border-radius: 2px;
3354
+ border-radius: 5px;
2983
3355
  border: none;
2984
3356
  background: transparent;
2985
3357
  color: var(--ide-accent-text);
@@ -3076,7 +3448,19 @@ button:not(.pico-btn) { margin-bottom: 0; }
3076
3448
  border-radius: 0;
3077
3449
  }
3078
3450
  /* Re-apply border-radius for buttons that intentionally need it */
3079
- .ide-shell .ide-titlebar-search { border-radius: 999px; } /* pill */
3451
+ .ide-shell .ide-titlebar-search { border-radius: 6px; }
3452
+ .ide-shell .collab-avatar {
3453
+ width: 9px;
3454
+ height: 9px;
3455
+ padding: 0;
3456
+ border-radius: 50%;
3457
+ }
3458
+ .ide-shell .collab-avatar-more {
3459
+ width: auto;
3460
+ height: auto;
3461
+ padding: 0;
3462
+ border-radius: 0;
3463
+ }
3080
3464
  .ide-shell .project-action-btn,
3081
3465
  .ide-shell .git-header-btn,
3082
3466
  .ide-shell .git-section-action-btn,
@@ -3192,15 +3576,6 @@ button:not(.pico-btn) { margin-bottom: 0; }
3192
3576
  background: var(--ide-panel-bg, #1e1e1e);
3193
3577
  border-top: 1px solid var(--ide-border, #333);
3194
3578
  }
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
3579
  .ide-log-header {
3205
3580
  display: flex;
3206
3581
  align-items: center;
@@ -3268,25 +3643,35 @@ button:not(.pico-btn) { margin-bottom: 0; }
3268
3643
  background: var(--ide-panel-bg, #1e1e1e);
3269
3644
  border-top: 1px solid var(--ide-border, #333);
3270
3645
  }
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
3646
  .ide-problems-header {
3279
3647
  display: flex;
3280
3648
  align-items: center;
3281
3649
  gap: 8px;
3282
- padding: 4px 10px;
3650
+ height: 35px;
3651
+ padding: 0 10px;
3283
3652
  border-bottom: 1px solid var(--ide-border, #333);
3284
3653
  font-size: 12px;
3285
- color: var(--ide-fg-muted, #ccc);
3654
+ color: var(--ide-text, #ccc);
3655
+ }
3656
+ .ide-problems-title {
3657
+ font-size: 11px;
3658
+ font-weight: 600;
3659
+ text-transform: uppercase;
3660
+ letter-spacing: 0.06em;
3661
+ }
3662
+ .ide-problems-total-pill {
3663
+ display: inline-flex;
3664
+ align-items: center;
3665
+ height: 18px;
3666
+ padding: 0 7px;
3667
+ border-radius: 999px;
3668
+ background: var(--ide-hover-bg, #2a2a2a);
3669
+ color: var(--ide-text, #ccc);
3670
+ font-size: 11px;
3671
+ font-variant-numeric: tabular-nums;
3286
3672
  }
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. */
3673
+ /* Severity toggles. Pushed to the right of the title/count; the text filter
3674
+ that follows keeps its own left margin off `auto` so the two sit together. */
3290
3675
  .ide-problems-severity-filter {
3291
3676
  margin-left: auto;
3292
3677
  display: flex;
@@ -3297,50 +3682,42 @@ button:not(.pico-btn) { margin-bottom: 0; }
3297
3682
  align-items: center;
3298
3683
  gap: 4px;
3299
3684
  background: transparent;
3300
- border: 1px solid transparent;
3685
+ border: none;
3301
3686
  border-radius: 3px;
3302
3687
  padding: 1px 6px;
3303
3688
  font-size: 11px;
3304
3689
  font-variant-numeric: tabular-nums;
3305
3690
  cursor: pointer;
3306
- /* Off is legible but clearly recessive — a disabled-looking chip reads as
3307
- "broken" rather than "toggled off". */
3308
3691
  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
3692
  }
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); }
3693
+ .ide-problems-sev-chip:hover,
3694
+ .ide-problems-sev-chip.is-on { color: var(--ide-text, #ccc); }
3320
3695
 
3321
3696
  .ide-problems-filter {
3322
3697
  margin-left: 8px;
3323
3698
  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;
3699
+ color: var(--ide-text, #eee);
3700
+ border: 1px solid var(--ide-border-input, #333);
3701
+ border-radius: 4px;
3702
+ padding: 0 6px;
3703
+ height: 22px;
3328
3704
  font-size: 12px;
3329
- width: 180px;
3705
+ width: 140px;
3330
3706
  }
3331
3707
  .ide-problems-btn {
3332
3708
  background: transparent;
3333
3709
  border: none;
3334
- color: var(--ide-fg-muted, #ccc);
3710
+ color: var(--ide-text-muted, #ccc);
3335
3711
  cursor: pointer;
3336
3712
  padding: 2px 6px;
3337
3713
  }
3338
- .ide-problems-btn:hover { color: var(--ide-fg, #fff); }
3714
+ .ide-problems-btn:hover { color: var(--ide-text, #fff); }
3339
3715
  .ide-problems-btn:disabled { opacity: 0.4; cursor: default; }
3340
- .ide-problems-btn:disabled:hover { color: var(--ide-fg-muted, #ccc); }
3716
+ .ide-problems-btn:disabled:hover { color: var(--ide-text-muted, #ccc); }
3341
3717
  .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); }
3718
+ .ide-problems-actions .ide-icon-btn,
3719
+ .ide-problems-header > .ide-icon-btn { min-width: 22px; width: 22px; height: 22px; font-size: 13px; }
3720
+ .ide-problems-actions .ide-icon-btn:disabled { opacity: 0.4; cursor: default; pointer-events: none; }
3344
3721
  .ide-problems-body { flex: 1; overflow: auto; padding: 4px 0; font-size: 12px; }
3345
3722
  .ide-problems-empty {
3346
3723
  padding: 10px 14px;
@@ -3351,9 +3728,18 @@ button:not(.pico-btn) { margin-bottom: 0; }
3351
3728
  display: flex;
3352
3729
  align-items: center;
3353
3730
  gap: 6px;
3354
- padding: 5px 12px 3px;
3355
- color: var(--ide-fg-muted, #ccc);
3356
- font-weight: 600;
3731
+ height: 22px;
3732
+ padding: 0 12px;
3733
+ color: var(--ide-text, #ccc);
3734
+ }
3735
+ .ide-problems-file-name .fa-chevron-down {
3736
+ color: var(--ide-text-muted, #858585);
3737
+ font-size: 10px;
3738
+ width: 10px;
3739
+ }
3740
+ .ide-problems-file-name .ide-problems-dir {
3741
+ color: var(--ide-text-muted, #858585);
3742
+ font-size: 12px;
3357
3743
  }
3358
3744
  .ide-problems-file-count {
3359
3745
  background: var(--ide-hover-bg, #2a2a2a);
@@ -3368,22 +3754,23 @@ button:not(.pico-btn) { margin-bottom: 0; }
3368
3754
  align-items: baseline;
3369
3755
  gap: 8px;
3370
3756
  width: 100%;
3371
- padding: 3px 12px 3px 28px;
3757
+ height: 22px;
3758
+ padding: 0 12px 0 28px;
3372
3759
  background: transparent;
3373
3760
  border: none;
3374
- color: var(--ide-fg, #d4d4d4);
3761
+ color: var(--ide-text, #d4d4d4);
3375
3762
  font-size: 12px;
3376
3763
  font-family: inherit;
3377
3764
  text-align: left;
3378
3765
  cursor: pointer;
3379
3766
  }
3380
- .ide-problems-item:hover,
3381
- .ide-problems-item:focus-visible { background: var(--ide-hover-bg, #2a2a2a); }
3767
+ .ide-problems-item:hover { background: var(--ide-hover-bg, #2a2a2a); }
3768
+ .ide-problems-item:focus-visible { background: var(--ide-active-bg, #2a2a2a); }
3382
3769
  .ide-problems-icon { flex-shrink: 0; }
3383
3770
  .ide-problems-item-error .ide-problems-icon { color: var(--ide-danger); }
3384
3771
  .ide-problems-item-warning .ide-problems-icon { color: var(--ide-warning); }
3385
3772
  .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; }
3773
+ .ide-problems-msg { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--ide-text, #d4d4d4); }
3387
3774
  /* The offending source line, dimmed so it reads as context rather than as part
3388
3775
  of the message. It is the one part of the row allowed to shrink, so a long
3389
3776
  line gives way to the message and location instead of pushing them out. */
@@ -3393,7 +3780,6 @@ button:not(.pico-btn) { margin-bottom: 0; }
3393
3780
  text-overflow: ellipsis;
3394
3781
  white-space: nowrap;
3395
3782
  color: var(--ide-text-muted, #858585);
3396
- opacity: 0.8;
3397
3783
  font-family: var(--ide-mono, monospace);
3398
3784
  font-size: 11px;
3399
3785
  }
@@ -3422,12 +3808,24 @@ button:not(.pico-btn) { margin-bottom: 0; }
3422
3808
  margin-left: auto;
3423
3809
  color: var(--ide-text-muted, #858585);
3424
3810
  font-size: 11px;
3811
+ font-variant-numeric: tabular-nums;
3425
3812
  }
3426
3813
 
3427
3814
  /* ── Model graph ──────────────────────────────────────────────────────────
3428
3815
  An SVG ER diagram of the app's ActiveRecord models. The panel is narrow, so
3429
3816
  the SVG scrolls in both directions rather than being scaled down to
3430
3817
  illegibility. */
3818
+ /* The graph replaces the whole centre column when its view is open, so it
3819
+ takes the same box the panes and drawers would have. */
3820
+ .ide-model-graph-view {
3821
+ flex: 1;
3822
+ display: flex;
3823
+ flex-direction: column;
3824
+ min-width: 0;
3825
+ min-height: 0;
3826
+ overflow: hidden;
3827
+ }
3828
+
3431
3829
  .ide-model-graph { display: flex; flex-direction: column; height: 100%; min-height: 0; position: relative; }
3432
3830
  .ide-model-graph-empty {
3433
3831
  padding: 16px;
@@ -3577,8 +3975,7 @@ button:not(.pico-btn) { margin-bottom: 0; }
3577
3975
  The gap is the icon-to-its-own-number spacing; the warning icon opens the
3578
3976
  second pair, so it gets the wider separator. */
3579
3977
  .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; }
3978
+ .statusbar-problems-warning-icon { margin-left: 8px; }
3582
3979
  .statusbar-problems-count { font-variant-numeric: tabular-nums; }
3583
3980
 
3584
3981
  /* Search decorations off across engines — Chrome/Safari draw their own
@@ -3717,6 +4114,7 @@ input.ide-model-graph-search[type="search"] {
3717
4114
  /* Pair-programming diagnostics. The chip only appears when a condition pairing
3718
4115
  needs has actually failed — an empty room is silence, not a warning. */
3719
4116
  .mbeditor-collab-trouble { color: var(--ide-warning, #cca700); }
4117
+ .mbeditor-conflict-chip { color: var(--ide-error, #f48771); }
3720
4118
  .mbeditor-modal-backdrop {
3721
4119
  position: fixed; inset: 0; z-index: 5000;
3722
4120
  background: rgba(0, 0, 0, 0.45);
@@ -3741,3 +4139,28 @@ input.ide-model-graph-search[type="search"] {
3741
4139
  .mbeditor-collab-diag-label { font-weight: 500; }
3742
4140
  .mbeditor-collab-diag-detail { color: var(--ide-text-muted, #858585); margin-top: 3px; line-height: 1.5; }
3743
4141
  .mbeditor-collab-diag-foot { color: var(--ide-text-muted, #858585); margin-bottom: 12px; }
4142
+
4143
+ .combined-view { height: 100%; overflow-y: auto; background: var(--ide-bg, #1e1e1e); color: var(--ide-text, #d4d4d4); }
4144
+ .combined-note { padding: 8px 16px; font-size: 12px; color: var(--ide-text-muted, #888); }
4145
+ .combined-file-header { position: sticky; top: 0; z-index: 1; display: flex; align-items: center; gap: 8px; padding: 6px 16px; background: var(--ide-bg-elevated, var(--ide-bg, #252526)); border-bottom: 1px solid var(--ide-border, #333); font-size: 12px; }
4146
+ .combined-file-path { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-weight: 600; }
4147
+ .combined-file-btn { font-size: 11px; padding: 2px 8px; cursor: pointer; color: inherit; background: transparent; border: 1px solid var(--ide-border, #444); border-radius: 3px; }
4148
+ .combined-file-btn:disabled { opacity: 0.4; cursor: default; }
4149
+ .combined-file-body { margin: 0; padding: 8px 16px 24px; overflow-x: auto; font: 12px/1.5 Menlo, Consolas, monospace; tab-size: 2; }
4150
+
4151
+ /* Combined view: method picking + Markdown export; import Compare; Paste & Place */
4152
+ .combined-toolbar { display: flex; gap: 8px; padding: 6px 16px; border-bottom: 1px solid var(--ide-border, #333); }
4153
+ .combined-methods { display: flex; flex-wrap: wrap; gap: 2px 12px; max-height: 96px; overflow-y: auto; padding: 4px 16px; font-size: 11px; border-bottom: 1px solid var(--ide-border, #333); }
4154
+ .combined-method { display: inline-flex; align-items: center; gap: 4px; white-space: nowrap; cursor: pointer; }
4155
+ .import-conflict-compare { margin-left: 8px; padding: 0; background: none; border: 0; color: var(--ide-accent, #0e639c); font-size: 11px; cursor: pointer; }
4156
+ .import-conflict-compare:hover { text-decoration: underline; }
4157
+ .import-conflict-modal.import-conflict-wide { min-width: min(900px, 94vw); max-width: 94vw; }
4158
+ .import-conflict-diff { height: 360px; margin-top: 8px; }
4159
+ .paste-place-modal { min-width: min(900px, 94vw); max-width: 94vw; }
4160
+ .paste-place-body { padding: 8px 16px; }
4161
+ .paste-place-input { width: 100%; box-sizing: border-box; font: 12px/1.5 var(--ide-mono-font, monospace); color: var(--ide-text, #d4d4d4); background: var(--ide-input-bg, #3c3c3c); border: 1px solid var(--ide-border, #3c3c3c); border-radius: 3px; padding: 8px; resize: vertical; }
4162
+ .paste-place-error { color: var(--ide-error, #f48771); font-size: 12px; padding: 6px 0; }
4163
+ .paste-place-row { display: flex; align-items: center; gap: 8px; padding: 4px 0; font-size: 12px; }
4164
+ .paste-place-name { font-family: var(--ide-mono-font, monospace); min-width: 160px; }
4165
+ .paste-place-row select { flex: 1; min-width: 0; color: var(--ide-text, #d4d4d4); background: var(--ide-input-bg, #3c3c3c); border: 1px solid var(--ide-border, #3c3c3c); }
4166
+ .paste-place-diff { height: 260px; margin-top: 8px; border: 1px solid var(--ide-border, #3c3c3c); }