nexus/.planning/milestones/v1.3-phases/25-file-system/25-RESEARCH.md
Nexus Dev ffc7b130e4 chore: archive v1.3 phase directories to milestones/
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 03:55:48 +00:00

76 lines
5.3 KiB
Markdown

# Phase 25: File System — Codebase Research
**Gathered:** 2026-04-01
**Status:** Complete
## Existing Patterns
### Storage System
- **StorageService** in `server/src/storage/service.ts` handles file persistence via a provider abstraction (local disk or S3)
- **LocalDiskProvider** in `server/src/storage/local-disk-provider.ts` writes files to `{instanceRoot}/data/storage/`
- Object keys follow `{companyId}/{namespace}/{year}/{month}/{day}/{uuid}-{stem}{ext}` pattern
- Storage service computes SHA-256 hash, validates inputs, sanitizes filenames
- `PutFileInput` requires: companyId, namespace, originalFilename, contentType, body (Buffer)
- `PutFileResult` returns: provider, objectKey, contentType, byteSize, sha256, originalFilename
### Asset Upload (existing reference)
- `server/src/routes/assets.ts` uses **multer v2** with memory storage for single file upload
- Pattern: `multer.memoryStorage()` + `limits: { fileSize: MAX_ATTACHMENT_BYTES, files: 1 }`
- Manual `runSingleFileUpload` wrapper around multer's middleware
- After upload: calls `storage.putFile()`, then `assetService.create()` to write DB row
- Content served via `GET /api/assets/:assetId/content` which pipes `storage.getObject()` stream
- `MAX_ATTACHMENT_BYTES` defaults to 10MB (from `server/src/attachment-types.ts`)
- Allowed content types configurable via `PAPERCLIP_ALLOWED_ATTACHMENT_TYPES` env var
### Database Schema
- **Drizzle ORM** with PostgreSQL, migrations in `packages/db/src/migrations/` (currently at 0052)
- Schema files in `packages/db/src/schema/`, exported from `index.ts`
- Convention: `pgTable()` with `uuid("id").primaryKey().defaultRandom()`, timestamps with timezone
- Index callbacks use object syntax: `(table) => ({ indexName: index("idx_name").on(table.col) })`
- Chat tables: `chat_conversations`, `chat_messages`, `chat_message_bookmarks`
- `chat_messages` columns: id, conversationId, role, content, agentId, messageType, createdAt, updatedAt
### Chat Routes
- `server/src/routes/chat.ts``chatRoutes(db: Db)` returns Express Router
- Mounted at `/api` in `server/src/app.ts` line 160: `api.use(chatRoutes(db))`
- Currently does NOT receive `storageService` — only `db`
- Uses `assertBoard(req)` + `assertCompanyAccess(req, companyId)` for auth
- Uses validators from `@paperclipai/shared` (zod schemas)
- Message creation via `svc.addMessage(conversationId, { role, content, agentId })`
### Chat Service
- `server/src/services/chat.ts``chatService(db)` returns object with methods
- Methods: listConversations, createConversation, getConversation, updateConversation, addMessage, editMessage, truncateMessagesAfter, addSystemMessage, streamEcho, searchMessages, toggleBookmark, getBookmarks, branchConversation, listBranches, exportConversation
### Shared Types
- `packages/shared/src/types/chat.ts` — ChatConversation, ChatMessage, ChatConversationListResponse, etc.
- ChatMessage has: id, conversationId, role, content, agentId, messageType, createdAt, updatedAt
- NO file/attachment fields exist on ChatMessage yet
### UI Components
- `ChatInput.tsx` — textarea with slash commands and @mentions; `onSend(content: string)` callback (text only)
- `ChatMessage.tsx` — dispatches to specialized components by messageType (spec_card, handoff, etc.)
- `ChatPanel.tsx` — orchestrates conversation selection, message list, input, streaming, bookmarks
- `chatApi` in `ui/src/api/chat.ts` — API client using fetch-based `api` helper
### Migration Pattern
- SQL files named `NNNN_descriptive_name.sql`
- Journal in `packages/db/src/migrations/meta/_journal.json`
- Snapshots in `packages/db/src/migrations/meta/NNNN_snapshot.json`
- Next migration index: 0053
## Key Design Decisions (Claude's Discretion)
1. **Reuse existing StorageService** — Files will be stored via the same `StorageService` (local disk or S3) used by assets. This means files live in `{instanceRoot}/data/storage/` under company-namespaced object keys, NOT in a separate `files/` git repo. The git versioning requirement (FILE-09, FILE-10) is out of scope for this batch of requirements (FILE-01 through FILE-06 only).
2. **New `chat_files` table** — A new Drizzle schema table tracking file metadata: id, companyId, conversationId, messageId, filename, originalFilename, mimeType, sizeBytes, objectKey (StorageService key), sha256, source (user_upload | agent_generated), createdAt.
3. **New `chat_file_references` table** — Enables a file to be referenced from multiple messages/conversations without re-uploading the binary.
4. **Extend chat routes** — Add upload endpoint to chat routes (`POST /api/conversations/:id/files`) rather than creating a separate file routes module. This keeps the chat domain cohesive.
5. **Extend ChatMessage type** — Add optional `files` array to ChatMessage response (populated via join or separate query). Messages with files get `messageType: "file_attachment"` or files are included as metadata alongside normal messages.
6. **ChatInput gets file drop zone** — Add drag-and-drop, paste, and button-based file selection to ChatInput. Files are uploaded immediately on drop/paste, and the pending file IDs are sent with the message.
7. **ChatFilePreview component** — Renders inline image previews, syntax-highlighted code previews (reusing existing rehype-highlight from Phase 21), and download buttons for other file types.