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:
2026-06-22 17:30:51 +02:00
parent 40ca3e522f
commit c19e375849
17 changed files with 1552 additions and 5 deletions

View File

@ -31,6 +31,7 @@ import type {
MemoryType,
EffectivePermissions,
PermissionSet,
PageDirection,
Project,
ProjectPermissions,
ProjectWorkState,
@ -40,6 +41,7 @@ import type {
SkillScope,
Template,
TerminalSession,
TurnPage,
Unsubscribe,
} from "@/domain";
@ -660,6 +662,26 @@ export interface WorkStateGateway {
getProjectWorkState(projectId: string): Promise<ProjectWorkState>;
}
/** One paginated page request over a conversation transcript (LS7). */
export interface ConversationPageRequest {
/** Turn id to paginate around; omit to start from a thread end. */
anchor?: string;
/** Travel direction; defaults to `"backward"` (latest page first). */
direction?: PageDirection;
/** Requested page size (clamped by the backend to `[1, 200]`). */
limit?: number;
}
/** Read-only, paginated access to a conversation's full transcript (LS7). */
export interface ConversationGateway {
/** Reads one page of a conversation's transcript (full text, never truncated). */
readPage(
projectId: string,
conversationId: string,
request?: ConversationPageRequest,
): Promise<TurnPage>;
}
/**
* The full set of gateways the app depends on, injected via the DI provider.
* The composition (real vs mock) is chosen in `app/`.
@ -680,4 +702,5 @@ export interface Gateways {
embedder: EmbedderGateway;
permission: PermissionGateway;
workState: WorkStateGateway;
conversation: ConversationGateway;
}