feat(frontend): viewer humain fil-par-paire en lecture seule (LS7)
Frontend pur, lecture seule, consomme read_conversation_page (LS6) ; aucun changement backend. - adapters : conversation.ts (gateway) + conversationNormalization.ts. - features/conversations : useConversationThread, ConversationViewer, index. - domain (types LS7), ports (port + Gateways.conversation), adapters/index, mock (+ clé inventaire). - features/workstate : ProjectWorkStatePanel prop onOpenConversation. - features/projects : ProjectsView swap viewer. - tests (QA, verts) : conversationNormalization, mock/conversationGateway, useConversationThread, ConversationViewer, ProjectsView.ls7. tsc 0, vitest 443/443 (+36). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -39,6 +39,7 @@ import { MemoryPanel } from "@/features/memory";
|
||||
import { EmbedderSettings } from "@/features/embedder";
|
||||
import { PermissionsPanel } from "@/features/permissions";
|
||||
import { ProjectWorkStatePanel } from "@/features/workstate";
|
||||
import { ConversationViewer } from "@/features/conversations";
|
||||
import { GitPanel, GitGraphView } from "@/features/git";
|
||||
import { Button, Input, Panel, Tabs, cn } from "@/shared";
|
||||
import { useGateways } from "@/app/di";
|
||||
@ -79,6 +80,12 @@ export function ProjectsView() {
|
||||
// truth. `kind` decides whether the main area is the terminal grid or the git
|
||||
// graph view.
|
||||
const [activeLayout, setActiveLayout] = useState<LayoutInfo | null>(null);
|
||||
// When set, the main area swaps the terminal grid for the read-only
|
||||
// conversation viewer (LS7) — pure local UI state, same mechanic as the
|
||||
// terminal↔gitGraph swap; **not** a backend layout kind.
|
||||
const [viewerConversationId, setViewerConversationId] = useState<
|
||||
string | null
|
||||
>(null);
|
||||
|
||||
const active = vm.openTabs.find((t) => t.id === vm.activeTabId) ?? null;
|
||||
|
||||
@ -89,6 +96,7 @@ export function ProjectsView() {
|
||||
// loading it against the new project's store fails with "not found: layout X".
|
||||
useEffect(() => {
|
||||
setActiveLayout(null);
|
||||
setViewerConversationId(null);
|
||||
}, [active?.id]);
|
||||
|
||||
const activeLayoutKind = activeLayout?.kind ?? "terminal";
|
||||
@ -287,7 +295,10 @@ export function ProjectsView() {
|
||||
|
||||
{/* Work-state panel */}
|
||||
{sidebarTab === "work" && active && (
|
||||
<ProjectWorkStatePanel projectId={active.id} />
|
||||
<ProjectWorkStatePanel
|
||||
projectId={active.id}
|
||||
onOpenConversation={setViewerConversationId}
|
||||
/>
|
||||
)}
|
||||
{sidebarTab === "work" && !active && (
|
||||
<p className="text-sm text-muted">Open a project to view work state.</p>
|
||||
@ -348,7 +359,14 @@ export function ProjectsView() {
|
||||
|
||||
{/* ── Main: terminal grid or git graph (fills remaining height) ── */}
|
||||
<main className="flex flex-1 flex-col overflow-hidden">
|
||||
{active ? (
|
||||
{active && viewerConversationId ? (
|
||||
<ConversationViewer
|
||||
key={`${active.id}-${viewerConversationId}`}
|
||||
projectId={active.id}
|
||||
conversationId={viewerConversationId}
|
||||
onClose={() => setViewerConversationId(null)}
|
||||
/>
|
||||
) : active ? (
|
||||
<>
|
||||
<LayoutTabs
|
||||
projectId={active.id}
|
||||
|
||||
Reference in New Issue
Block a user