error redesign
error css fixes css cleanup copy btn in errors
3clyp50 committed
Jan 20, 2026 at 16:04 UTC
f95db44a511868dba28f02742a45f5605f38313a
5 files changed
+272
-26
webui/components/messages/action-buttons/simple-action-buttons.css
+2
-1
@@ -101,7 +101,8 @@
101
/* .kvps-row:hover .action-buttons, */
102
.message-text:hover .action-buttons,
103
.kvps-val:hover .action-buttons,
104
-.message-body:hover > .action-buttons {
104
+.message-body:hover > .action-buttons,
105
+.error-content-inner:hover .action-buttons {
106
display: flex;
107
animation: fadeInAfterDelay 0.3s ease-in-out;
108
animation-delay: 0.3s;
webui/components/messages/process-group/process-group-store.js
+7
@@ -230,6 +230,13 @@ const model = {
230
}
231
step.classList.toggle("step-expanded", shouldExpand);
232
});
233
+
234
+ // Apply to error groups
235
+ const allErrorGroups = document.querySelectorAll(".error-group");
236
+ allErrorGroups.forEach(errorGroup => {
237
+ const shouldExpand = mode === "current" || mode === "expanded";
238
+ errorGroup.classList.toggle("expanded", shouldExpand);
239
+ });
240
}
241
};
242
webui/components/messages/process-group/process-group.css
+2
-2
@@ -210,7 +210,7 @@
210
211
/* ERR - error type (red) */
212
.status-err {
213
- --step-accent: #ef4444;
213
+ --step-accent: var(--color-accent);
214
color: var(--step-accent);
215
}
216
@@ -742,7 +742,7 @@
742
}
743
744
.light-mode .status-err {
745
- --step-accent: #b91c1c;
745
+ --step-accent: var(--color-accent);
746
color: var(--step-accent);
747
}
748
webui/css/messages.css
+141
-13
@@ -272,13 +272,138 @@
272
background-color: transparent;
273
}
274
275
-.message-error {
276
- background-color: rgba(180, 40, 40, 0.25);
277
- border: 1px solid rgba(220, 60, 60, 0.5);
278
- border-radius: 8px !important;
279
- padding: 12px !important;
275
+/* Collapsible Error Group */
276
+
277
+.message-error-group {
278
+ background: transparent;
279
+ border: none !important;
280
+ padding: 0 !important;
281
+}
282
+
283
+.error-group {
284
+ display: inline-flex;
285
+ flex-direction: column;
286
+ position: relative;
287
+ z-index: 1;
288
+ margin: var(--spacing-xs) 0;
289
+ padding: var(--spacing-xs) 0;
290
+ min-width: 200px;
291
+ max-width: 100%;
292
+ box-sizing: border-box;
293
+ flex-shrink: 0;
294
+}
295
+
296
+/* Error Group Header */
297
+.error-group-header {
298
+ display: flex;
299
+ align-items: center;
300
+ padding: 0;
301
+ cursor: pointer;
302
+ user-select: none;
303
+ transition: opacity 0.15s ease;
304
+ min-height: 22px;
305
+ gap: 6px;
306
+ white-space: nowrap;
307
+}
308
+
309
+.error-group-header:hover {
310
+ opacity: 0.85;
311
+}
312
+
313
+/* Expand/collapse triangle */
314
+.error-group-header .expand-icon {
315
+ display: inline-block;
316
+ width: 0;
317
+ height: 0;
318
+ border-style: solid;
319
+ border-width: 4px 0 4px 6px;
320
+ border-color: transparent transparent transparent #ef4444;
321
+ opacity: 0.7;
322
+ transition: transform 0.2s ease, opacity 0.15s ease;
323
+ flex-shrink: 0;
324
+ font-size: 0;
325
+ margin-right: 2px;
326
+}
327
+
328
+.error-group-header:hover .expand-icon {
329
+ opacity: 1;
330
+}
331
+
332
+.error-group.expanded .error-group-header .expand-icon {
333
+ transform: rotate(90deg);
334
+}
335
+
336
+/* Error title */
337
+.error-group-header .error-title {
338
+ flex: 0 0 auto;
339
+ font-size: var(--font-size-medium);
340
+ font-weight: 600;
341
+ color: var(--color-accent);
342
+ opacity: 0.95;
343
+}
344
+
345
+/* Error subtitle (short description) */
346
+.error-group-header .error-subtitle {
347
+ flex: 1 1 auto;
348
+ font-size: 0.75rem;
349
+ color: var(--color-text);
350
+ opacity: 0.7;
351
+ white-space: nowrap;
352
+ overflow: hidden;
353
+ text-overflow: ellipsis;
354
+ margin-left: var(--spacing-xs);
355
+ font-family: var(--font-family-code);
356
+}
357
+
358
+/* Error Group Content - Animated expand/collapse */
359
+.error-group-content {
360
+ display: grid;
361
+ grid-template-rows: 0fr;
362
+ opacity: 0;
363
+ margin-top: 0;
364
+ padding-top: 0;
365
+ transition: grid-template-rows 0.25s ease-out,
366
+ opacity 0.2s ease-out,
367
+ margin-top 0.25s ease-out,
368
+ padding-top 0.25s ease-out;
369
+ overflow: hidden;
370
+}
371
+
372
+.error-group-content > .error-content-inner {
373
+ min-height: 0;
374
+}
375
+
376
+.error-group.expanded .error-group-content {
377
+ grid-template-rows: 1fr;
378
+ opacity: 1;
379
+ margin-top: var(--spacing-xs);
380
+ padding-top: var(--spacing-xs);
381
+}
382
+
383
+.error-content-inner {
384
+ padding: var(--spacing-md);
385
+ border-radius: 8px;
386
+ border: 1px solid var(--color-accent);
387
+ margin-left: var(--spacing-md);
388
}
389
390
+/* Callstack styling */
391
+.error-callstack {
392
+ margin: 0;
393
+ white-space: pre-wrap;
394
+ word-break: break-word;
395
+ font-family: var(--font-family-code);
396
+ font-size: 0.72rem;
397
+ line-height: 1.5;
398
+ color: var(--color-accent);
399
+ opacity: 0.9;
400
+ max-height: 30vh;
401
+ overflow-y: auto;
402
+ scrollbar-width: thin;
403
+ scrollbar-color: var(--color-accent) transparent;
404
+}
405
+
406
+
407
/* Terminal styling moved to new terminal block above */
408
409
/* Agent and AI Info */
@@ -497,10 +622,17 @@
622
border-bottom: none;
623
}
624
500
-.light-mode .message-error {
501
- background-color: rgba(220, 60, 60, 0.15);
502
- border: 1px solid rgba(180, 40, 40, 0.4);
503
- color: #8f1010;
625
+/* Light mode error group */
626
+.light-mode .error-group-header .expand-icon {
627
+ border-color: transparent transparent transparent var(--color-accent);
628
+}
629
+
630
+.light-mode .error-group-header .error-title {
631
+ color: var(--color-accent);
632
+}
633
+
634
+.light-mode .error-content-inner {
635
+ border: 1px solid var(--color-accent);
636
}
637
638
.light-mode .message-user {
@@ -636,10 +768,6 @@
768
margin-bottom:5em;
769
}
770
639
-.message-group-mid {
640
- margin-left: 2em;
641
-}
642
-
771
.message-container {
772
animation: fadeIn 0.2s;
773
-webkit-animation: fadeIn 0.2s;
webui/js/messages.js
+120
-10
@@ -789,16 +789,126 @@ export function drawMessageError(
789
temp,
790
kvps = null
791
) {
792
- return drawMessageAgentPlain(
793
- "message-error",
794
- messageContainer,
795
- id,
796
- type,
797
- heading,
798
- content,
799
- temp,
800
- kvps
801
- );
792
+ // Create or get the message div
793
+ let messageDiv = messageContainer.querySelector(".message");
794
+ if (!messageDiv) {
795
+ messageDiv = document.createElement("div");
796
+ messageDiv.classList.add("message", "message-error-group");
797
+ messageContainer.appendChild(messageDiv);
798
+ }
799
+
800
+ // Check if error group already exists
801
+ let errorGroup = messageDiv.querySelector(".error-group");
802
+ if (!errorGroup) {
803
+ errorGroup = document.createElement("div");
804
+ errorGroup.classList.add("error-group");
805
+ errorGroup.setAttribute("data-error-id", id);
806
+
807
+ // Create header (clickable for expand/collapse)
808
+ const header = document.createElement("div");
809
+ header.classList.add("error-group-header");
810
+
811
+ // Expand icon (triangle)
812
+ const expandIcon = document.createElement("span");
813
+ expandIcon.classList.add("expand-icon");
814
+ header.appendChild(expandIcon);
815
+
816
+ // Status badge (before title)
817
+ const badge = document.createElement("span");
818
+ badge.classList.add("status-badge", "status-err");
819
+ badge.textContent = "ERR";
820
+ header.appendChild(badge);
821
+
822
+ // Title
823
+ const title = document.createElement("span");
824
+ title.classList.add("error-title");
825
+ title.textContent = "Error";
826
+ header.appendChild(title);
827
+
828
+ // Subtitle (short error description)
829
+ const subtitle = document.createElement("span");
830
+ subtitle.classList.add("error-subtitle");
831
+ header.appendChild(subtitle);
832
+
833
+ // Click handler for expand/collapse
834
+ header.addEventListener("click", () => {
835
+ errorGroup.classList.toggle("expanded");
836
+ });
837
+
838
+ errorGroup.appendChild(header);
839
+
840
+ // Create content container (collapsible)
841
+ const contentWrapper = document.createElement("div");
842
+ contentWrapper.classList.add("error-group-content");
843
+
844
+ const contentInner = document.createElement("div");
845
+ contentInner.classList.add("error-content-inner");
846
+ contentWrapper.appendChild(contentInner);
847
+
848
+ errorGroup.appendChild(contentWrapper);
849
+ messageDiv.appendChild(errorGroup);
850
+
851
+ // Check detail mode and expand if needed
852
+ const detailMode = window.Alpine?.store("preferences")?.detailMode || "current";
853
+ if (detailMode === "current" || detailMode === "expanded") {
854
+ errorGroup.classList.add("expanded");
855
+ }
856
+ }
857
+
858
+ // Update subtitle with short error description
859
+ const subtitle = errorGroup.querySelector(".error-subtitle");
860
+ if (subtitle) {
861
+ // Extract short description from heading or content
862
+ let shortDesc = "";
863
+ // Skip if heading is just "Error" (redundant with title)
864
+ if (heading && heading.trim() && heading.trim().toLowerCase() !== "error") {
865
+ shortDesc = heading.trim();
866
+ }
867
+ // If no useful heading, try to extract from content
868
+ if (!shortDesc && content && content.trim()) {
869
+ const lines = content.trim().split("\n");
870
+ // Look for the error line (usually last meaningful line or one matching ErrorType: pattern)
871
+ for (let i = lines.length - 1; i >= 0; i--) {
872
+ const line = lines[i].trim();
873
+ if (line && /^[\w\.]+Error[:\s]/.test(line)) {
874
+ shortDesc = line;
875
+ break;
876
+ }
877
+ }
878
+ // Fallback to first non-empty line if no error pattern found
879
+ if (!shortDesc) {
880
+ for (const line of lines) {
881
+ if (line.trim() && !line.startsWith("Traceback")) {
882
+ shortDesc = line.trim();
883
+ break;
884
+ }
885
+ }
886
+ }
887
+ }
888
+ // Truncate if too long
889
+ if (shortDesc.length > 100) {
890
+ shortDesc = shortDesc.substring(0, 97) + "...";
891
+ }
892
+ subtitle.textContent = shortDesc;
893
+ subtitle.title = shortDesc; // Full text on hover
894
+ }
895
+
896
+ // Update content (full callstack)
897
+ const contentInner = errorGroup.querySelector(".error-content-inner");
898
+ if (contentInner && content) {
899
+ contentInner.innerHTML = "";
900
+
901
+ // Create pre element for callstack/content
902
+ const pre = document.createElement("pre");
903
+ pre.classList.add("error-callstack");
904
+ pre.textContent = content;
905
+ contentInner.appendChild(pre);
906
+
907
+ // Add action buttons for copy functionality
908
+ addActionButtonsToElement(contentInner);
909
+ }
910
+
911
+ messageContainer.classList.add("center-container");
912
}
913
914
function drawKvps(container, kvps, latex) {