# 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.