mbeditor 0.5.3 → 0.7.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 (54) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +77 -0
  3. data/README.md +7 -0
  4. data/app/assets/javascripts/mbeditor/application.js +3 -0
  5. data/app/assets/javascripts/mbeditor/components/ChangelogView.js +145 -0
  6. data/app/assets/javascripts/mbeditor/components/DiffViewer.js +1 -1
  7. data/app/assets/javascripts/mbeditor/components/EditorPanel.js +359 -31
  8. data/app/assets/javascripts/mbeditor/components/FileTree.js +177 -116
  9. data/app/assets/javascripts/mbeditor/components/MbeditorApp.js +952 -143
  10. data/app/assets/javascripts/mbeditor/components/TabBar.js +9 -0
  11. data/app/assets/javascripts/mbeditor/conflict_parser.js +48 -0
  12. data/app/assets/javascripts/mbeditor/editor_plugins.js +420 -67
  13. data/app/assets/javascripts/mbeditor/editor_store.js +1 -0
  14. data/app/assets/javascripts/mbeditor/file_service.js +34 -6
  15. data/app/assets/javascripts/mbeditor/git_service.js +2 -1
  16. data/app/assets/javascripts/mbeditor/history_service.js +177 -0
  17. data/app/assets/javascripts/mbeditor/search_service.js +1 -0
  18. data/app/assets/javascripts/mbeditor/tab_manager.js +8 -5
  19. data/app/assets/stylesheets/mbeditor/application.css +112 -0
  20. data/app/assets/stylesheets/mbeditor/editor.css +443 -78
  21. data/app/channels/mbeditor/editor_channel.rb +5 -41
  22. data/app/controllers/mbeditor/application_controller.rb +8 -1
  23. data/app/controllers/mbeditor/editors_controller.rb +276 -654
  24. data/app/controllers/mbeditor/git_controller.rb +2 -61
  25. data/app/services/mbeditor/availability_probe.rb +83 -0
  26. data/app/services/mbeditor/code_search_service.rb +42 -0
  27. data/app/services/mbeditor/editor_state_service.rb +91 -0
  28. data/app/services/mbeditor/exclusion_matcher.rb +23 -0
  29. data/app/services/mbeditor/file_operation_service.rb +68 -0
  30. data/app/services/mbeditor/file_tree_service.rb +69 -0
  31. data/app/services/mbeditor/git_combined_diff_service.rb +43 -0
  32. data/app/services/mbeditor/git_commit_detail_service.rb +46 -0
  33. data/app/services/mbeditor/git_info_service.rb +151 -0
  34. data/app/services/mbeditor/git_service.rb +36 -26
  35. data/app/services/mbeditor/js_definition_service.rb +59 -0
  36. data/app/services/mbeditor/js_members_service.rb +62 -0
  37. data/app/services/mbeditor/process_runner.rb +48 -0
  38. data/app/services/mbeditor/rails_related_files_service.rb +282 -0
  39. data/app/services/mbeditor/ruby_definition_service.rb +77 -101
  40. data/app/services/mbeditor/schema_service.rb +270 -0
  41. data/app/services/mbeditor/search_replace_service.rb +184 -0
  42. data/app/services/mbeditor/test_runner_service.rb +5 -27
  43. data/app/views/layouts/mbeditor/application.html.erb +2 -2
  44. data/config/routes.rb +8 -1
  45. data/lib/mbeditor/configuration.rb +4 -2
  46. data/lib/mbeditor/version.rb +1 -1
  47. data/public/monaco-editor/vs/language/css/cssMode.js +13 -0
  48. data/public/monaco-editor/vs/language/css/cssWorker.js +77 -0
  49. data/public/monaco-editor/vs/language/html/htmlMode.js +13 -0
  50. data/public/monaco-editor/vs/language/html/htmlWorker.js +454 -0
  51. data/public/monaco-editor/vs/language/json/jsonMode.js +19 -0
  52. data/public/monaco-editor/vs/language/json/jsonWorker.js +42 -0
  53. metadata +26 -3
  54. data/app/services/mbeditor/unused_methods_service.rb +0 -139
@@ -8,6 +8,19 @@
8
8
  box-shadow: none !important;
9
9
  }
10
10
 
11
+ /* Prevent Pico CSS from applying its primary-colour focus/active state to IDE buttons.
12
+ Pico sets --pico-background-color and --pico-border-color to its primary blue on
13
+ button:focus, which bleeds through as a blue tint on any button not explicitly
14
+ overriding its background with a literal value. */
15
+ #mbeditor-root button:not(.pico-btn):focus,
16
+ #mbeditor-root button:not(.pico-btn):focus-visible,
17
+ #mbeditor-root button:not(.pico-btn):active {
18
+ --pico-background-color: transparent;
19
+ --pico-border-color: transparent;
20
+ --pico-box-shadow: none;
21
+ --pico-color: inherit;
22
+ }
23
+
11
24
  html, body, #mbeditor-root {
12
25
  margin: 0; padding: 0;
13
26
  width: 100%; height: 100%;
@@ -58,33 +71,30 @@ html, body, #mbeditor-root {
58
71
 
59
72
  /* ── Sidebar ─────────────────────────────────────────────── */
60
73
  .ide-sidebar {
61
- width: 280px;
62
74
  min-width: 280px;
63
75
  max-width: 560px;
64
76
  background: var(--ide-panel-bg);
65
- border-right: 1px solid var(--ide-border);
66
77
  display: flex;
67
78
  flex-direction: column;
68
79
  overflow: hidden;
69
80
  flex-shrink: 0;
70
- transition: width 0.15s ease;
71
- }
72
-
73
- .ide-sidebar.ide-sidebar-collapsed {
74
- min-width: 0;
75
- max-width: none;
76
81
  }
77
82
 
78
- /* ── Collapsed sidebar icon strip ──────────────────────────── */
79
- .sidebar-icon-strip {
83
+ /* ── Activity Bar (VSCode-style, always visible) ───────── */
84
+ .ide-activity-bar {
85
+ width: 48px;
86
+ min-width: 48px;
87
+ flex-shrink: 0;
88
+ background: var(--ide-panel-bg);
89
+ border-right: 1px solid var(--ide-border);
80
90
  display: flex;
81
91
  flex-direction: column;
82
92
  align-items: center;
83
- height: 100%;
84
93
  padding: 4px 0;
94
+ z-index: 10;
85
95
  }
86
96
 
87
- .sidebar-strip-top {
97
+ .ide-activity-bar-top {
88
98
  display: flex;
89
99
  flex-direction: column;
90
100
  align-items: center;
@@ -92,7 +102,7 @@ html, body, #mbeditor-root {
92
102
  flex: 1;
93
103
  }
94
104
 
95
- .sidebar-strip-bottom {
105
+ .ide-activity-bar-bottom {
96
106
  display: flex;
97
107
  flex-direction: column;
98
108
  align-items: center;
@@ -100,7 +110,7 @@ html, body, #mbeditor-root {
100
110
  padding-bottom: 4px;
101
111
  }
102
112
 
103
- .sidebar-strip-btn {
113
+ .ide-activity-btn {
104
114
  width: 36px;
105
115
  height: 36px;
106
116
  background: transparent;
@@ -115,25 +125,28 @@ html, body, #mbeditor-root {
115
125
  transition: background 0.12s ease, color 0.12s ease;
116
126
  }
117
127
 
118
- .sidebar-strip-btn:hover {
128
+ .ide-activity-btn:hover {
119
129
  background: var(--ide-hover-bg);
120
130
  color: var(--ide-text);
121
131
  }
122
132
 
123
- .sidebar-strip-btn.active {
133
+ .ide-activity-btn.active {
124
134
  color: var(--ide-accent-fg);
125
135
  border-left: 2px solid var(--ide-accent-fg);
126
136
  }
127
137
 
128
- /* Visually groups Explorer + Search + Settings buttons in the collapsed strip */
129
- .sidebar-nav-group {
130
- display: flex;
131
- flex-direction: column;
132
- border: 1px solid var(--ide-border);
133
- border-radius: 4px;
134
- overflow: hidden;
138
+ .sidebar-panel-title {
139
+ font-size: 11px;
140
+ font-weight: 700;
141
+ text-transform: uppercase;
142
+ letter-spacing: 0.08em;
143
+ color: var(--ide-fg-muted, var(--ide-text-muted, #888));
144
+ padding: 10px 12px 6px;
145
+ flex-shrink: 0;
146
+ border-bottom: 1px solid var(--ide-border);
135
147
  }
136
148
 
149
+
137
150
  .panel-divider {
138
151
  flex-shrink: 0;
139
152
  cursor: col-resize;
@@ -146,13 +159,6 @@ html, body, #mbeditor-root {
146
159
  border-right: 1px solid var(--ide-border);
147
160
  }
148
161
 
149
- .ide-sidebar-collapsed + .sidebar-divider {
150
- cursor: default;
151
- }
152
-
153
- .ide-sidebar-collapsed + .sidebar-divider:hover {
154
- background: var(--ide-panel-bg-alt);
155
- }
156
162
 
157
163
  .pane-divider {
158
164
  width: 4px;
@@ -179,33 +185,6 @@ html, body, #mbeditor-root {
179
185
  gap: 6px;
180
186
  }
181
187
 
182
- .ide-sidebar-tabs {
183
- display: flex;
184
- border-bottom: 1px solid var(--ide-border);
185
- flex-shrink: 0;
186
- }
187
-
188
- .ide-sidebar-tab {
189
- flex: 1;
190
- padding: 5px 0;
191
- font-size: 11px;
192
- text-align: center;
193
- cursor: pointer;
194
- color: var(--ide-text-muted);
195
- border: none;
196
- border-radius: 0;
197
- background: transparent;
198
- transition: color 0.15s, background 0.15s;
199
- }
200
-
201
- .ide-sidebar-tab.ide-sidebar-tab-icon {
202
- flex: 0 0 auto;
203
- padding: 5px 10px;
204
- font-size: 14px;
205
- }
206
-
207
- .ide-sidebar-tab:hover { color: var(--ide-text); background: var(--ide-input-bg); }
208
- .ide-sidebar-tab.active { color: var(--ide-accent-fg); border-bottom: 2px solid var(--ide-accent-fg); }
209
188
 
210
189
  .ide-sidebar-content {
211
190
  flex: 1;
@@ -319,6 +298,19 @@ html, body, #mbeditor-root {
319
298
  box-shadow: inset 0 0 0 1px rgba(78, 201, 176, 0.35);
320
299
  }
321
300
 
301
+ .branch-switch-overlay {
302
+ position: absolute;
303
+ inset: 0;
304
+ background: rgba(30, 30, 30, 0.8);
305
+ display: flex;
306
+ align-items: center;
307
+ justify-content: center;
308
+ color: #ccc;
309
+ font-size: 13px;
310
+ z-index: 100;
311
+ pointer-events: none;
312
+ }
313
+
322
314
  /* ── Tab Bar ─────────────────────────────────────────────── */
323
315
  .tab-bar {
324
316
  display: flex;
@@ -827,6 +819,11 @@ html, body, #mbeditor-root {
827
819
  font-size: 10px;
828
820
  white-space: nowrap;
829
821
  padding-left: 8px;
822
+ cursor: pointer;
823
+ }
824
+ .statusbar-version:hover {
825
+ color: var(--ide-accent-text);
826
+ background: color-mix(in srgb, var(--ide-accent-text) 15%, transparent);
830
827
  }
831
828
 
832
829
  .statusbar-eol-btn {
@@ -861,7 +858,7 @@ html, body, #mbeditor-root {
861
858
  }
862
859
 
863
860
  .search-input-shell {
864
- flex-shrink: 0;
861
+ width: 100%;
865
862
  position: relative;
866
863
  display: flex;
867
864
  align-items: center;
@@ -1069,6 +1066,368 @@ html, body, #mbeditor-root {
1069
1066
  gap: 6px;
1070
1067
  }
1071
1068
 
1069
+ /* ── Rails Panel ─────────────────────────────────────────── */
1070
+ .rails-panel {
1071
+ display: flex;
1072
+ flex-direction: column;
1073
+ overflow-y: auto;
1074
+ flex: 1;
1075
+ padding: 8px 0;
1076
+ }
1077
+
1078
+ .rails-panel-loading,
1079
+ .rails-panel-empty {
1080
+ padding: 12px 16px;
1081
+ color: var(--ide-text-muted);
1082
+ font-size: 12px;
1083
+ }
1084
+
1085
+ .rails-group {
1086
+ margin-bottom: 4px;
1087
+ }
1088
+
1089
+ .rails-group-title {
1090
+ padding: 4px 12px 2px;
1091
+ font-size: 10px;
1092
+ font-weight: 700;
1093
+ letter-spacing: 0.08em;
1094
+ text-transform: uppercase;
1095
+ color: var(--ide-text-muted);
1096
+ user-select: none;
1097
+ }
1098
+
1099
+ .rails-group-item {
1100
+ display: flex;
1101
+ align-items: center;
1102
+ gap: 6px;
1103
+ padding: 2px 12px 2px 16px;
1104
+ cursor: pointer;
1105
+ font-size: 12px;
1106
+ color: var(--ide-text);
1107
+ white-space: nowrap;
1108
+ overflow: hidden;
1109
+ text-overflow: ellipsis;
1110
+ }
1111
+
1112
+ .rails-group-item:hover {
1113
+ background: var(--ide-hover-bg);
1114
+ }
1115
+
1116
+ .rails-group-item-name {
1117
+ overflow: hidden;
1118
+ text-overflow: ellipsis;
1119
+ white-space: nowrap;
1120
+ flex: 1;
1121
+ min-width: 0;
1122
+ }
1123
+
1124
+ .rails-group-item-dirty {
1125
+ flex-shrink: 0;
1126
+ font-size: 8px;
1127
+ color: #e5c07b;
1128
+ line-height: 1;
1129
+ }
1130
+
1131
+ .rails-group-item-kind {
1132
+ font-size: 10px;
1133
+ color: #888;
1134
+ margin-left: auto;
1135
+ padding-right: 4px;
1136
+ white-space: nowrap;
1137
+ flex-shrink: 0;
1138
+ }
1139
+
1140
+ .rails-panel-overflow {
1141
+ padding: 4px 12px 8px;
1142
+ font-size: 11px;
1143
+ color: var(--ide-text-muted, #888);
1144
+ font-style: italic;
1145
+ }
1146
+
1147
+ /* ── Rails schema button (in CollapsibleSection actions) ──── */
1148
+ .rails-schema-btn {
1149
+ display: inline-flex;
1150
+ align-items: center;
1151
+ justify-content: center;
1152
+ width: 20px;
1153
+ height: 20px;
1154
+ border: none;
1155
+ background: transparent;
1156
+ color: var(--ide-text-muted, #888);
1157
+ cursor: pointer;
1158
+ border-radius: 3px;
1159
+ padding: 0;
1160
+ font-size: 11px;
1161
+ opacity: 0;
1162
+ transition: opacity 0.1s, color 0.1s;
1163
+ }
1164
+ .collapsible-header:hover .rails-schema-btn,
1165
+ .rails-schema-btn-loading {
1166
+ opacity: 1;
1167
+ }
1168
+ .rails-schema-btn:hover {
1169
+ color: var(--ide-text, #ccc);
1170
+ background: var(--ide-hover, rgba(255,255,255,0.08));
1171
+ }
1172
+
1173
+ /* ── Schema modal ─────────────────────────────────────────── */
1174
+ .schema-modal-overlay {
1175
+ position: fixed;
1176
+ inset: 0;
1177
+ background: rgba(0,0,0,0.55);
1178
+ z-index: 9000;
1179
+ display: flex;
1180
+ align-items: center;
1181
+ justify-content: center;
1182
+ }
1183
+ .schema-modal {
1184
+ background: var(--ide-bg, #1e1e1e);
1185
+ border: 1px solid var(--ide-border, #3c3c3c);
1186
+ border-radius: 6px;
1187
+ min-width: 420px;
1188
+ max-width: 680px;
1189
+ max-height: 80vh;
1190
+ display: flex;
1191
+ flex-direction: column;
1192
+ box-shadow: 0 8px 32px rgba(0,0,0,0.5);
1193
+ overflow: hidden;
1194
+ }
1195
+ .schema-modal-header {
1196
+ display: flex;
1197
+ align-items: center;
1198
+ justify-content: space-between;
1199
+ padding: 12px 16px;
1200
+ border-bottom: 1px solid var(--ide-border, #3c3c3c);
1201
+ flex-shrink: 0;
1202
+ }
1203
+ .schema-modal-title {
1204
+ font-size: 13px;
1205
+ font-weight: 600;
1206
+ color: var(--ide-text, #ccc);
1207
+ display: flex;
1208
+ align-items: center;
1209
+ gap: 4px;
1210
+ }
1211
+ .schema-modal-table-name {
1212
+ font-size: 11px;
1213
+ font-weight: 400;
1214
+ color: var(--ide-text-muted, #888);
1215
+ margin-left: 8px;
1216
+ font-family: monospace;
1217
+ background: var(--ide-hover, rgba(255,255,255,0.06));
1218
+ padding: 1px 6px;
1219
+ border-radius: 3px;
1220
+ }
1221
+ .schema-modal-close {
1222
+ background: transparent;
1223
+ border: none;
1224
+ color: var(--ide-text-muted, #888);
1225
+ cursor: pointer;
1226
+ padding: 2px 6px;
1227
+ border-radius: 3px;
1228
+ font-size: 13px;
1229
+ }
1230
+ .schema-modal-close:hover { color: var(--ide-text, #ccc); background: var(--ide-hover, rgba(255,255,255,0.08)); }
1231
+ .schema-modal-body {
1232
+ overflow-y: auto;
1233
+ padding: 0 0 12px;
1234
+ flex: 1;
1235
+ }
1236
+ .schema-table {
1237
+ width: 100%;
1238
+ border-collapse: collapse;
1239
+ font-size: 12px;
1240
+ }
1241
+ .schema-table th {
1242
+ text-align: left;
1243
+ padding: 6px 16px;
1244
+ font-size: 10px;
1245
+ font-weight: 600;
1246
+ text-transform: uppercase;
1247
+ letter-spacing: 0.06em;
1248
+ color: var(--ide-text-muted, #888);
1249
+ border-bottom: 1px solid var(--ide-border, #3c3c3c);
1250
+ background: var(--ide-sidebar-bg, #252526);
1251
+ }
1252
+ .schema-table td {
1253
+ padding: 5px 16px;
1254
+ border-bottom: 1px solid rgba(255,255,255,0.04);
1255
+ vertical-align: middle;
1256
+ }
1257
+ .schema-table tr:last-child td { border-bottom: none; }
1258
+ .schema-table tr:hover td { background: var(--ide-hover, rgba(255,255,255,0.04)); }
1259
+ .schema-col-name {
1260
+ font-family: monospace;
1261
+ font-size: 12px;
1262
+ color: var(--ide-text, #d4d4d4);
1263
+ white-space: nowrap;
1264
+ }
1265
+ .schema-col-type {
1266
+ font-family: monospace;
1267
+ font-size: 11px;
1268
+ white-space: nowrap;
1269
+ }
1270
+ /* type colouring */
1271
+ .schema-type-string, .schema-type-text, .schema-type-citext { color: #ce9178; }
1272
+ .schema-type-integer, .schema-type-bigint, .schema-type-decimal, .schema-type-float, .schema-type-numeric { color: #b5cea8; }
1273
+ .schema-type-boolean { color: #569cd6; }
1274
+ .schema-type-datetime, .schema-type-date, .schema-type-time, .schema-type-timestamp { color: #9cdcfe; }
1275
+ .schema-type-json, .schema-type-jsonb { color: #dcdcaa; }
1276
+ .schema-col-opts {
1277
+ font-size: 11px;
1278
+ color: var(--ide-text-muted, #888);
1279
+ white-space: nowrap;
1280
+ }
1281
+ .schema-indexes {
1282
+ padding: 0 16px;
1283
+ margin-top: 4px;
1284
+ }
1285
+ .schema-indexes-header {
1286
+ font-size: 10px;
1287
+ font-weight: 600;
1288
+ text-transform: uppercase;
1289
+ letter-spacing: 0.06em;
1290
+ color: var(--ide-text-muted, #888);
1291
+ padding: 8px 0 4px;
1292
+ border-top: 1px solid var(--ide-border, #3c3c3c);
1293
+ }
1294
+ .schema-index-row {
1295
+ display: flex;
1296
+ align-items: center;
1297
+ gap: 8px;
1298
+ padding: 3px 0;
1299
+ font-size: 11px;
1300
+ font-family: monospace;
1301
+ color: var(--ide-text-muted, #999);
1302
+ }
1303
+ .schema-index-cols { flex: 1; }
1304
+ .schema-index-unique {
1305
+ font-size: 9px;
1306
+ font-weight: 600;
1307
+ text-transform: uppercase;
1308
+ letter-spacing: 0.05em;
1309
+ color: #569cd6;
1310
+ background: rgba(86,156,214,0.12);
1311
+ padding: 1px 5px;
1312
+ border-radius: 3px;
1313
+ font-family: sans-serif;
1314
+ }
1315
+ .schema-index-name {
1316
+ font-size: 10px;
1317
+ color: var(--ide-text-muted, #666);
1318
+ opacity: 0.7;
1319
+ }
1320
+ .schema-modal-error {
1321
+ padding: 24px 20px;
1322
+ color: var(--ide-text-muted, #888);
1323
+ font-size: 12px;
1324
+ display: flex;
1325
+ align-items: center;
1326
+ }
1327
+
1328
+ /* ── Changelog tab ────────────────────────────────────────── */
1329
+ .changelog-view {
1330
+ height: 100%;
1331
+ overflow: hidden;
1332
+ display: flex;
1333
+ flex-direction: column;
1334
+ background: var(--ide-bg, #1e1e1e);
1335
+ }
1336
+ .changelog-body {
1337
+ flex: 1;
1338
+ overflow-y: auto;
1339
+ padding: 32px 40px 48px;
1340
+ max-width: 800px;
1341
+ margin: 0 auto;
1342
+ width: 100%;
1343
+ box-sizing: border-box;
1344
+ }
1345
+ .changelog-loading,
1346
+ .changelog-error {
1347
+ display: flex;
1348
+ align-items: center;
1349
+ justify-content: center;
1350
+ height: 100%;
1351
+ color: var(--ide-text-muted, #888);
1352
+ font-size: 13px;
1353
+ }
1354
+ .changelog-h1 {
1355
+ font-size: 22px;
1356
+ font-weight: 700;
1357
+ color: var(--ide-text, #d4d4d4);
1358
+ margin: 0 0 28px;
1359
+ padding-bottom: 12px;
1360
+ border-bottom: 1px solid var(--ide-border, #3c3c3c);
1361
+ }
1362
+ .changelog-version-row {
1363
+ display: flex;
1364
+ align-items: baseline;
1365
+ gap: 12px;
1366
+ margin: 32px 0 10px;
1367
+ }
1368
+ .changelog-version-badge {
1369
+ font-size: 15px;
1370
+ font-weight: 700;
1371
+ color: var(--ide-accent-text, #4fc1ff);
1372
+ letter-spacing: -0.01em;
1373
+ }
1374
+ .changelog-version-date {
1375
+ font-size: 11px;
1376
+ color: var(--ide-text-muted, #888);
1377
+ }
1378
+ .changelog-section {
1379
+ display: flex;
1380
+ align-items: center;
1381
+ gap: 6px;
1382
+ font-size: 11px;
1383
+ font-weight: 600;
1384
+ text-transform: uppercase;
1385
+ letter-spacing: 0.07em;
1386
+ margin: 18px 0 6px;
1387
+ padding: 3px 0;
1388
+ }
1389
+ .changelog-section i { font-size: 10px; }
1390
+ .changelog-section-added { color: #4ec9b0; }
1391
+ .changelog-section-fixed { color: #569cd6; }
1392
+ .changelog-section-changed { color: #dcdcaa; }
1393
+ .changelog-section-removed { color: #f44747; }
1394
+ .changelog-section-performance { color: #ce9178; }
1395
+ .changelog-section-tests { color: #b5cea8; }
1396
+ .changelog-section-security { color: #d7ba7d; }
1397
+ .changelog-list {
1398
+ margin: 0 0 4px 0;
1399
+ padding-left: 20px;
1400
+ list-style: none;
1401
+ }
1402
+ .changelog-list li {
1403
+ position: relative;
1404
+ padding: 2px 0 2px 4px;
1405
+ font-size: 13px;
1406
+ line-height: 1.6;
1407
+ color: var(--ide-text, #ccc);
1408
+ }
1409
+ .changelog-list li::before {
1410
+ content: '–';
1411
+ position: absolute;
1412
+ left: -12px;
1413
+ color: var(--ide-text-muted, #666);
1414
+ }
1415
+ .changelog-list li strong {
1416
+ color: var(--ide-text, #d4d4d4);
1417
+ font-weight: 600;
1418
+ }
1419
+ .changelog-p {
1420
+ font-size: 12px;
1421
+ color: var(--ide-text-muted, #999);
1422
+ margin: 4px 0;
1423
+ line-height: 1.5;
1424
+ }
1425
+ .changelog-rule {
1426
+ border: none;
1427
+ border-top: 1px solid var(--ide-border, #3c3c3c);
1428
+ margin: 24px 0;
1429
+ }
1430
+
1072
1431
  /* ── Git Status Panel ─────────────────────────────────────── */
1073
1432
  .git-panel { padding: 8px; }
1074
1433
 
@@ -1419,9 +1778,9 @@ html, body, #mbeditor-root {
1419
1778
  .collapsible-title {
1420
1779
  flex: 1;
1421
1780
  min-width: 0;
1422
- overflow: hidden;
1423
- text-overflow: ellipsis;
1424
- white-space: nowrap;
1781
+ word-wrap: break-word;
1782
+ overflow-wrap: break-word;
1783
+ word-break: break-word;
1425
1784
  }
1426
1785
 
1427
1786
  .collapsible-actions {
@@ -1435,7 +1794,7 @@ html, body, #mbeditor-root {
1435
1794
  .project-actions {
1436
1795
  display: inline-flex;
1437
1796
  align-items: center;
1438
- gap: 8px;
1797
+ gap: 0;
1439
1798
  }
1440
1799
 
1441
1800
  /* Open-editors panel: height is controlled by the --open-editors-height CSS variable
@@ -1473,15 +1832,8 @@ html, body, #mbeditor-root {
1473
1832
  }
1474
1833
 
1475
1834
  .open-editors-group-actions {
1476
- opacity: 0;
1477
- pointer-events: none;
1478
- transition: opacity 0.12s ease;
1479
- }
1480
-
1481
- .open-editors-group:hover .open-editors-group-actions,
1482
- .open-editors-group:focus-within .open-editors-group-actions {
1483
1835
  opacity: 1;
1484
- pointer-events: auto;
1836
+ transition: opacity 0.12s ease;
1485
1837
  }
1486
1838
 
1487
1839
  .ide-icon-btn {
@@ -2109,14 +2461,6 @@ textarea {
2109
2461
  align-items: center;
2110
2462
  justify-content: center;
2111
2463
  }
2112
- .sidebar-strip-btn {
2113
- padding: 0;
2114
- margin: 0;
2115
- border: none;
2116
- display: inline-flex;
2117
- align-items: center;
2118
- justify-content: center;
2119
- }
2120
2464
 
2121
2465
  /* Context menu items */
2122
2466
  .context-menu-item {
@@ -2272,3 +2616,24 @@ button:not(.pico-btn) { margin-bottom: 0; }
2272
2616
  .project-actions[role="group"] button {
2273
2617
  --pico-form-element-spacing-horizontal: 0;
2274
2618
  }
2619
+
2620
+ /* Pico also sets width:auto on [role=group] button at (0,1,1), beating our
2621
+ .project-action-btn width:24px at (0,1,0). Use (0,2,0) to enforce dimensions. */
2622
+ .project-actions .project-action-btn {
2623
+ width: 24px;
2624
+ height: 24px;
2625
+ padding: 0;
2626
+ }
2627
+
2628
+ /* Pico applies a blue focus ring to the GROUP container via :has(button:focus),
2629
+ not to the button itself — so our button-level fix never reached it.
2630
+ Kill it here by zeroing the group-level box-shadow custom properties. */
2631
+ .project-actions[role="group"] {
2632
+ --pico-group-box-shadow: none !important;
2633
+ --pico-group-box-shadow-focus-with-button: none !important;
2634
+ box-shadow: none !important;
2635
+ }
2636
+
2637
+ .mbeditor-format-changed {
2638
+ background-color: rgba(80, 200, 100, 0.15);
2639
+ }
@@ -1,15 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "fileutils"
4
3
  require "pathname"
5
4
 
6
5
  module Mbeditor
7
6
  CableBaseClass = defined?(ActionCable::Channel::Base) ? ActionCable::Channel::Base : Object
8
7
 
9
8
  class EditorChannel < CableBaseClass
10
- STATE_MAX_BYTES = 1 * 1024 * 1024
11
- SAFE_BRANCH_NAME = /\A[a-zA-Z0-9._\-\/]+\z/
12
-
13
9
  def subscribed
14
10
  stream_from "mbeditor_editor" if respond_to?(:stream_from)
15
11
  end
@@ -18,48 +14,17 @@ module Mbeditor
18
14
  # no-op
19
15
  end
20
16
 
21
- # Called via WebSocketService.perform('save_state', { state: ... })
22
17
  def save_state(data)
23
- Rails.logger.silence do
24
- payload = (data["state"] || data).to_json
25
- return if payload.bytesize > STATE_MAX_BYTES
26
-
27
- root = workspace_root
28
- path = root.join("tmp", "mbeditor_workspace.json")
29
- FileUtils.mkdir_p(root.join("tmp"))
30
- File.open(path, File::RDWR | File::CREAT) do |f|
31
- f.flock(File::LOCK_EX)
32
- f.truncate(0)
33
- f.rewind
34
- f.write(payload)
35
- end
36
- end
18
+ state = data["state"] || data
19
+ EditorStateService.new(workspace_root).write_state(state)
37
20
  rescue StandardError
38
21
  # Never let a state-save failure crash the WebSocket connection
39
22
  end
40
23
 
41
- # Called via WebSocketService.perform('save_branch_state', { branch: ..., state: ... })
42
24
  def save_branch_state(data)
43
- Rails.logger.silence do
44
- branch = data["branch"].to_s.strip
45
- return unless branch.match?(SAFE_BRANCH_NAME)
46
-
47
- state_data = data["state"]
48
- payload_json = state_data.to_json
49
- return if payload_json.bytesize > STATE_MAX_BYTES
50
-
51
- root = workspace_root
52
- path = root.join("tmp", "mbeditor_branch_states.json")
53
- FileUtils.mkdir_p(root.join("tmp"))
54
- File.open(path, File::RDWR | File::CREAT) do |f|
55
- f.flock(File::LOCK_EX)
56
- existing = f.size > 0 ? JSON.parse(f.read) : {}
57
- existing[branch] = state_data
58
- f.truncate(0)
59
- f.rewind
60
- f.write(existing.to_json)
61
- end
62
- end
25
+ branch = data["branch"].to_s.strip
26
+ state = data["state"]
27
+ EditorStateService.new(workspace_root).write_branch_state(branch, state)
63
28
  rescue StandardError
64
29
  # Never let a state-save failure crash the WebSocket connection
65
30
  end
@@ -70,7 +35,6 @@ module Mbeditor
70
35
  configured = Mbeditor.configuration.workspace_root
71
36
  return Pathname.new(configured.to_s) if configured.present?
72
37
 
73
- # Fall back to git root, same logic as ApplicationController
74
38
  rails_root = Rails.root.to_s
75
39
  out, _err, status = Open3.capture3("git", "-C", rails_root, "rev-parse", "--show-toplevel")
76
40
  Pathname.new(status.success? && out.strip.present? ? out.strip : rails_root)