2659 lines · 79.8 KB · TypeScript
RawDownload
// Production CSS is part of the behavior under test because row height depends on it.import "../index.css"; import {  EventId,  ORCHESTRATION_WS_METHODS,  type MessageId,  type OrchestrationEvent,  type OrchestrationReadModel,  type ProjectId,  type ServerConfig,  type ServerLifecycleWelcomePayload,  type ThreadId,  type TurnId,  WS_METHODS,  OrchestrationSessionStatus,  DEFAULT_SERVER_SETTINGS,} from "@t3tools/contracts";import { RouterProvider, createMemoryHistory } from "@tanstack/react-router";import { HttpResponse, http, ws } from "msw";import { setupWorker } from "msw/browser";import { page } from "vitest/browser";import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";import { render } from "vitest-browser-react"; import { useComposerDraftStore } from "../composerDraftStore";import {  INLINE_TERMINAL_CONTEXT_PLACEHOLDER,  type TerminalContextDraft,  removeInlineTerminalContextPlaceholder,} from "../lib/terminalContext";import { isMacPlatform } from "../lib/utils";import { __resetNativeApiForTests } from "../nativeApi";import { getRouter } from "../router";import { useStore } from "../store";import { BrowserWsRpcHarness, type NormalizedWsRpcRequestBody } from "../../test/wsRpcHarness";import { estimateTimelineMessageHeight } from "./timelineHeight";import { DEFAULT_CLIENT_SETTINGS } from "@t3tools/contracts/settings"; const THREAD_ID = "thread-browser-test" as ThreadId;const UUID_ROUTE_RE = /^\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;const PROJECT_ID = "project-1" as ProjectId;const NOW_ISO = "2026-03-04T12:00:00.000Z";const BASE_TIME_MS = Date.parse(NOW_ISO);const ATTACHMENT_SVG = "<svg xmlns='http://www.w3.org/2000/svg' width='120' height='300'></svg>"; interface TestFixture {  snapshot: OrchestrationReadModel;  serverConfig: ServerConfig;  welcome: ServerLifecycleWelcomePayload;} let fixture: TestFixture;const rpcHarness = new BrowserWsRpcHarness();const wsRequests = rpcHarness.requests;let customWsRpcResolver: ((body: NormalizedWsRpcRequestBody) => unknown | undefined) | null = null;const wsLink = ws.link(/ws(s)?:\/\/.*/); interface ViewportSpec {  name: string;  width: number;  height: number;  textTolerancePx: number;  attachmentTolerancePx: number;} const DEFAULT_VIEWPORT: ViewportSpec = {  name: "desktop",  width: 960,  height: 1_100,  textTolerancePx: 44,  attachmentTolerancePx: 56,};const WIDE_FOOTER_VIEWPORT: ViewportSpec = {  name: "wide-footer",  width: 1_400,  height: 1_100,  textTolerancePx: 44,  attachmentTolerancePx: 56,};const COMPACT_FOOTER_VIEWPORT: ViewportSpec = {  name: "compact-footer",  width: 430,  height: 932,  textTolerancePx: 56,  attachmentTolerancePx: 56,};const TEXT_VIEWPORT_MATRIX = [  DEFAULT_VIEWPORT,  { name: "tablet", width: 720, height: 1_024, textTolerancePx: 44, attachmentTolerancePx: 56 },  { name: "mobile", width: 430, height: 932, textTolerancePx: 56, attachmentTolerancePx: 56 },  { name: "narrow", width: 320, height: 700, textTolerancePx: 84, attachmentTolerancePx: 56 },] as const satisfies readonly ViewportSpec[];const ATTACHMENT_VIEWPORT_MATRIX = [  DEFAULT_VIEWPORT,  { name: "mobile", width: 430, height: 932, textTolerancePx: 56, attachmentTolerancePx: 56 },  { name: "narrow", width: 320, height: 700, textTolerancePx: 84, attachmentTolerancePx: 56 },] as const satisfies readonly ViewportSpec[]; interface UserRowMeasurement {  measuredRowHeightPx: number;  timelineWidthMeasuredPx: number;  renderedInVirtualizedRegion: boolean;} interface MountedChatView {  [Symbol.asyncDispose]: () => Promise<void>;  cleanup: () => Promise<void>;  measureUserRow: (targetMessageId: MessageId) => Promise<UserRowMeasurement>;  setViewport: (viewport: ViewportSpec) => Promise<void>;  setContainerSize: (viewport: Pick<ViewportSpec, "width" | "height">) => Promise<void>;  router: ReturnType<typeof getRouter>;} function isoAt(offsetSeconds: number): string {  return new Date(BASE_TIME_MS + offsetSeconds * 1_000).toISOString();} function createBaseServerConfig(): ServerConfig {  return {    cwd: "/repo/project",    keybindingsConfigPath: "/repo/project/.t3code-keybindings.json",    keybindings: [],    issues: [],    providers: [      {        provider: "codex",        enabled: true,        installed: true,        version: "0.116.0",        status: "ready",        auth: { status: "authenticated" },        checkedAt: NOW_ISO,        models: [],      },    ],    availableEditors: [],    settings: {      ...DEFAULT_SERVER_SETTINGS,      ...DEFAULT_CLIENT_SETTINGS,    },  };} function createUserMessage(options: {  id: MessageId;  text: string;  offsetSeconds: number;  attachments?: Array<{    type: "image";    id: string;    name: string;    mimeType: string;    sizeBytes: number;  }>;}) {  return {    id: options.id,    role: "user" as const,    text: options.text,    ...(options.attachments ? { attachments: options.attachments } : {}),    turnId: null,    streaming: false,    createdAt: isoAt(options.offsetSeconds),    updatedAt: isoAt(options.offsetSeconds + 1),  };} function createAssistantMessage(options: { id: MessageId; text: string; offsetSeconds: number }) {  return {    id: options.id,    role: "assistant" as const,    text: options.text,    turnId: null,    streaming: false,    createdAt: isoAt(options.offsetSeconds),    updatedAt: isoAt(options.offsetSeconds + 1),  };} function createTerminalContext(input: {  id: string;  terminalLabel: string;  lineStart: number;  lineEnd: number;  text: string;}): TerminalContextDraft {  return {    id: input.id,    threadId: THREAD_ID,    terminalId: `terminal-${input.id}`,    terminalLabel: input.terminalLabel,    lineStart: input.lineStart,    lineEnd: input.lineEnd,    text: input.text,    createdAt: NOW_ISO,  };} function createSnapshotForTargetUser(options: {  targetMessageId: MessageId;  targetText: string;  targetAttachmentCount?: number;  sessionStatus?: OrchestrationSessionStatus;}): OrchestrationReadModel {  const messages: Array<OrchestrationReadModel["threads"][number]["messages"][number]> = [];   for (let index = 0; index < 22; index += 1) {    const isTarget = index === 3;    const userId = `msg-user-${index}` as MessageId;    const assistantId = `msg-assistant-${index}` as MessageId;    const attachments =      isTarget && (options.targetAttachmentCount ?? 0) > 0        ? Array.from({ length: options.targetAttachmentCount ?? 0 }, (_, attachmentIndex) => ({            type: "image" as const,            id: `attachment-${attachmentIndex + 1}`,            name: `attachment-${attachmentIndex + 1}.png`,            mimeType: "image/png",            sizeBytes: 128,          }))        : undefined;     messages.push(      createUserMessage({        id: isTarget ? options.targetMessageId : userId,        text: isTarget ? options.targetText : `filler user message ${index}`,        offsetSeconds: messages.length * 3,        ...(attachments ? { attachments } : {}),      }),    );    messages.push(      createAssistantMessage({        id: assistantId,        text: `assistant filler ${index}`,        offsetSeconds: messages.length * 3,      }),    );  }   return {    snapshotSequence: 1,    projects: [      {        id: PROJECT_ID,        title: "Project",        workspaceRoot: "/repo/project",        defaultModelSelection: {          provider: "codex",          model: "gpt-5",        },        scripts: [],        createdAt: NOW_ISO,        updatedAt: NOW_ISO,        deletedAt: null,      },    ],    threads: [      {        id: THREAD_ID,        projectId: PROJECT_ID,        title: "Browser test thread",        modelSelection: {          provider: "codex",          model: "gpt-5",        },        interactionMode: "default",        runtimeMode: "full-access",        branch: "main",        worktreePath: null,        latestTurn: null,        createdAt: NOW_ISO,        updatedAt: NOW_ISO,        archivedAt: null,        deletedAt: null,        messages,        activities: [],        proposedPlans: [],        checkpoints: [],        session: {          threadId: THREAD_ID,          status: options.sessionStatus ?? "ready",          providerName: "codex",          runtimeMode: "full-access",          activeTurnId: null,          lastError: null,          updatedAt: NOW_ISO,        },      },    ],    updatedAt: NOW_ISO,  };} function buildFixture(snapshot: OrchestrationReadModel): TestFixture {  return {    snapshot,    serverConfig: createBaseServerConfig(),    welcome: {      cwd: "/repo/project",      projectName: "Project",      bootstrapProjectId: PROJECT_ID,      bootstrapThreadId: THREAD_ID,    },  };} function addThreadToSnapshot(  snapshot: OrchestrationReadModel,  threadId: ThreadId,): OrchestrationReadModel {  return {    ...snapshot,    snapshotSequence: snapshot.snapshotSequence + 1,    threads: [      ...snapshot.threads,      {        id: threadId,        projectId: PROJECT_ID,        title: "New thread",        modelSelection: {          provider: "codex",          model: "gpt-5",        },        interactionMode: "default",        runtimeMode: "full-access",        branch: "main",        worktreePath: null,        latestTurn: null,        createdAt: NOW_ISO,        updatedAt: NOW_ISO,        archivedAt: null,        deletedAt: null,        messages: [],        activities: [],        proposedPlans: [],        checkpoints: [],        session: {          threadId,          status: "ready",          providerName: "codex",          runtimeMode: "full-access",          activeTurnId: null,          lastError: null,          updatedAt: NOW_ISO,        },      },    ],  };} function createThreadCreatedEvent(threadId: ThreadId, sequence: number): OrchestrationEvent {  return {    sequence,    eventId: EventId.makeUnsafe(`event-thread-created-${sequence}`),    aggregateKind: "thread",    aggregateId: threadId,    occurredAt: NOW_ISO,    commandId: null,    causationEventId: null,    correlationId: null,    metadata: {},    type: "thread.created",    payload: {      threadId,      projectId: PROJECT_ID,      title: "New thread",      modelSelection: {        provider: "codex",        model: "gpt-5",      },      runtimeMode: "full-access",      interactionMode: "default",      branch: "main",      worktreePath: null,      createdAt: NOW_ISO,      updatedAt: NOW_ISO,    },  };} function sendOrchestrationDomainEvent(event: OrchestrationEvent): void {  rpcHarness.emitStreamValue(WS_METHODS.subscribeOrchestrationDomainEvents, event);} async function waitForWsClient(): Promise<void> {  await vi.waitFor(    () => {      expect(        wsRequests.some(          (request) => request._tag === WS_METHODS.subscribeOrchestrationDomainEvents,        ),      ).toBe(true);    },    { timeout: 8_000, interval: 16 },  );} async function promoteDraftThreadViaDomainEvent(threadId: ThreadId): Promise<void> {  await waitForWsClient();  fixture.snapshot = addThreadToSnapshot(fixture.snapshot, threadId);  sendOrchestrationDomainEvent(    createThreadCreatedEvent(threadId, fixture.snapshot.snapshotSequence),  );  await vi.waitFor(    () => {      expect(useComposerDraftStore.getState().draftThreadsByThreadId[threadId]).toBeUndefined();    },    { timeout: 8_000, interval: 16 },  );} function createDraftOnlySnapshot(): OrchestrationReadModel {  const snapshot = createSnapshotForTargetUser({    targetMessageId: "msg-user-draft-target" as MessageId,    targetText: "draft thread",  });  return {    ...snapshot,    threads: [],  };} function withProjectScripts(  snapshot: OrchestrationReadModel,  scripts: OrchestrationReadModel["projects"][number]["scripts"],): OrchestrationReadModel {  return {    ...snapshot,    projects: snapshot.projects.map((project) =>      project.id === PROJECT_ID ? { ...project, scripts: Array.from(scripts) } : project,    ),  };} function setDraftThreadWithoutWorktree(): void {  useComposerDraftStore.setState({    draftThreadsByThreadId: {      [THREAD_ID]: {        projectId: PROJECT_ID,        createdAt: NOW_ISO,        runtimeMode: "full-access",        interactionMode: "default",        branch: null,        worktreePath: null,        envMode: "local",      },    },    projectDraftThreadIdByProjectId: {      [PROJECT_ID]: THREAD_ID,    },  });} function createSnapshotWithLongProposedPlan(): OrchestrationReadModel {  const snapshot = createSnapshotForTargetUser({    targetMessageId: "msg-user-plan-target" as MessageId,    targetText: "plan thread",  });  const planMarkdown = [    "# Ship plan mode follow-up",    "",    "- Step 1: capture the thread-open trace",    "- Step 2: identify the main-thread bottleneck",    "- Step 3: keep collapsed cards cheap",    "- Step 4: render the full markdown only on demand",    "- Step 5: preserve export and save actions",    "- Step 6: add regression coverage",    "- Step 7: verify route transitions stay responsive",    "- Step 8: confirm no server-side work changed",    "- Step 9: confirm short plans still render normally",    "- Step 10: confirm long plans stay collapsed by default",    "- Step 11: confirm preview text is still useful",    "- Step 12: confirm plan follow-up flow still works",    "- Step 13: confirm timeline virtualization still behaves",    "- Step 14: confirm theme styling still looks correct",    "- Step 15: confirm save dialog behavior is unchanged",    "- Step 16: confirm download behavior is unchanged",    "- Step 17: confirm code fences do not parse until expand",    "- Step 18: confirm preview truncation ends cleanly",    "- Step 19: confirm markdown links still open in editor after expand",    "- Step 20: confirm deep hidden detail only appears after expand",    "",    "```ts",    "export const hiddenPlanImplementationDetail = 'deep hidden detail only after expand';",    "```",  ].join("\n");   return {    ...snapshot,    threads: snapshot.threads.map((thread) =>      thread.id === THREAD_ID        ? Object.assign({}, thread, {            proposedPlans: [              {                id: "plan-browser-test",                turnId: null,                planMarkdown,                implementedAt: null,                implementationThreadId: null,                createdAt: isoAt(1_000),

Lines 1–500 of 2659.