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) {