- {logs.map((log) => {
- const isSelected = selectedId === log.id;
- const isExpanded = expandedIds.has(log.id);
- return (
-
onSelect(log.id)}
+
+
+
>
+
setSearchQuery(e.target.value)}
+ />
+ {searchQuery && (
+
+ );
+ })
+ )}
+
);
}
diff --git a/client/src/components/PrivacyToggle.css b/client/src/components/PrivacyToggle.css
index f31ea0a..4ff696d 100644
--- a/client/src/components/PrivacyToggle.css
+++ b/client/src/components/PrivacyToggle.css
@@ -4,15 +4,15 @@
display: flex;
flex-direction: column;
align-items: center;
- gap: 6px;
+ gap: 8px;
}
.privacy-toggle {
position: relative;
display: inline-flex;
align-items: stretch;
- border: 1px solid #2a3a6a;
- background: rgba(10, 14, 39, 0.85);
+ border: 1px solid var(--border);
+ background: var(--background);
overflow: hidden;
}
@@ -22,8 +22,8 @@
top: 2px;
bottom: 2px;
width: calc(50% - 2px);
- background: rgba(77, 208, 225, 0.12);
- border: 1px solid rgba(77, 208, 225, 0.35);
+ background: rgba(var(--primary-rgb), 0.12);
+ border: 1px solid rgba(var(--primary-rgb), 0.35);
transition:
transform 0.3s cubic-bezier(0.4, 0, 0.2, 1),
box-shadow 0.3s ease;
@@ -34,15 +34,15 @@
.indicator-local {
transform: translateX(2px);
box-shadow:
- 0 0 12px rgba(77, 208, 225, 0.2),
- inset 0 0 8px rgba(77, 208, 225, 0.08);
+ 0 0 12px rgba(var(--primary-rgb), 0.2),
+ inset 0 0 8px rgba(var(--primary-rgb), 0.08);
}
.indicator-cloud {
transform: translateX(calc(100% + 2px));
box-shadow:
- 0 0 12px rgba(77, 208, 225, 0.2),
- inset 0 0 8px rgba(77, 208, 225, 0.08);
+ 0 0 12px rgba(var(--primary-rgb), 0.2),
+ inset 0 0 8px rgba(var(--primary-rgb), 0.08);
}
/* Individual toggle buttons */
@@ -51,12 +51,12 @@
z-index: 1;
display: inline-flex;
align-items: center;
- gap: 6px;
- padding: 7px 14px;
+ gap: 8px;
+ padding: 10px 18px;
border: none;
background: transparent;
- color: #5a7aa3;
- font-size: 11px;
+ color: var(--muted-foreground);
+ font-size: 13px;
letter-spacing: 0.16em;
text-transform: uppercase;
cursor: pointer;
@@ -67,54 +67,54 @@
}
.toggle-option:hover:not(.active) {
- color: #8bb4d9;
+ color: var(--foreground);
}
.toggle-option.active {
- color: #b8f2ff;
- text-shadow: 0 0 8px rgba(77, 208, 225, 0.5);
+ color: var(--foreground);
+ text-shadow: 0 0 8px rgba(var(--primary-rgb), 0.5);
}
.toggle-icon {
- width: 14px;
- height: 14px;
+ width: 16px;
+ height: 16px;
flex-shrink: 0;
transition: color 0.25s ease;
}
.toggle-option.active .toggle-icon {
- color: #4dd0e1;
- filter: drop-shadow(0 0 4px rgba(77, 208, 225, 0.6));
+ color: var(--primary);
+ filter: drop-shadow(0 0 4px rgba(var(--primary-rgb), 0.6));
}
/* Hint text below toggle */
.toggle-hint {
display: inline-flex;
align-items: center;
- gap: 6px;
- font-size: 10px;
+ gap: 8px;
+ font-size: 12px;
letter-spacing: 0.18em;
text-transform: uppercase;
- color: #5a7aa3;
+ color: var(--muted-foreground);
transition: color 0.25s ease;
- min-height: 14px;
+ min-height: 16px;
}
.hint-dot {
- width: 5px;
- height: 5px;
+ width: 6px;
+ height: 6px;
display: inline-block;
transition: background 0.25s ease, box-shadow 0.25s ease;
}
.hint-dot--local {
- background: #4dd0e1;
- box-shadow: 0 0 6px rgba(77, 208, 225, 0.6);
+ background: var(--primary);
+ box-shadow: 0 0 6px rgba(var(--primary-rgb), 0.6);
}
.hint-dot--cloud {
- background: #ffd93d;
- box-shadow: 0 0 6px rgba(255, 217, 61, 0.5);
+ background: var(--chart-5);
+ box-shadow: 0 0 6px rgba(var(--primary-rgb), 0.5);
}
/* ─── Responsive ──────────────────────────────────────── */
@@ -125,6 +125,6 @@
}
.toggle-option {
- padding: 7px 10px;
+ padding: 10px 14px;
}
}
diff --git a/client/src/components/ResolveModal.css b/client/src/components/ResolveModal.css
index 5764b92..c3b6a7f 100644
--- a/client/src/components/ResolveModal.css
+++ b/client/src/components/ResolveModal.css
@@ -1,14 +1,14 @@
/* ─── Resolve Modal ──────────────────────────────────── */
.resolve-modal {
- max-width: 520px;
+ max-width: 600px;
}
.resolve-body {
- padding: 16px;
+ padding: 24px;
display: flex;
flex-direction: column;
- gap: 16px;
+ gap: 20px;
}
/* ─── Issue Context ──────────────────────────────────── */
@@ -16,22 +16,22 @@
.resolve-context {
display: flex;
align-items: center;
- gap: 8px;
- padding: 8px 12px;
- border: 1px solid #1e2d5f;
- background: rgba(10, 14, 39, 0.6);
+ gap: 10px;
+ padding: 12px 16px;
+ border: 1px solid var(--border);
+ background: var(--background);
}
.resolve-context-label {
- font-size: 10px;
+ font-size: 12px;
letter-spacing: 0.18em;
text-transform: uppercase;
- color: #5a7aa3;
+ color: var(--muted-foreground);
}
.resolve-context-value {
- font-size: 11px;
- color: #b8f2ff;
+ font-size: 13px;
+ color: var(--foreground);
letter-spacing: 0.06em;
}
@@ -40,20 +40,20 @@
.resolve-section {
display: flex;
flex-direction: column;
- gap: 8px;
+ gap: 10px;
}
.resolve-section-label {
- font-size: 11px;
+ font-size: 13px;
letter-spacing: 0.16em;
text-transform: uppercase;
- color: #4dd0e1;
- text-shadow: 0 0 6px rgba(77, 208, 225, 0.3);
+ color: var(--primary);
+ text-shadow: 0 0 6px rgba(var(--primary-rgb), 0.3);
}
.resolve-section-desc {
- font-size: 11px;
- color: #6b8fb5;
+ font-size: 13px;
+ color: var(--muted-foreground);
line-height: 1.5;
margin: 0;
}
@@ -62,13 +62,13 @@
.resolve-simple-btn {
align-self: flex-start;
- border-color: #36526e !important;
+ border-color: var(--border) !important;
}
.resolve-simple-btn:hover:not(:disabled) {
- border-color: #4dd0e1 !important;
- color: #b8f2ff !important;
- box-shadow: 0 0 10px rgba(77, 208, 225, 0.15);
+ border-color: var(--primary) !important;
+ color: var(--foreground) !important;
+ box-shadow: 0 0 10px rgba(var(--primary-rgb), 0.15);
}
/* ─── RAG Submit Button ──────────────────────────────── */
@@ -82,7 +82,7 @@
.resolve-divider {
display: flex;
align-items: center;
- gap: 12px;
+ gap: 16px;
}
.resolve-divider::before,
@@ -90,12 +90,12 @@
content: "";
flex: 1;
height: 1px;
- background: #1e2d5f;
+ background: var(--border);
}
.resolve-divider-text {
- font-size: 10px;
+ font-size: 12px;
letter-spacing: 0.2em;
text-transform: uppercase;
- color: #3b5d7c;
+ color: var(--muted-foreground);
}
diff --git a/client/src/index.css b/client/src/index.css
index f829f92..5aa6b55 100644
--- a/client/src/index.css
+++ b/client/src/index.css
@@ -36,6 +36,64 @@
--sidebar-accent-foreground: #8bb4d9;
--sidebar-border: #2a3a6a;
--sidebar-ring: #4dd0e1;
+ --border: #2a3a6a;
+ --primary-rgb: 77, 208, 225;
+}
+
+/* Classic Phosphor Green Theme */
+.theme-green {
+ --background: #050a05;
+ --foreground: #33ff33;
+ --card: #0c180c;
+ --card-foreground: #a3ffa3;
+ --popover: #0f220f;
+ --popover-foreground: #a3ffa3;
+ --primary: #33ff33;
+ --primary-foreground: #050a05;
+ --secondary: #163316;
+ --secondary-foreground: #a3ffa3;
+ --muted: #0f220f;
+ --muted-foreground: #22aa22;
+ --destructive: #ff4d4d;
+ --ring: #33ff33;
+ --sidebar: #0c180c;
+ --sidebar-foreground: #a3ffa3;
+ --sidebar-primary: #33ff33;
+ --sidebar-primary-foreground: #050a05;
+ --sidebar-accent: #163316;
+ --sidebar-accent-foreground: #a3ffa3;
+ --sidebar-border: #1a3c1a;
+ --sidebar-ring: #33ff33;
+ --border: #1a3c1a;
+ --primary-rgb: 51, 255, 51;
+}
+
+/* Monochrome Phosphor Amber Theme */
+.theme-amber {
+ --background: #0a0600;
+ --foreground: #ffb000;
+ --card: #160d00;
+ --card-foreground: #ffd580;
+ --popover: #1f1200;
+ --popover-foreground: #ffd580;
+ --primary: #ffb000;
+ --primary-foreground: #0a0600;
+ --secondary: #301b00;
+ --secondary-foreground: #ffd580;
+ --muted: #1f1200;
+ --muted-foreground: #b37d00;
+ --destructive: #ff5500;
+ --ring: #ffb000;
+ --sidebar: #160d00;
+ --sidebar-foreground: #ffd580;
+ --sidebar-primary: #ffb000;
+ --sidebar-primary-foreground: #0a0600;
+ --sidebar-accent: #301b00;
+ --sidebar-accent-foreground: #ffd580;
+ --sidebar-border: #442600;
+ --sidebar-ring: #ffb000;
+ --border: #442600;
+ --primary-rgb: 255, 176, 0;
}
@theme inline {
diff --git a/client/src/test/InsightsPanel.test.jsx b/client/src/test/InsightsPanel.test.jsx
index 3f518c8..b416c6f 100644
--- a/client/src/test/InsightsPanel.test.jsx
+++ b/client/src/test/InsightsPanel.test.jsx
@@ -30,8 +30,8 @@ const mockResult = {
describe("InsightsPanel", () => {
it("should render loading state when loading is true", () => {
render(React.createElement(InsightsPanel, { loading: true, result: null }));
- expect(screen.getByText("Analyzing…")).toBeInTheDocument();
- expect(screen.getByText("Running AI analysis on selected log")).toBeInTheDocument();
+ expect(screen.getByText(/INITIALIZING CORE AGENT/i)).toBeInTheDocument();
+ expect(screen.getByText(/Core reasoning engine is processing/i)).toBeInTheDocument();
});
it("should render null when result is not provided and not loading", () => {
diff --git a/client/src/test/LogList.test.jsx b/client/src/test/LogList.test.jsx
index 8089472..4365f6e 100644
--- a/client/src/test/LogList.test.jsx
+++ b/client/src/test/LogList.test.jsx
@@ -131,4 +131,19 @@ describe("LogList", () => {
expect(onSelect).not.toHaveBeenCalled();
});
+
+ it("should call onDelete and prevent propagation when delete button is clicked", () => {
+ const onDelete = vi.fn();
+ const onSelect = vi.fn();
+ const { container } = renderLogList({ onDelete, onSelect });
+
+ const firstEntry = container.querySelectorAll(".log-entry")[0];
+ const deleteBtn = firstEntry.querySelector(".delete-btn");
+
+ fireEvent.click(deleteBtn);
+
+ expect(onDelete).toHaveBeenCalledTimes(1);
+ expect(onDelete).toHaveBeenCalledWith(1);
+ expect(onSelect).not.toHaveBeenCalled();
+ });
});
diff --git a/client/src/test/PrivacyToggle.test.jsx b/client/src/test/PrivacyToggle.test.jsx
index 82fa882..259c9be 100644
--- a/client/src/test/PrivacyToggle.test.jsx
+++ b/client/src/test/PrivacyToggle.test.jsx
@@ -8,6 +8,22 @@ import {
} from "../context/PrivacyModeContext";
import { STORAGE_KEY } from "../context/privacyModeConstants";
+// Setup localStorage Mock for Vitest JSDOM environment
+const localStorageMock = (() => {
+ let store = {};
+ return {
+ getItem: (key) => store[key] || null,
+ setItem: (key, value) => { store[key] = String(value); },
+ clear: () => { store = {}; },
+ removeItem: (key) => { delete store[key]; }
+ };
+})();
+Object.defineProperty(window, "localStorage", {
+ value: localStorageMock,
+ writable: true,
+ configurable: true
+});
+
// Helper: render toggle wrapped in its required provider
function renderToggle() {
return render(
From 7ea1a3f65bf3eed347349c1fb1f04d066489af94 Mon Sep 17 00:00:00 2001
From: Sehmuel Wagner <111253615+sachmii@users.noreply.github.com>
Date: Sun, 21 Jun 2026 20:31:25 +0200
Subject: [PATCH 7/7] fix errors
---
client/src/App.jsx | 4 +--
client/src/components/InsightsPanel.jsx | 36 ++++++++++++-------------
2 files changed, 20 insertions(+), 20 deletions(-)
diff --git a/client/src/App.jsx b/client/src/App.jsx
index 20a149a..37bf4fc 100644
--- a/client/src/App.jsx
+++ b/client/src/App.jsx
@@ -20,7 +20,7 @@ function App() {
if (typeof localStorage !== "undefined" && typeof localStorage.getItem === "function") {
return localStorage.getItem("devpulse-theme") || "cyan";
}
- } catch (e) {
+ } catch {
// ignore
}
return "cyan";
@@ -42,7 +42,7 @@ function App() {
if (typeof localStorage !== "undefined" && typeof localStorage.setItem === "function") {
localStorage.setItem("devpulse-theme", theme);
}
- } catch (e) {
+ } catch {
// ignore
}
}, [theme]);
diff --git a/client/src/components/InsightsPanel.jsx b/client/src/components/InsightsPanel.jsx
index f2fb931..a2a7652 100644
--- a/client/src/components/InsightsPanel.jsx
+++ b/client/src/components/InsightsPanel.jsx
@@ -7,24 +7,21 @@ const CONFIDENCE_COLORS = {
high: "#4dd0e1",
};
+const STAGES = [
+ "INITIALIZING CORE AGENT...",
+ "ESTABLISHING INCIDENT CONTEXT...",
+ "PARSING LOG STACK TRACES...",
+ "RUNNING PROMPT INFERENCE...",
+ "RETRIEVING LATEST RAG RUNBOOKS...",
+ "COMPILING REMEDIES...",
+ "FINALIZING DIAGNOSTIC REPORT...",
+];
+
function RetroLoader() {
const [progress, setProgress] = useState(0);
const [stage, setStage] = useState(0);
- const STAGES = [
- "INITIALIZING CORE AGENT...",
- "ESTABLISHING INCIDENT CONTEXT...",
- "PARSING LOG STACK TRACES...",
- "RUNNING PROMPT INFERENCE...",
- "RETRIEVING LATEST RAG RUNBOOKS...",
- "COMPILING REMEDIES...",
- "FINALIZING DIAGNOSTIC REPORT..."
- ];
-
useEffect(() => {
- setProgress(0);
- setStage(0);
-
const progressInterval = setInterval(() => {
setProgress((prev) => {
if (prev >= 100) {
@@ -33,13 +30,13 @@ function RetroLoader() {
}
const increment = Math.floor(Math.random() * 8) + 4;
const nextProgress = Math.min(prev + increment, 100);
-
+
const stageIndex = Math.min(
Math.floor((nextProgress / 100) * STAGES.length),
- STAGES.length - 1
+ STAGES.length - 1,
);
setStage(stageIndex);
-
+
return nextProgress;
});
}, 250);
@@ -50,12 +47,15 @@ function RetroLoader() {
const totalBars = 20;
const filledBars = Math.round((progress / 100) * totalBars);
const emptyBars = totalBars - filledBars;
- const barString = "=".repeat(Math.max(0, filledBars - 1)) + (filledBars > 0 ? ">" : "") + " ".repeat(emptyBars);
+ const barString =
+ "=".repeat(Math.max(0, filledBars - 1)) +
+ (filledBars > 0 ? ">" : "") +
+ " ".repeat(emptyBars);
return (
-{`
+ {`
__ __ ____ ____ _ ___ _ _ ____
| \\/ |/ ___| / ___|| | |_ _| \\ | |/ ___|
| |\\/| | | _ \\___ \\| | | || \\| | | _