Plans cover FILE-01 through FILE-06: DB schema + shared types (wave 1), server file service + routes and UI file upload (wave 2, parallel), file preview components + full wiring (wave 3). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
5.3 KiB
Phase 25: File System — Codebase Research
Gathered: 2026-04-01 Status: Complete
Existing Patterns
Storage System
- StorageService in
server/src/storage/service.tshandles file persistence via a provider abstraction (local disk or S3) - LocalDiskProvider in
server/src/storage/local-disk-provider.tswrites 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
PutFileInputrequires: companyId, namespace, originalFilename, contentType, body (Buffer)PutFileResultreturns: provider, objectKey, contentType, byteSize, sha256, originalFilename
Asset Upload (existing reference)
server/src/routes/assets.tsuses multer v2 with memory storage for single file upload- Pattern:
multer.memoryStorage()+limits: { fileSize: MAX_ATTACHMENT_BYTES, files: 1 } - Manual
runSingleFileUploadwrapper around multer's middleware - After upload: calls
storage.putFile(), thenassetService.create()to write DB row - Content served via
GET /api/assets/:assetId/contentwhich pipesstorage.getObject()stream MAX_ATTACHMENT_BYTESdefaults to 10MB (fromserver/src/attachment-types.ts)- Allowed content types configurable via
PAPERCLIP_ALLOWED_ATTACHMENT_TYPESenv var
Database Schema
- Drizzle ORM with PostgreSQL, migrations in
packages/db/src/migrations/(currently at 0052) - Schema files in
packages/db/src/schema/, exported fromindex.ts - Convention:
pgTable()withuuid("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_messagescolumns: id, conversationId, role, content, agentId, messageType, createdAt, updatedAt
Chat Routes
server/src/routes/chat.ts—chatRoutes(db: Db)returns Express Router- Mounted at
/apiinserver/src/app.tsline 160:api.use(chatRoutes(db)) - Currently does NOT receive
storageService— onlydb - 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, bookmarkschatApiinui/src/api/chat.ts— API client using fetch-basedapihelper
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)
-
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 separatefiles/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). -
New
chat_filestable — 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. -
New
chat_file_referencestable — Enables a file to be referenced from multiple messages/conversations without re-uploading the binary. -
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. -
Extend ChatMessage type — Add optional
filesarray to ChatMessage response (populated via join or separate query). Messages with files getmessageType: "file_attachment"or files are included as metadata alongside normal messages. -
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.
-
ChatFilePreview component — Renders inline image previews, syntax-highlighted code previews (reusing existing rehype-highlight from Phase 21), and download buttons for other file types.