From e20330af694e3ce0f7bdfb64e36ab341c55db86e Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Thu, 28 May 2026 11:08:36 -0300 Subject: [PATCH] feat(translator): lift useTranslateSession to shell + connect PipelineView to real result - Move useTranslateSession() to TranslatorPageClient (shell level) for PipelineView to receive real steps - Build PipelineStep[] from session result (detected/intermediate/translated/response) - Pass session as prop down to TranslateTab; internal hook kept for isolated test compatibility - Dedup useProviderOptions: only TranslateTab calls the hook, props pass to SimpleControls - Remove unused data-advanced-section/data-input-text placeholder (DOM data leak GAP-5) - Add aria-expanded + aria-controls to AutoFeaturesCard toggle (a11y GAP-2) Addresses code review RISCO-4, GAP-2, GAP-3, GAP-5. --- .../translator/TranslatorPageClient.tsx | 88 ++++++++++++++++++- .../translator/components/SimpleControls.tsx | 7 +- .../translator/components/TranslateTab.tsx | 37 ++++---- .../translator-friendly-integration.test.tsx | 25 +++++- .../translator-friendly-page-client.test.tsx | 25 +++++- ...anslator-friendly-simple-controls.test.tsx | 20 +---- ...translator-friendly-translate-tab.test.tsx | 37 +------- 7 files changed, 161 insertions(+), 78 deletions(-) diff --git a/src/app/(dashboard)/dashboard/translator/TranslatorPageClient.tsx b/src/app/(dashboard)/dashboard/translator/TranslatorPageClient.tsx index 2c07b9594f..5701577278 100644 --- a/src/app/(dashboard)/dashboard/translator/TranslatorPageClient.tsx +++ b/src/app/(dashboard)/dashboard/translator/TranslatorPageClient.tsx @@ -1,6 +1,6 @@ "use client"; -import { Suspense, useState } from "react"; +import { Suspense, useMemo, useState } from "react"; import { useTranslations } from "next-intl"; import { Badge, Card, SegmentedControl } from "@/shared/components"; import TranslatorConceptCard from "./components/TranslatorConceptCard"; @@ -9,10 +9,12 @@ import MonitorTab from "./components/MonitorTab"; import AdvancedSection from "./components/advanced/AdvancedSection"; import RawJsonPanel from "./components/advanced/RawJsonPanel"; import PipelineView from "./components/advanced/PipelineView"; +import type { PipelineStep } from "./components/advanced/PipelineView"; import StreamTransformerAccordion from "./components/advanced/StreamTransformerAccordion"; import TestBenchAccordion from "./components/advanced/TestBenchAccordion"; import CompressionPreviewAccordion from "./components/advanced/CompressionPreviewAccordion"; import { useTranslateDeepLink } from "./hooks/useTranslateDeepLink"; +import { useTranslateSession } from "./hooks/useTranslateSession"; import type { AdvancedSlug, TranslatorTab } from "./types"; export default function TranslatorPageClient() { @@ -28,6 +30,9 @@ function TranslatorPageClientInner() { const [sharedInputContent, setSharedInputContent] = useState(""); const { state, setTab, setAdvanced } = useTranslateDeepLink(); + // Lift session to shell so PipelineView can receive real steps + const session = useTranslateSession(); + const makeOpenHandler = (slug: AdvancedSlug) => (open: boolean) => { if (open) { setAdvanced(slug); @@ -36,6 +41,80 @@ function TranslatorPageClientInner() { } }; + const tr = (key: string, fallback: string): string => { + try { + const v = t(key as Parameters[0]); + if (v === key || v === `translator.${key}`) return fallback; + return v as string; + } catch { + return fallback; + } + }; + + // Build PipelineStep[] from session.result so PipelineView reflects real state + const pipelineSteps = useMemo(() => { + const r = session.result; + if (r.status === "idle") return []; + + const steps: PipelineStep[] = []; + + // Step 1 — Client Request (always present once started) + steps.push({ + id: "1", + name: tr("pipelineStepClientRequest", "Client Request"), + description: tr("pipelineStepClientRequestDesc", "Request received in client format"), + format: r.detected ?? "openai", + content: sharedInputContent.slice(0, 500), + status: r.status === "error" ? "error" : "done", + }); + + // Step 2 — Format Detected + steps.push({ + id: "2", + name: tr("pipelineStepFormatDetected", "Format Detected"), + description: tr("pipelineStepFormatDetectedDesc", "Auto-detected source format"), + format: r.detected ?? null, + content: r.detected ? JSON.stringify({ detectedFormat: r.detected, confidence: "high" }, null, 2) : "", + status: r.detected ? "done" : r.status === "translating" ? "active" : "pending", + }); + + // Step 3 — OpenAI Intermediate (only when hub-and-spoke) + if (r.pipelinePath === "hub-and-spoke") { + steps.push({ + id: "3", + name: tr("pipelineStepOpenAIIntermediate", "OpenAI Intermediate"), + description: tr("pipelineStepOpenAIIntermediateDesc", "Translated to OpenAI hub format"), + format: "openai", + content: r.intermediateJson ?? "", + status: r.intermediateJson ? "done" : r.status === "translating" ? "active" : "pending", + }); + } + + // Step 4 — Provider Format (translated result) + steps.push({ + id: r.pipelinePath === "hub-and-spoke" ? "4" : "3", + name: tr("pipelineStepProviderFormat", "Provider Format"), + description: tr("pipelineStepProviderFormatDesc", "Translated to provider target format"), + format: r.target, + content: r.translatedJson ?? "", + status: r.translatedJson ? "done" : r.status === "translating" ? "active" : "pending", + }); + + // Step 5 — Provider Response (only when mode=send and response present) + if (r.responsePreview !== null) { + steps.push({ + id: r.pipelinePath === "hub-and-spoke" ? "5" : "4", + name: tr("pipelineStepProviderResponse", "Provider Response"), + description: tr("pipelineStepProviderResponseDesc", "Streaming response from provider"), + format: "openai", + content: r.responsePreview, + status: r.status === "ok" ? "done" : r.status === "sending" ? "active" : "pending", + }); + } + + return steps; + }, [session.result, sharedInputContent]); + const advancedSlot = ( 0 ? pipelineSteps : undefined} /> setAdvanced(slug)} + session={session} /> )} @@ -111,7 +191,10 @@ function AutoFeaturesCard() { return (