mbeditor 0.12.8 → 0.13.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +207 -0
- data/app/assets/javascripts/mbeditor/application.js +7 -1
- data/app/assets/javascripts/mbeditor/collaboration_service.js +31 -0
- data/app/assets/javascripts/mbeditor/color_provider.js +5 -0
- data/app/assets/javascripts/mbeditor/components/CollapsibleSection.js +3 -1
- data/app/assets/javascripts/mbeditor/components/EditorPanel.js +135 -40
- data/app/assets/javascripts/mbeditor/components/FileTree.js +6 -1
- data/app/assets/javascripts/mbeditor/components/GitPanel.js +10 -1
- data/app/assets/javascripts/mbeditor/components/ImportConflictModal.js +15 -8
- data/app/assets/javascripts/mbeditor/components/ImportDialog.js +216 -0
- data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +629 -137
- data/app/assets/javascripts/mbeditor/components/ModelGraph.js +25 -6
- data/app/assets/javascripts/mbeditor/components/ProblemsPanel.js +218 -13
- data/app/assets/javascripts/mbeditor/components/QuickOpenDialog.js +39 -33
- data/app/assets/javascripts/mbeditor/components/TabBar.js +11 -2
- data/app/assets/javascripts/mbeditor/components/TestRunPanel.js +312 -0
- data/app/assets/javascripts/mbeditor/editor_plugins.js +444 -116
- data/app/assets/javascripts/mbeditor/file_import.js +78 -0
- data/app/assets/javascripts/mbeditor/file_service.js +122 -18
- data/app/assets/javascripts/mbeditor/git_service.js +130 -8
- data/app/assets/javascripts/mbeditor/history_service.js +33 -38
- data/app/assets/javascripts/mbeditor/log_service.js +4 -0
- data/app/assets/javascripts/mbeditor/search_service.js +30 -2
- data/app/assets/javascripts/mbeditor/tab_manager.js +59 -6
- data/app/assets/javascripts/mbeditor/websocket_service.js +88 -4
- data/app/assets/stylesheets/mbeditor/editor.css +302 -55
- data/app/channels/mbeditor/channel_authentication.rb +7 -0
- data/app/channels/mbeditor/editor_channel.rb +6 -3
- data/app/controllers/mbeditor/application_controller.rb +5 -0
- data/app/controllers/mbeditor/editors_controller.rb +173 -29
- data/app/controllers/mbeditor/git_controller.rb +3 -3
- data/app/controllers/mbeditor/logs_controller.rb +3 -1
- data/app/services/mbeditor/collaboration_doc_store.rb +7 -0
- data/app/services/mbeditor/editor_state_service.rb +36 -26
- data/app/services/mbeditor/exclusion_matcher.rb +12 -10
- data/app/services/mbeditor/file_operation_service.rb +38 -3
- data/app/services/mbeditor/file_tree_service.rb +30 -2
- data/app/services/mbeditor/git_combined_diff_service.rb +13 -3
- data/app/services/mbeditor/git_commit_detail_service.rb +20 -16
- data/app/services/mbeditor/git_diff_service.rb +5 -1
- data/app/services/mbeditor/git_info_service.rb +20 -8
- data/app/services/mbeditor/git_line_diff_service.rb +6 -2
- data/app/services/mbeditor/git_service.rb +65 -15
- data/app/services/mbeditor/js_definition_service.rb +3 -1
- data/app/services/mbeditor/js_globals_service.rb +7 -4
- data/app/services/mbeditor/js_members_service.rb +3 -2
- data/app/services/mbeditor/js_program_service.rb +15 -5
- data/app/services/mbeditor/js_syntax_check_service.rb +15 -4
- data/app/services/mbeditor/process_runner.rb +42 -12
- data/app/services/mbeditor/ri_definition_service.rb +8 -1
- data/app/services/mbeditor/route_service.rb +45 -8
- data/app/services/mbeditor/rubocop_run_service.rb +86 -0
- data/app/services/mbeditor/ruby_definition_service.rb +23 -4
- data/app/services/mbeditor/schema_service.rb +65 -64
- data/app/services/mbeditor/search_replace_service.rb +75 -10
- data/app/services/mbeditor/test_runner_service.rb +98 -10
- data/lib/mbeditor/cable_log_filter.rb +8 -2
- data/lib/mbeditor/configuration.rb +12 -1
- data/lib/mbeditor/editor_bootstrap.rb +18 -12
- data/lib/mbeditor/engine.rb +22 -3
- data/lib/mbeditor/exception_log.rb +2 -2
- data/lib/mbeditor/pending_migrations.rb +33 -0
- data/lib/mbeditor/rack/handle_pending_migrations.rb +25 -15
- data/lib/mbeditor/rack/pending_migration_bypass.rb +104 -0
- data/lib/mbeditor/rack/silence_ping_request.rb +10 -2
- data/lib/mbeditor/route_map.rb +2 -0
- data/lib/mbeditor/ruby_lsp_client.rb +71 -12
- data/lib/mbeditor/version.rb +1 -1
- metadata +7 -2
|
@@ -8,10 +8,86 @@
|
|
|
8
8
|
box-shadow: none !important;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
/*
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
/* ── Pico containment ──────────────────────────────────────────────────────
|
|
12
|
+
*
|
|
13
|
+
* The host app's stylesheet loads alongside this one, and when it is Pico (the
|
|
14
|
+
* dummy app's is) it styles bare element and ARIA selectors — `button`,
|
|
15
|
+
* `[role=button]`, `[role=group]`, `[disabled]` — every one of which matches
|
|
16
|
+
* this UI's markup. Two shipped bugs came from exactly that: `[role=button]`
|
|
17
|
+
* painted the tab close buttons as filled primary squares, and
|
|
18
|
+
* `[disabled] { pointer-events: none }` stopped disabled toolbar buttons from
|
|
19
|
+
* receiving hover at all, so their tooltips never appeared.
|
|
20
|
+
*
|
|
21
|
+
* The technique is to zero Pico's own custom properties: every Pico button
|
|
22
|
+
* declaration reads from them, so neutralising the variables disarms the whole
|
|
23
|
+
* ruleset at once, which beats fighting each declaration on specificity. Only
|
|
24
|
+
* the few properties Pico hard-codes need explicit overrides.
|
|
25
|
+
*
|
|
26
|
+
* Two things make this safe to widen, and both depend on where the block sits:
|
|
27
|
+
*
|
|
28
|
+
* - The root is wrapped in :where(), so these rules carry exactly Pico's own
|
|
29
|
+
* specificity, no more. They win over Pico only because the whole of
|
|
30
|
+
* editor.css is bundled after the whole of Pico.
|
|
31
|
+
* - The block lives at the TOP of this file, so every component rule below —
|
|
32
|
+
* .statusbar-btn, .project-actions, .tab-close — is later at equal or
|
|
33
|
+
* greater specificity and still wins.
|
|
34
|
+
*
|
|
35
|
+
* That combination is what makes this a floor rather than a new source of
|
|
36
|
+
* truth: it can only remove Pico's opinions, never override this file's own.
|
|
37
|
+
* Moving it further down would silently invert that.
|
|
38
|
+
*
|
|
39
|
+
* Deliberately NOT reset: Pico's `*` box-sizing and background-repeat rules
|
|
40
|
+
* (this UI assumes border-box, which is what Pico sets), and `position` on
|
|
41
|
+
* [role=group] (Pico's `relative` is the safer value for any absolutely
|
|
42
|
+
* positioned descendant, and nothing here needs it to be static).
|
|
43
|
+
*
|
|
44
|
+
* Monaco is listed separately because its widgets portal out of the shell to
|
|
45
|
+
* document.body (.context-view, .action-widget), so a #mbeditor-root scope
|
|
46
|
+
* would miss them. Monaco renders its own actions as <a role="button">.
|
|
47
|
+
*/
|
|
48
|
+
:where(#mbeditor-root) :is(button, [type="button"], [type="submit"], [type="reset"], [role="button"], [role="group"]),
|
|
49
|
+
.monaco-hover [role=button],
|
|
50
|
+
.action-widget [role=button],
|
|
51
|
+
.context-view [role=button],
|
|
52
|
+
.monaco-editor [role=button],
|
|
53
|
+
.monaco-diff-editor [role=button] {
|
|
54
|
+
--pico-background-color: transparent;
|
|
55
|
+
--pico-border-color: transparent;
|
|
56
|
+
--pico-color: inherit;
|
|
57
|
+
--pico-box-shadow: none;
|
|
58
|
+
--pico-form-element-spacing-vertical: 0;
|
|
59
|
+
--pico-form-element-spacing-horizontal: 0;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/* Properties Pico hard-codes, which the variable reset above cannot reach. */
|
|
63
|
+
|
|
64
|
+
/* Pico's `[disabled] { pointer-events: none }` means a disabled control never
|
|
65
|
+
* receives hover, so neither a title attribute nor a CSS hover tooltip can
|
|
66
|
+
* fire on it — and a disabled button is exactly when a user most wants to be
|
|
67
|
+
* told what it is. */
|
|
68
|
+
:where(#mbeditor-root) [disabled],
|
|
69
|
+
:where(#mbeditor-root) [aria-disabled="true"] {
|
|
70
|
+
pointer-events: auto;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* Pico lays [role=group] out as a full-width bordered input group. It is used
|
|
74
|
+
* here only to label toolbars for screen readers. */
|
|
75
|
+
:where(#mbeditor-root) [role="group"] {
|
|
76
|
+
display: inline-flex;
|
|
77
|
+
width: auto;
|
|
78
|
+
margin-bottom: 0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Pico gives every button a bottom margin to space form controls. */
|
|
82
|
+
:where(#mbeditor-root) :is(button, [type="button"], [type="submit"], [type="reset"]) {
|
|
83
|
+
margin: 0;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/* Pico's focus/active state sets its primary blue on --pico-background-color
|
|
87
|
+
and --pico-border-color, which bleeds through as a blue tint on any button
|
|
88
|
+
not overriding its background with a literal value. Kept separate from the
|
|
89
|
+
block above: this one is intentionally more specific than a component rule,
|
|
90
|
+
because no component should be able to opt back into a Pico focus colour. */
|
|
15
91
|
#mbeditor-root button:not(.pico-btn):focus,
|
|
16
92
|
#mbeditor-root button:not(.pico-btn):focus-visible,
|
|
17
93
|
#mbeditor-root button:not(.pico-btn):active {
|
|
@@ -129,57 +205,66 @@ html, body, #mbeditor-root {
|
|
|
129
205
|
|
|
130
206
|
/* ── Activity Bar (VSCode-style, always visible) ───────── */
|
|
131
207
|
.ide-activity-bar {
|
|
132
|
-
width:
|
|
133
|
-
min-width:
|
|
208
|
+
width: 60px;
|
|
209
|
+
min-width: 60px;
|
|
134
210
|
flex-shrink: 0;
|
|
135
211
|
background: var(--ide-panel-bg);
|
|
136
212
|
border-right: 1px solid var(--ide-border);
|
|
137
213
|
display: flex;
|
|
138
214
|
flex-direction: column;
|
|
139
|
-
|
|
140
|
-
|
|
215
|
+
/* stretch, not center: the buttons fill the bar edge to edge so the active
|
|
216
|
+
indicator can sit flush against the left of the screen. Centering them
|
|
217
|
+
left a gutter the indicator was inset by. */
|
|
218
|
+
align-items: stretch;
|
|
219
|
+
padding: 0;
|
|
141
220
|
z-index: 10;
|
|
142
221
|
}
|
|
143
222
|
|
|
144
223
|
.ide-activity-bar-top {
|
|
145
224
|
display: flex;
|
|
146
225
|
flex-direction: column;
|
|
147
|
-
align-items:
|
|
148
|
-
gap:
|
|
226
|
+
align-items: stretch;
|
|
227
|
+
gap: 0;
|
|
149
228
|
flex: 1;
|
|
150
229
|
}
|
|
151
230
|
|
|
152
231
|
.ide-activity-bar-bottom {
|
|
153
232
|
display: flex;
|
|
154
233
|
flex-direction: column;
|
|
155
|
-
align-items:
|
|
156
|
-
gap:
|
|
157
|
-
padding-bottom: 4px;
|
|
234
|
+
align-items: stretch;
|
|
235
|
+
gap: 0;
|
|
158
236
|
}
|
|
159
237
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
238
|
+
/* Scoped through the bar so the glyph size outranks the global
|
|
239
|
+
`button:not(.pico-btn) { font-size: 12px }` reset, which is more specific
|
|
240
|
+
than a bare class and was silently winning. */
|
|
241
|
+
.ide-activity-bar .ide-activity-btn {
|
|
242
|
+
width: 100%;
|
|
243
|
+
height: 60px;
|
|
163
244
|
background: transparent;
|
|
164
245
|
border: none;
|
|
246
|
+
/* Always reserve the indicator's width so the glyph does not shift sideways
|
|
247
|
+
by a pixel the moment a panel is opened. */
|
|
248
|
+
border-left: 2px solid transparent;
|
|
165
249
|
border-radius: 0;
|
|
166
250
|
color: var(--ide-text-muted);
|
|
167
251
|
display: flex;
|
|
168
252
|
align-items: center;
|
|
169
253
|
justify-content: center;
|
|
170
254
|
cursor: pointer;
|
|
171
|
-
font-size:
|
|
172
|
-
transition:
|
|
255
|
+
font-size: 25px;
|
|
256
|
+
transition: color 0.12s ease;
|
|
173
257
|
}
|
|
174
258
|
|
|
175
|
-
.
|
|
176
|
-
|
|
259
|
+
/* The glyph carries the state, not a filled box behind it. */
|
|
260
|
+
.ide-activity-bar .ide-activity-btn:hover {
|
|
261
|
+
background: transparent;
|
|
177
262
|
color: var(--ide-text);
|
|
178
263
|
}
|
|
179
264
|
|
|
180
|
-
.ide-activity-btn.active {
|
|
265
|
+
.ide-activity-bar .ide-activity-btn.active {
|
|
181
266
|
color: var(--ide-accent-fg);
|
|
182
|
-
border-left:
|
|
267
|
+
border-left-color: var(--ide-accent-fg);
|
|
183
268
|
}
|
|
184
269
|
|
|
185
270
|
.sidebar-panel-title {
|
|
@@ -300,25 +385,25 @@ html, body, #mbeditor-root {
|
|
|
300
385
|
border: 1px solid var(--ide-accent);
|
|
301
386
|
border-radius: 3px;
|
|
302
387
|
color: var(--ide-text);
|
|
303
|
-
font-size:
|
|
388
|
+
font-size: 14px;
|
|
304
389
|
font-family: inherit;
|
|
305
390
|
padding: 1px 4px;
|
|
306
391
|
outline: none;
|
|
307
|
-
height:
|
|
308
|
-
line-height:
|
|
392
|
+
height: 21px;
|
|
393
|
+
line-height: 21px;
|
|
309
394
|
}
|
|
310
395
|
|
|
311
396
|
.tree-item-icon {
|
|
312
|
-
width:
|
|
313
|
-
margin-right:
|
|
314
|
-
font-size:
|
|
397
|
+
width: 20px;
|
|
398
|
+
margin-right: 6px;
|
|
399
|
+
font-size: 14px;
|
|
315
400
|
flex-shrink: 0;
|
|
316
401
|
text-align: center;
|
|
317
402
|
}
|
|
318
403
|
|
|
319
404
|
.tree-folder-icon { color: #dcb67a; }
|
|
320
405
|
.tree-file-icon { color: #78b4e0; }
|
|
321
|
-
.tree-item-name { flex: 1; overflow: hidden; text-overflow: ellipsis; font-size:
|
|
406
|
+
.tree-item-name { flex: 1; overflow: hidden; text-overflow: ellipsis; font-size: 14px; }
|
|
322
407
|
|
|
323
408
|
/* ── Custom File Icons ───────────────────────────────────── */
|
|
324
409
|
.ruby-icon { color: #f26262 !important; }
|
|
@@ -1412,7 +1497,90 @@ html, body, #mbeditor-root {
|
|
|
1412
1497
|
cursor: pointer;
|
|
1413
1498
|
}
|
|
1414
1499
|
.import-conflict-btn:hover { background: var(--ide-hover-bg, #4a4a4a); }
|
|
1500
|
+
.import-conflict-btn:disabled { opacity: 0.45; cursor: default; }
|
|
1415
1501
|
.import-conflict-btn-danger { border-color: var(--ide-error, #f48771); color: var(--ide-error, #f48771); }
|
|
1502
|
+
.import-conflict-btn-primary {
|
|
1503
|
+
background: var(--ide-accent, #0e639c);
|
|
1504
|
+
border-color: var(--ide-accent, #0e639c);
|
|
1505
|
+
color: #fff;
|
|
1506
|
+
}
|
|
1507
|
+
.import-conflict-btn-primary:hover:not(:disabled) { background: var(--ide-accent-hover, #1177bb); }
|
|
1508
|
+
|
|
1509
|
+
/* Upload dialog — the pointed-at half of drag-and-drop import */
|
|
1510
|
+
.import-dialog { min-width: 460px; max-width: 620px; }
|
|
1511
|
+
.import-dialog .schema-modal-body { padding: 12px 16px; }
|
|
1512
|
+
.import-pick-row { display: flex; gap: 8px; }
|
|
1513
|
+
.import-pick-btn {
|
|
1514
|
+
background: var(--ide-input-bg, #3c3c3c);
|
|
1515
|
+
color: var(--ide-text, #d4d4d4);
|
|
1516
|
+
border: 1px solid var(--ide-border, #3c3c3c);
|
|
1517
|
+
border-radius: 3px;
|
|
1518
|
+
padding: 6px 12px;
|
|
1519
|
+
font-size: 12px;
|
|
1520
|
+
cursor: pointer;
|
|
1521
|
+
}
|
|
1522
|
+
.import-pick-btn:hover { background: var(--ide-hover-bg, #4a4a4a); }
|
|
1523
|
+
.import-pick-btn i { margin-right: 6px; opacity: 0.75; }
|
|
1524
|
+
.import-dialog-empty,
|
|
1525
|
+
.import-dialog-warning {
|
|
1526
|
+
margin-top: 12px;
|
|
1527
|
+
font-size: 12px;
|
|
1528
|
+
color: var(--ide-text-muted, #888);
|
|
1529
|
+
}
|
|
1530
|
+
.import-dialog-warning { color: var(--ide-warning, #cca700); }
|
|
1531
|
+
.import-dialog-section-title {
|
|
1532
|
+
margin: 16px 0 6px;
|
|
1533
|
+
font-size: 10px;
|
|
1534
|
+
font-weight: 600;
|
|
1535
|
+
text-transform: uppercase;
|
|
1536
|
+
letter-spacing: 0.06em;
|
|
1537
|
+
color: var(--ide-text-muted, #888);
|
|
1538
|
+
}
|
|
1539
|
+
.import-dest-options { display: flex; flex-direction: column; gap: 2px; }
|
|
1540
|
+
.import-dest-option {
|
|
1541
|
+
display: flex;
|
|
1542
|
+
align-items: center;
|
|
1543
|
+
gap: 8px;
|
|
1544
|
+
width: 100%;
|
|
1545
|
+
text-align: left;
|
|
1546
|
+
background: none;
|
|
1547
|
+
border: none;
|
|
1548
|
+
border-radius: 3px;
|
|
1549
|
+
padding: 4px 6px;
|
|
1550
|
+
color: var(--ide-text, #d4d4d4);
|
|
1551
|
+
font-size: 12px;
|
|
1552
|
+
cursor: pointer;
|
|
1553
|
+
}
|
|
1554
|
+
.import-dest-option:hover { background: var(--ide-hover, rgba(255,255,255,0.06)); }
|
|
1555
|
+
.import-dest-option.active { background: var(--ide-selection-bg, rgba(14,99,156,0.35)); }
|
|
1556
|
+
.import-dest-radio { opacity: 0.7; font-size: 11px; }
|
|
1557
|
+
.import-dest-label {
|
|
1558
|
+
font-family: var(--ide-mono-font, monospace);
|
|
1559
|
+
overflow: hidden;
|
|
1560
|
+
text-overflow: ellipsis;
|
|
1561
|
+
white-space: nowrap;
|
|
1562
|
+
}
|
|
1563
|
+
.import-dest-hint { color: var(--ide-text-muted, #888); font-size: 11px; margin-left: auto; flex-shrink: 0; }
|
|
1564
|
+
.import-dest-input {
|
|
1565
|
+
width: 100%;
|
|
1566
|
+
margin-top: 8px;
|
|
1567
|
+
box-sizing: border-box;
|
|
1568
|
+
background: var(--ide-input-bg, #3c3c3c);
|
|
1569
|
+
color: var(--ide-text, #d4d4d4);
|
|
1570
|
+
border: 1px solid var(--ide-border, #3c3c3c);
|
|
1571
|
+
border-radius: 3px;
|
|
1572
|
+
padding: 5px 8px;
|
|
1573
|
+
font-family: var(--ide-mono-font, monospace);
|
|
1574
|
+
font-size: 12px;
|
|
1575
|
+
}
|
|
1576
|
+
.import-dest-input:focus { outline: 1px solid var(--ide-accent, #0e639c); }
|
|
1577
|
+
.import-target-replaces {
|
|
1578
|
+
margin-left: 8px;
|
|
1579
|
+
font-size: 10px;
|
|
1580
|
+
text-transform: uppercase;
|
|
1581
|
+
letter-spacing: 0.06em;
|
|
1582
|
+
color: var(--ide-warning, #cca700);
|
|
1583
|
+
}
|
|
1416
1584
|
.schema-table {
|
|
1417
1585
|
width: 100%;
|
|
1418
1586
|
border-collapse: collapse;
|
|
@@ -1969,12 +2137,20 @@ html, body, #mbeditor-root {
|
|
|
1969
2137
|
flex-shrink: 0;
|
|
1970
2138
|
}
|
|
1971
2139
|
|
|
2140
|
+
/* The title yields the header to the action buttons rather than wrapping.
|
|
2141
|
+
*
|
|
2142
|
+
* It used to break-word onto three lines ("SAMPLE_ / WORKSPA / CE") because a
|
|
2143
|
+
* long workspace name plus six buttons genuinely does not fit a 300px sidebar.
|
|
2144
|
+
* flex-shrink against a `flex-shrink: 0` action group means the buttons always
|
|
2145
|
+
* win: full title, then ellipsis, then gone. No JS measuring, and it adapts on
|
|
2146
|
+
* its own to sections with fewer buttons — the Open Editors header keeps its
|
|
2147
|
+
* title at the same width where Explorer has already dropped it. */
|
|
1972
2148
|
.collapsible-title {
|
|
1973
|
-
flex: 1;
|
|
2149
|
+
flex: 1 1 auto;
|
|
1974
2150
|
min-width: 0;
|
|
1975
|
-
|
|
1976
|
-
overflow
|
|
1977
|
-
|
|
2151
|
+
white-space: nowrap;
|
|
2152
|
+
overflow: hidden;
|
|
2153
|
+
text-overflow: ellipsis;
|
|
1978
2154
|
}
|
|
1979
2155
|
|
|
1980
2156
|
.collapsible-actions {
|
|
@@ -1991,6 +2167,45 @@ html, body, #mbeditor-root {
|
|
|
1991
2167
|
gap: 0;
|
|
1992
2168
|
}
|
|
1993
2169
|
|
|
2170
|
+
/* Tooltip for the icon-only sidebar action buttons. See SidebarActionButton
|
|
2171
|
+
* for why this is not a title attribute. Anchored to the button's right edge
|
|
2172
|
+
* because .ide-sidebar-scrollable is overflow-x: hidden — a left-anchored or
|
|
2173
|
+
* centred tooltip on the rightmost button would be clipped by the sidebar. */
|
|
2174
|
+
.sb-tip {
|
|
2175
|
+
position: relative;
|
|
2176
|
+
display: inline-flex;
|
|
2177
|
+
}
|
|
2178
|
+
.sb-tip::after {
|
|
2179
|
+
content: attr(data-tip);
|
|
2180
|
+
position: absolute;
|
|
2181
|
+
top: calc(100% + 4px);
|
|
2182
|
+
right: 0;
|
|
2183
|
+
z-index: 4000;
|
|
2184
|
+
padding: 4px 7px;
|
|
2185
|
+
background: var(--ide-panel-bg, #252526);
|
|
2186
|
+
border: 1px solid var(--ide-border, #3c3c3c);
|
|
2187
|
+
border-radius: 4px;
|
|
2188
|
+
box-shadow: 0 4px 16px rgba(0, 0, 0, 0.4);
|
|
2189
|
+
color: var(--ide-text, #d4d4d4);
|
|
2190
|
+
/* The section header sets uppercase, 600 weight and letter-spacing; a
|
|
2191
|
+
tooltip inheriting those reads as another heading. */
|
|
2192
|
+
font-size: 11px;
|
|
2193
|
+
font-weight: 400;
|
|
2194
|
+
letter-spacing: 0;
|
|
2195
|
+
text-transform: none;
|
|
2196
|
+
white-space: nowrap;
|
|
2197
|
+
opacity: 0;
|
|
2198
|
+
pointer-events: none;
|
|
2199
|
+
transition: opacity 0.08s ease 0.3s;
|
|
2200
|
+
}
|
|
2201
|
+
.sb-tip:hover::after,
|
|
2202
|
+
.sb-tip:focus-within::after {
|
|
2203
|
+
opacity: 1;
|
|
2204
|
+
}
|
|
2205
|
+
@media (prefers-reduced-motion: reduce) {
|
|
2206
|
+
.sb-tip::after { transition: none; }
|
|
2207
|
+
}
|
|
2208
|
+
|
|
1994
2209
|
/* Open-editors panel: height is controlled by the --open-editors-height CSS variable
|
|
1995
2210
|
set inline from React state (default 140px ≈ 5-6 items), drag-resizable via the
|
|
1996
2211
|
.open-editors-resize-handle strip below. */
|
|
@@ -2652,26 +2867,8 @@ button:not(.pico-btn) {
|
|
|
2652
2867
|
font-size: 12px;
|
|
2653
2868
|
}
|
|
2654
2869
|
|
|
2655
|
-
/*
|
|
2656
|
-
*
|
|
2657
|
-
* because Monaco renders them as <a role="button">. Without this reset, Pico
|
|
2658
|
-
* applies its primary-colour background, white text, and large form-element
|
|
2659
|
-
* padding, making those buttons huge and unreadable on certain themes.
|
|
2660
|
-
* Resetting the Pico CSS variables here is lower-impact than fighting
|
|
2661
|
-
* per-property specificity battles with Monaco's --vscode-* rules.
|
|
2662
|
-
*/
|
|
2663
|
-
.monaco-hover [role=button],
|
|
2664
|
-
.action-widget [role=button],
|
|
2665
|
-
.context-view [role=button],
|
|
2666
|
-
.monaco-editor [role=button],
|
|
2667
|
-
.monaco-diff-editor [role=button] {
|
|
2668
|
-
--pico-background-color: transparent;
|
|
2669
|
-
--pico-border-color: transparent;
|
|
2670
|
-
--pico-color: inherit;
|
|
2671
|
-
--pico-box-shadow: none;
|
|
2672
|
-
--pico-form-element-spacing-vertical: 0;
|
|
2673
|
-
--pico-form-element-spacing-horizontal: 0;
|
|
2674
|
-
}
|
|
2870
|
+
/* Pico containment lives at the top of this file — see the block there. It has
|
|
2871
|
+
* to sit above every component rule to stay a floor rather than an override. */
|
|
2675
2872
|
|
|
2676
2873
|
/* Inputs and selects — keep compact but styled */
|
|
2677
2874
|
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]),
|
|
@@ -2998,9 +3195,41 @@ button:not(.pico-btn) { margin-bottom: 0; }
|
|
|
2998
3195
|
color: var(--ide-fg-muted, #ccc);
|
|
2999
3196
|
}
|
|
3000
3197
|
.ide-problems-title { font-weight: 600; }
|
|
3001
|
-
.
|
|
3002
|
-
.
|
|
3198
|
+
/* Severity toggles. Pushed to the right of the summary; the text filter that
|
|
3199
|
+
follows keeps its own left margin off `auto` so the two sit together. */
|
|
3200
|
+
.ide-problems-severity-filter {
|
|
3003
3201
|
margin-left: auto;
|
|
3202
|
+
display: flex;
|
|
3203
|
+
gap: 4px;
|
|
3204
|
+
}
|
|
3205
|
+
.ide-problems-sev-chip {
|
|
3206
|
+
display: inline-flex;
|
|
3207
|
+
align-items: center;
|
|
3208
|
+
gap: 4px;
|
|
3209
|
+
background: transparent;
|
|
3210
|
+
border: 1px solid transparent;
|
|
3211
|
+
border-radius: 3px;
|
|
3212
|
+
padding: 1px 6px;
|
|
3213
|
+
font-size: 11px;
|
|
3214
|
+
font-variant-numeric: tabular-nums;
|
|
3215
|
+
cursor: pointer;
|
|
3216
|
+
/* Off is legible but clearly recessive — a disabled-looking chip reads as
|
|
3217
|
+
"broken" rather than "toggled off". */
|
|
3218
|
+
color: var(--ide-text-muted, #858585);
|
|
3219
|
+
opacity: 0.55;
|
|
3220
|
+
}
|
|
3221
|
+
.ide-problems-sev-chip:hover { opacity: 0.85; }
|
|
3222
|
+
.ide-problems-sev-chip.is-on {
|
|
3223
|
+
opacity: 1;
|
|
3224
|
+
border-color: var(--ide-border, #333);
|
|
3225
|
+
background: var(--ide-input-bg, #2d2d2d);
|
|
3226
|
+
}
|
|
3227
|
+
.ide-problems-sev-chip.is-on.ide-problems-sev-error { color: var(--ide-error, #f14c4c); }
|
|
3228
|
+
.ide-problems-sev-chip.is-on.ide-problems-sev-warning { color: var(--ide-warning, #cca700); }
|
|
3229
|
+
.ide-problems-sev-chip.is-on.ide-problems-sev-info { color: var(--ide-info, #3794ff); }
|
|
3230
|
+
|
|
3231
|
+
.ide-problems-filter {
|
|
3232
|
+
margin-left: 8px;
|
|
3004
3233
|
background: var(--ide-input-bg, #2d2d2d);
|
|
3005
3234
|
color: var(--ide-fg, #eee);
|
|
3006
3235
|
border: 1px solid var(--ide-border, #333);
|
|
@@ -3017,6 +3246,24 @@ button:not(.pico-btn) { margin-bottom: 0; }
|
|
|
3017
3246
|
padding: 2px 6px;
|
|
3018
3247
|
}
|
|
3019
3248
|
.ide-problems-btn:hover { color: var(--ide-fg, #fff); }
|
|
3249
|
+
.ide-problems-btn:disabled { opacity: 0.4; cursor: default; }
|
|
3250
|
+
.ide-problems-btn:disabled:hover { color: var(--ide-fg-muted, #ccc); }
|
|
3251
|
+
.ide-problems-actions { display: flex; gap: 2px; }
|
|
3252
|
+
.ide-problems-actions .ide-problems-btn { white-space: nowrap; }
|
|
3253
|
+
.ide-problems-btn.is-on { color: var(--ide-accent, #0a84ff); }
|
|
3254
|
+
.ide-testrun-stat { font-size: 11px; color: var(--ide-text-muted, #858585); }
|
|
3255
|
+
.ide-testrun-pass { color: #4ec9b0; }
|
|
3256
|
+
.ide-testrun-fail { color: #f14c4c; }
|
|
3257
|
+
.ide-testrun-raw {
|
|
3258
|
+
margin: 0;
|
|
3259
|
+
padding: 8px 14px;
|
|
3260
|
+
font-family: var(--ide-mono, monospace);
|
|
3261
|
+
font-size: 11px;
|
|
3262
|
+
line-height: 1.5;
|
|
3263
|
+
white-space: pre;
|
|
3264
|
+
overflow-x: auto;
|
|
3265
|
+
color: var(--ide-fg, #eee);
|
|
3266
|
+
}
|
|
3020
3267
|
.ide-problems-body { flex: 1; overflow: auto; padding: 4px 0; font-size: 12px; }
|
|
3021
3268
|
.ide-problems-empty {
|
|
3022
3269
|
padding: 10px 14px;
|
|
@@ -19,6 +19,13 @@ module Mbeditor
|
|
|
19
19
|
# True when the connection is allowed (or no hook is configured); otherwise
|
|
20
20
|
# rejects the subscription and returns false.
|
|
21
21
|
def mbeditor_authenticated?
|
|
22
|
+
# The same gate the controllers apply. Without it the cable channels were
|
|
23
|
+
# reachable in any environment, hook or no hook.
|
|
24
|
+
unless Array(Mbeditor.configuration.allowed_environments).map(&:to_sym).include?(Rails.env.to_sym)
|
|
25
|
+
mbeditor_reject_subscription
|
|
26
|
+
return false
|
|
27
|
+
end
|
|
28
|
+
|
|
22
29
|
hook = mbeditor_auth_hook
|
|
23
30
|
return true unless hook
|
|
24
31
|
|
|
@@ -50,8 +50,10 @@ module Mbeditor
|
|
|
50
50
|
def save_state(data)
|
|
51
51
|
state = data["state"] || data
|
|
52
52
|
EditorStateService.new(workspace_root).write_state(state)
|
|
53
|
-
rescue StandardError
|
|
54
|
-
# Never let a state-save failure crash the WebSocket connection
|
|
53
|
+
rescue StandardError => e
|
|
54
|
+
# Never let a state-save failure crash the WebSocket connection — but a
|
|
55
|
+
# silently dropped save looked exactly like a working one.
|
|
56
|
+
Rails.logger.warn("[mbeditor] EditorChannel#save_state failed: #{e.class}: #{e.message}")
|
|
55
57
|
end
|
|
56
58
|
|
|
57
59
|
def save_branch_state(data)
|
|
@@ -62,8 +64,9 @@ module Mbeditor
|
|
|
62
64
|
# A misbehaving client sent a malformed branch name. Don't crash the
|
|
63
65
|
# connection, but log it so the misconfiguration is observable.
|
|
64
66
|
Rails.logger.warn("[mbeditor] EditorChannel#save_branch_state: rejected invalid branch name #{branch.inspect}")
|
|
65
|
-
rescue StandardError
|
|
67
|
+
rescue StandardError => e
|
|
66
68
|
# Never let a state-save failure crash the WebSocket connection
|
|
69
|
+
Rails.logger.warn("[mbeditor] EditorChannel#save_branch_state failed: #{e.class}: #{e.message}")
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
def start_log_tail(data)
|
|
@@ -6,6 +6,11 @@ require "pathname"
|
|
|
6
6
|
module Mbeditor
|
|
7
7
|
class ApplicationController < ActionController::Base
|
|
8
8
|
protect_from_forgery with: :exception
|
|
9
|
+
# Before the auth hook, and on every controller: a disallowed environment is
|
|
10
|
+
# "this engine is not here", so it must 404 without running the host app's
|
|
11
|
+
# authenticate_with proc. Declaring it per-controller meant LogsController
|
|
12
|
+
# missed it entirely and served the environment's log file anywhere.
|
|
13
|
+
before_action :ensure_allowed_environment!
|
|
9
14
|
before_action :run_authentication
|
|
10
15
|
|
|
11
16
|
private
|