nexus/.planning/phases/21-chat-foundation/21-00-PLAN.md

268 lines
9.3 KiB
Markdown

---
phase: 21-chat-foundation
plan: 00
type: execute
wave: 0
depends_on: []
files_modified:
- server/src/__tests__/chat-service.test.ts
- server/src/__tests__/chat-routes.test.ts
- ui/src/components/ChatMarkdownMessage.test.tsx
- ui/src/components/ChatInput.test.tsx
autonomous: true
requirements: [HIST-01, CHAT-02, CHAT-03, CHAT-04, CHAT-05, CHAT-06, INPUT-07]
must_haves:
truths:
- "Test stubs exist and can be executed by vitest without errors"
- "Each stub has describe blocks with placeholder tests that skip or pass trivially"
artifacts:
- path: "server/src/__tests__/chat-service.test.ts"
provides: "Test scaffold for chat service (HIST-01, CHAT-04, CHAT-05, CHAT-06)"
contains: "describe.*chatService"
- path: "server/src/__tests__/chat-routes.test.ts"
provides: "Test scaffold for chat routes (POST conversation, GET list, POST message)"
contains: "describe.*chatRoutes"
- path: "ui/src/components/ChatMarkdownMessage.test.tsx"
provides: "Test scaffold for markdown rendering (CHAT-02, CHAT-03)"
contains: "describe.*ChatMarkdownMessage"
- path: "ui/src/components/ChatInput.test.tsx"
provides: "Test scaffold for keyboard shortcuts (INPUT-07)"
contains: "describe.*ChatInput"
key_links: []
---
<objective>
Create Wave 0 test stubs for the four key test files needed by Plans 01-05.
Purpose: Satisfy the Nyquist rule — every implementation task must have a pre-existing test file with describe blocks and placeholder expectations. Plans 01 and 02 depend on these stubs existing before they execute.
Output: Four test files with describe/it blocks that vitest can discover and run.
</objective>
<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/phases/21-chat-foundation/21-RESEARCH.md
<interfaces>
From server/src/__tests__/activity-routes.test.ts (reference pattern for server tests):
```typescript
import express from "express";
import request from "supertest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import { errorHandler } from "../middleware/index.js";
const mockService = vi.hoisted(() => ({ list: vi.fn(), create: vi.fn() }));
vi.mock("../services/activity.js", () => ({ activityService: () => mockService }));
function createApp() {
const app = express();
app.use(express.json());
// ... mock actor middleware
}
```
From ui/src/components/MarkdownBody.test.tsx (reference pattern for UI component tests):
```typescript
// @vitest-environment node
import { describe, expect, it } from "vitest";
import { renderToStaticMarkup } from "react-dom/server";
import { ThemeProvider } from "../context/ThemeContext";
```
</interfaces>
</context>
<tasks>
<task type="auto">
<name>Task 1: Create server-side test stubs (chat-service + chat-routes)</name>
<files>server/src/__tests__/chat-service.test.ts, server/src/__tests__/chat-routes.test.ts</files>
<read_first>
- server/src/__tests__/activity-routes.test.ts (full file — reference for mock pattern, createApp, supertest usage)
</read_first>
<action>
Create `server/src/__tests__/chat-service.test.ts`:
```typescript
import { describe, it, expect } from "vitest";
describe("chatService", () => {
describe("createConversation", () => {
it.todo("creates a conversation row with companyId");
it.todo("returns the created conversation with id and timestamps");
});
describe("listConversations", () => {
it.todo("returns conversations sorted by updatedAt DESC");
it.todo("excludes soft-deleted conversations");
it.todo("supports cursor-based pagination with hasMore");
it.todo("limits results to max 100");
});
describe("getConversation", () => {
it.todo("returns conversation by id");
it.todo("throws notFound for non-existent conversation");
it.todo("throws notFound for soft-deleted conversation");
});
describe("updateConversation", () => {
it.todo("updates title");
it.todo("sets pinnedAt timestamp");
it.todo("clears pinnedAt when set to null");
it.todo("sets archivedAt timestamp");
it.todo("bumps updatedAt on every update");
});
describe("softDeleteConversation", () => {
it.todo("sets deletedAt timestamp");
it.todo("throws notFound if already deleted");
});
describe("addMessage", () => {
it.todo("inserts a message row with conversationId and role");
it.todo("bumps conversation updatedAt after insert");
it.todo("auto-sets title from first user message when title is null");
it.todo("does not overwrite existing title on subsequent messages");
});
describe("listMessages", () => {
it.todo("returns messages for conversation sorted by createdAt DESC");
it.todo("supports cursor-based pagination");
});
});
```
Create `server/src/__tests__/chat-routes.test.ts`:
```typescript
import { describe, it, expect } from "vitest";
describe("chatRoutes", () => {
describe("POST /companies/:companyId/conversations", () => {
it.todo("creates a conversation and returns 201");
it.todo("accepts optional title and agentId");
});
describe("GET /companies/:companyId/conversations", () => {
it.todo("returns paginated conversation list");
it.todo("supports cursor query param");
});
describe("GET /conversations/:id", () => {
it.todo("returns conversation by id");
it.todo("returns 404 for non-existent conversation");
});
describe("PATCH /conversations/:id", () => {
it.todo("updates conversation fields");
});
describe("DELETE /conversations/:id", () => {
it.todo("soft-deletes and returns 204");
});
describe("POST /conversations/:id/messages", () => {
it.todo("creates a message and returns 201");
it.todo("rejects invalid role");
});
describe("GET /conversations/:id/messages", () => {
it.todo("returns paginated message list");
});
});
```
Both files use `it.todo()` which vitest marks as skipped — they run without error and serve as scaffolds for implementation tasks to fill in.
</action>
<verify>
<automated>cd /opt/nexus && pnpm vitest run server/src/__tests__/chat-service.test.ts server/src/__tests__/chat-routes.test.ts --reporter=verbose 2>&1 | tail -5</automated>
</verify>
<done>Server test stubs exist with describe/it.todo blocks covering all chatService methods and chatRoutes endpoints. Vitest runs them without error.</done>
</task>
<task type="auto">
<name>Task 2: Create UI test stubs (ChatMarkdownMessage + ChatInput)</name>
<files>ui/src/components/ChatMarkdownMessage.test.tsx, ui/src/components/ChatInput.test.tsx</files>
<read_first>
- ui/src/components/MarkdownBody.test.tsx (reference for UI component test pattern — renderToStaticMarkup, ThemeProvider wrapper)
- ui/src/components/IssueRow.test.tsx (reference for component test with RTL if used)
</read_first>
<action>
Create `ui/src/components/ChatMarkdownMessage.test.tsx`:
```tsx
// @vitest-environment node
import { describe, it, expect } from "vitest";
describe("ChatMarkdownMessage", () => {
describe("markdown rendering (CHAT-02)", () => {
it.todo("renders plain text as paragraph");
it.todo("renders code blocks with hljs classes for syntax highlighting");
it.todo("renders GFM tables");
it.todo("renders headings, lists, and links");
});
describe("code block features (CHAT-03)", () => {
it.todo("renders language label from code fence");
it.todo("renders copy button with aria-label");
it.todo("extracts code text content for clipboard");
});
});
```
Create `ui/src/components/ChatInput.test.tsx`:
```tsx
// @vitest-environment node
import { describe, it, expect } from "vitest";
describe("ChatInput", () => {
describe("keyboard shortcuts (INPUT-07)", () => {
it.todo("calls onSend when Enter is pressed without Shift");
it.todo("inserts newline when Shift+Enter is pressed");
it.todo("clears input when Escape is pressed");
it.todo("does not send when input is empty");
});
describe("auto-resize (INPUT-01)", () => {
it.todo("textarea has max-height constraint");
});
describe("submit state", () => {
it.todo("disables send button when isSubmitting is true");
});
});
```
Both files use `it.todo()` for the same reason as the server stubs.
</action>
<verify>
<automated>cd /opt/nexus && pnpm vitest run ui/src/components/ChatMarkdownMessage.test.tsx ui/src/components/ChatInput.test.tsx --reporter=verbose 2>&1 | tail -5</automated>
</verify>
<done>UI test stubs exist with describe/it.todo blocks covering ChatMarkdownMessage rendering and ChatInput keyboard shortcuts. Vitest runs them without error.</done>
</task>
</tasks>
<verification>
- All four test files exist and vitest discovers them
- `pnpm vitest run server/src/__tests__/chat-service.test.ts` exits 0
- `pnpm vitest run server/src/__tests__/chat-routes.test.ts` exits 0
- `pnpm vitest run ui/src/components/ChatMarkdownMessage.test.tsx` exits 0
- `pnpm vitest run ui/src/components/ChatInput.test.tsx` exits 0
</verification>
<success_criteria>
- Four test stub files created with describe blocks matching the requirements they cover
- Vitest runs all four files without errors (todo tests are skipped, not failed)
- Implementation plans (01-05) can reference these files in their verify commands
</success_criteria>
<output>
After completion, create `.planning/phases/21-chat-foundation/21-00-SUMMARY.md`
</output>