fix(dashboard): v3.8.8 screen quick wins (Phase 1)

- search-tools: export modal no longer opens by default / stuck — guard on
  exportOpen and drop the invalid isOpen prop the modal never read.
- logs: remove duplicate proxy/console tabs + SegmentedControl (dedicated
  /dashboard/logs/proxy and /console pages already exist in the menu).
- memory: order tabs Memories -> Engine -> Playground.
- ui(Select): render children and suppress the placeholder/options branch when
  children are provided — fixes the "empty" memory type/strategy dropdowns
  (children were being shadowed by the component's own option list).
- test: source-level regression guards for all four.
This commit is contained in:
diegosouzapw
2026-05-30 12:31:08 -03:00
parent cad06d85a6
commit a59a90e6a1
5 changed files with 57 additions and 52 deletions

View File

@@ -1,9 +1,7 @@
"use client";
import { useState, useRef, useEffect } from "react";
import { useSearchParams } from "next/navigation";
import { ConfirmModal, RequestLoggerV2, ProxyLogger, SegmentedControl } from "@/shared/components";
import ConsoleLogViewer from "@/shared/components/ConsoleLogViewer";
import { ConfirmModal, RequestLoggerV2 } from "@/shared/components";
import EmailPrivacyToggle from "@/shared/components/EmailPrivacyToggle";
import ActiveRequestsPanel from "@/shared/components/ActiveRequestsPanel";
import { useTranslations } from "next-intl";
@@ -15,18 +13,7 @@ const TIME_RANGES = [
{ label: "24h", hours: 24 },
];
const TAB_TO_LOG_TYPE: Record<string, string> = {
"request-logs": "request-logs",
"proxy-logs": "proxy-logs",
console: "call-logs",
};
export default function LogsPage() {
const searchParams = useSearchParams();
const requestedTab = searchParams.get("tab");
const [activeTab, setActiveTab] = useState(
requestedTab && TAB_TO_LOG_TYPE[requestedTab] ? requestedTab : "request-logs"
);
const [showExport, setShowExport] = useState(false);
const [exporting, setExporting] = useState(false);
const [showCleanHistory, setShowCleanHistory] = useState(false);
@@ -36,12 +23,6 @@ export default function LogsPage() {
const dropdownRef = useRef<HTMLDivElement>(null);
const t = useTranslations("logs");
useEffect(() => {
if (requestedTab && TAB_TO_LOG_TYPE[requestedTab] && requestedTab !== activeTab) {
setActiveTab(requestedTab);
}
}, [activeTab, requestedTab]);
useEffect(() => {
function handleClickOutside(e: MouseEvent) {
if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {
@@ -56,7 +37,7 @@ export default function LogsPage() {
setExporting(true);
setShowExport(false);
try {
const logType = TAB_TO_LOG_TYPE[activeTab] || "call-logs";
const logType = "request-logs";
const res = await fetch(`/api/logs/export?hours=${hours}&type=${logType}`);
if (!res.ok) throw new Error(t("exportFailed"));
const blob = await res.blob();
@@ -108,15 +89,7 @@ export default function LogsPage() {
return (
<div className="flex flex-col gap-6">
<div className="flex items-center justify-between gap-4 flex-wrap">
<SegmentedControl
options={[
{ value: "request-logs", label: t("requestLogs") },
{ value: "proxy-logs", label: t("proxyLogs") },
{ value: "console", label: t("console") },
]}
value={activeTab}
onChange={setActiveTab}
/>
<h2 className="text-lg font-semibold text-text-main">{t("requestLogs")}</h2>
<div className="flex items-center gap-2">
<EmailPrivacyToggle size="md" />
@@ -211,14 +184,10 @@ export default function LogsPage() {
</div>
)}
{activeTab === "request-logs" && (
<div className="flex flex-col gap-6">
<ActiveRequestsPanel />
<RequestLoggerV2 key={requestLogKey} />
</div>
)}
{activeTab === "proxy-logs" && <ProxyLogger />}
{activeTab === "console" && <ConsoleLogViewer />}
<div className="flex flex-col gap-6">
<ActiveRequestsPanel />
<RequestLoggerV2 key={requestLogKey} />
</div>
<ConfirmModal
isOpen={showCleanHistory}

View File

@@ -10,7 +10,7 @@ import EngineTab from "./components/tabs/EngineTab";
type TabId = "memories" | "playground" | "engine";
const TABS: TabId[] = ["memories", "playground", "engine"];
const TABS: TabId[] = ["memories", "engine", "playground"];
function MemoryPageContent() {
const t = useTranslations("memory");

View File

@@ -86,12 +86,8 @@ export default function SearchToolsTopBar({
</div>
</div>
{exportState && (
<ExportCodeModal
isOpen={exportOpen}
onClose={() => setExportOpen(false)}
state={exportState}
/>
{exportOpen && exportState != null && (
<ExportCodeModal onClose={() => setExportOpen(false)} state={exportState} />
)}
</>
);

View File

@@ -30,6 +30,7 @@ export default function Select({
className,
selectClassName,
id: externalId,
children,
...props
}: SelectProps) {
const generatedId = useId();
@@ -71,14 +72,18 @@ export default function Select({
)}
{...props}
>
<option value="" disabled className="bg-surface text-text-muted">
{placeholder}
</option>
{options.map((option) => (
<option key={option.value} value={option.value} className="bg-surface text-text-main">
{option.label}
{!children && placeholder && (
<option value="" disabled className="bg-surface text-text-muted">
{placeholder}
</option>
))}
)}
{!children &&
options.map((option) => (
<option key={option.value} value={option.value} className="bg-surface text-text-main">
{option.label}
</option>
))}
{children}
</select>
<div
className="absolute inset-y-0 right-0 flex items-center pr-3 pointer-events-none text-text-muted"

View File

@@ -0,0 +1,35 @@
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { join } from "node:path";
// Regression guards for v3.8.8 screen-fix Phase 1 (quick wins).
const root = join(import.meta.dirname, "../..");
const read = (p: string) => readFileSync(join(root, p), "utf8");
test("search-tools: export modal mounts on exportOpen (not by default) without invalid isOpen prop", () => {
const src = read("src/app/(dashboard)/dashboard/search-tools/components/SearchToolsTopBar.tsx");
assert.ok(src.includes("{exportOpen && exportState != null && ("), "modal guarded by exportOpen");
assert.equal(/<ExportCodeModal[^>]*\bisOpen=/.test(src), false, "no invalid isOpen prop on ExportCodeModal");
});
test("memory: tabs ordered memories -> engine -> playground", () => {
const src = read("src/app/(dashboard)/dashboard/memory/page.tsx");
assert.ok(src.includes('["memories", "engine", "playground"]'), "TABS order is memories, engine, playground");
});
test("shared Select: renders children and guards placeholder/options when children present", () => {
const src = read("src/shared/components/Select.tsx");
assert.ok(src.includes("{children}"), "renders children passed by callers");
assert.ok(src.includes("!children && placeholder"), "placeholder guarded by !children");
assert.ok(src.includes("!children &&\n options.map") || src.includes("!children &&"), "options guarded by !children");
});
test("logs: proxy/console tabs removed (dedicated menu pages exist)", () => {
const src = read("src/app/(dashboard)/dashboard/logs/page.tsx");
assert.equal(/value:\s*"proxy-logs"/.test(src), false, "proxy-logs tab removed");
assert.equal(/value:\s*"console"/.test(src), false, "console tab removed");
assert.equal(src.includes("<ProxyLogger"), false, "ProxyLogger not rendered here");
assert.equal(src.includes("<ConsoleLogViewer"), false, "ConsoleLogViewer not rendered here");
assert.equal(src.includes("SegmentedControl"), false, "SegmentedControl removed (single tab left)");
});