step-title shiny text

3clyp50 committed Jan 19, 2026 at 15:31 UTC f0e4e6d1fe195826e449e0c4858587716784ba4c
2 files changed +55 -61
webui/components/messages/process-group/process-group.css
+2 -44
@@ -226,33 +226,6 @@
226 color: var(--step-accent);
227 }
228
229 -/* Animated spinner for active status (CSS-only, no Material Icons) */
230 -.status-badge.status-active::after {
231 - content: "";
232 - display: inline-block;
233 - width: 8px;
234 - height: 8px;
235 - border: 1.5px solid currentColor;
236 - border-top-color: transparent;
237 - border-radius: 50%;
238 - animation: spin 0.8s linear infinite;
239 - margin-left: 4px;
240 - flex-shrink: 0;
241 -}
242 -
243 -@keyframes spin {
244 - from { transform: rotate(0deg); }
245 - to { transform: rotate(360deg); }
246 -}
247 -
248 -/* Don't show spinner pseudo-element on END/ERR (they have their own indicators) */
249 -.status-badge.status-end::after,
250 -.status-badge.status-err::after {
251 - display: none;
252 -}
253 -
254 -/* END status with checkmark icon (icon added via JS, no ::before needed) */
255 -
229 /* Completed process group styling */
230 .process-group-completed {
231 opacity: 0.95;
@@ -388,16 +361,6 @@
361 min-height: 18px;
362 }
363
391 -/* Step icon removed - using status badges instead */
392 -.process-step-header .step-icon {
393 - display: none;
394 -}
395 -
396 -/* Step type label removed - using status badges instead */
397 -.process-step-header .step-type {
398 - display: none;
399 -}
400 -
364 /* Step title */
365 .process-step-header .step-title {
366 flex: 1;
@@ -787,9 +750,8 @@
750 50% { opacity: 0.8; }
751 }
752
790 -.process-step.loading .step-icon {
791 - animation: pulse-step 1.2s ease-in-out infinite;
792 -}
753 +.step-title.shiny-text { color: transparent !important; -webkit-background-clip: text; background-clip: text; animation: shine 1s linear infinite; }
754 + @keyframes shine { to { background-position: -100% center; } }
755
756 /* Responsive adjustments */
757 @media (max-width: 768px) {
@@ -797,10 +759,6 @@
759 padding: var(--spacing-xs) var(--spacing-sm);
760 }
761
800 - .process-step-header .step-type {
801 - display: none;
802 - }
803 -
762 .process-step-header .step-title {
763 font-size: 0.7rem;
764 }
webui/js/messages.js
+53 -17
@@ -13,6 +13,27 @@ const chatHistory = document.getElementById("chat-history");
13 let messageGroup = null;
14 let currentProcessGroup = null; // Track current process group for collapsible UI
15 let currentDelegationSteps = {}; // Track delegation steps by agent number for nesting
16 +let activeProcessGroupId = null; // Only one process group should show "running" indicators at a time
17 +let activeProcessGroupEl = null;
18 +let activeStepTitleEl = null;
19 +
20 +/**
21 + * Mark current process group as active and clear active badges.
22 + */
23 +function setActiveProcessGroup(group) {
24 + if (!group || !group.id) return;
25 + if (activeProcessGroupId === group.id) return;
26 +
27 + // Clear shiny effect from the previous active step title if we moved to a new group
28 + if (activeStepTitleEl && activeProcessGroupEl && activeProcessGroupEl !== group && activeProcessGroupEl.contains(activeStepTitleEl)) {
29 + activeStepTitleEl.classList.remove("shiny-text");
30 + activeStepTitleEl = null;
31 + }
32 +
33 + activeProcessGroupId = group.id;
34 + activeProcessGroupEl = group;
35 +
36 +}
37
38 /**
39 * Resolve tool name from kvps, existing attribute, or previous siblings
@@ -82,6 +103,7 @@ export function setMessage(id, type, heading, content, temp, kvps = null, timest
103 if (!currentProcessGroup || !document.getElementById(currentProcessGroup.id)) {
104 currentProcessGroup = createProcessGroup(id);
105 chatHistory.appendChild(currentProcessGroup);
106 + setActiveProcessGroup(currentProcessGroup);
107 }
108
109 // Add step to current process group
@@ -102,6 +124,7 @@ export function setMessage(id, type, heading, content, temp, kvps = null, timest
124 if (!currentProcessGroup || !document.getElementById(currentProcessGroup.id)) {
125 currentProcessGroup = createProcessGroup(id);
126 chatHistory.appendChild(currentProcessGroup);
127 + setActiveProcessGroup(currentProcessGroup);
128 }
129
130 // Add subordinate response as a response step (special type to show content)
@@ -1200,7 +1223,7 @@ function createProcessGroup(id) {
1223 header.innerHTML = `
1224 <span class="expand-icon"></span>
1225 <span class="group-title">Processing...</span>
1203 - <span class="status-badge status-gen status-active group-status">GEN</span>
1226 + <span class="status-badge status-gen group-status">GEN</span>
1227 <span class="group-metrics">
1228 <span class="metric-time" title="Start time"><span class="material-symbols-outlined">schedule</span><span class="metric-value">--:--</span></span>
1229 <span class="metric-steps" title="Steps"><span class="material-symbols-outlined">footprint</span><span class="metric-value">0</span></span>
@@ -1260,6 +1283,9 @@ function getNestedContainer(parentStep) {
1283 * Add a step to a process group
1284 */
1285 function addProcessStep(group, id, type, heading, content, kvps, timestamp = null, durationMs = null, agentNumber = 0) {
1286 + // group with newest step becomes the active one
1287 + setActiveProcessGroup(group);
1288 +
1289 const groupId = group.getAttribute("data-group-id");
1290 let stepsContainer = group.querySelector(".process-steps");
1291 const isGroupCompleted = group.classList.contains("process-group-completed");
@@ -1339,10 +1365,9 @@ function addProcessStep(group, id, type, heading, content, kvps, timestamp = nul
1365 // Add status color class to step for cascading --step-accent to internal icons
1366 step.classList.add(statusColorClass);
1367
1342 - const activeClass = isGroupCompleted ? "" : " status-active";
1368 stepHeader.innerHTML = `
1369 <span class="step-expand-icon"></span>
1345 - <span class="status-badge ${statusColorClass}${activeClass}">${statusCode}</span>
1370 + <span class="status-badge ${statusColorClass}">${statusCode}</span>
1371 <span class="step-title">${escapeHTML(title)}</span>
1372 `;
1373
@@ -1407,14 +1432,25 @@ function addProcessStep(group, id, type, heading, content, kvps, timestamp = nul
1432 }
1433 }
1434
1410 - // Remove status-active from all previous steps (only the current step is active)
1411 - const prevSteps = stepsContainer.querySelectorAll(".process-step .status-badge.status-active");
1412 - prevSteps.forEach(badge => badge.classList.remove("status-active"));
1435 + // Remove shiny effect from the previously active step title (O(1))
1436 + if (activeStepTitleEl) {
1437 + activeStepTitleEl.classList.remove("shiny-text");
1438 + activeStepTitleEl = null;
1439 + }
1440
1441 appendTarget.appendChild(step);
1442
1443 // Update group header
1444 updateProcessGroupHeader(group);
1445 +
1446 + // Apply shiny effect to the active step title
1447 + if (!isGroupCompleted && group.id === activeProcessGroupId) {
1448 + const titleEl = step.querySelector(".process-step-header .step-title");
1449 + if (titleEl) {
1450 + titleEl.classList.add("shiny-text");
1451 + activeStepTitleEl = titleEl;
1452 + }
1453 + }
1454
1455 return step;
1456 }
@@ -1810,10 +1846,8 @@ function updateProcessGroupHeader(group) {
1846 const metricsEl = group.querySelector(".group-metrics");
1847 const isCompleted = group.classList.contains("process-group-completed");
1848
1813 - // If completed, only remove active badges and exit early (don't update metrics)
1849 + // If completed, don't update metrics
1850 if (isCompleted) {
1815 - const activeBadges = group.querySelectorAll(".status-badge.status-active");
1816 - activeBadges.forEach(badge => badge.classList.remove("status-active"));
1851 return;
1852 }
1853
@@ -1881,14 +1915,14 @@ function updateProcessGroupHeader(group) {
1915 const lastToolName = lastStep.getAttribute("data-tool-name");
1916 const lastTitle = lastStep.querySelector(".step-title")?.textContent || "";
1917
1884 - // Update status badge (keep status-active during execution)
1918 + // Update status badge
1919 if (statusEl) {
1920 // Status code and color class from store (maps backend types)
1921 const statusCode = processGroupStore.getStepCode(lastType, lastToolName);
1922 const statusColorClass = processGroupStore.getStatusColorClass(lastType, lastToolName);
1923
1924 statusEl.textContent = statusCode;
1891 - statusEl.className = `status-badge ${statusColorClass} status-active group-status`;
1925 + statusEl.className = `status-badge ${statusColorClass} group-status`;
1926 }
1927
1928 // Update title
@@ -1929,17 +1963,13 @@ function truncateText(text, maxLength) {
1963 function markProcessGroupComplete(group, responseTitle) {
1964 if (!group) return;
1965
1932 - // Update status badge to END (remove status-active)
1966 + // Update status badge to END
1967 const statusEl = group.querySelector(".group-status");
1968 if (statusEl) {
1969 statusEl.innerHTML = '<span class="badge-icon material-symbols-outlined">check</span>END';
1936 - statusEl.className = "status-badge status-end group-status"; // No status-active
1970 + statusEl.className = "status-badge status-end group-status";
1971 }
1972
1939 - // Remove status-active from all step badges (stop spinners)
1940 - const stepBadges = group.querySelectorAll(".process-step .status-badge.status-active");
1941 - stepBadges.forEach(badge => badge.classList.remove("status-active"));
1942 -
1973 // Update title if response title is available
1974 const titleEl = group.querySelector(".group-title");
1975 if (titleEl && responseTitle) {
@@ -1975,6 +2005,12 @@ export function resetProcessGroups() {
2005 currentProcessGroup = null;
2006 currentDelegationSteps = {};
2007 messageGroup = null;
2008 + activeProcessGroupId = null;
2009 + activeProcessGroupEl = null;
2010 + if (activeStepTitleEl) {
2011 + activeStepTitleEl.classList.remove("shiny-text");
2012 + }
2013 + activeStepTitleEl = null;
2014 }
2015
2016 /**