DevSync
The plan is organized into sequential stages, each building on a stable version of the one before it. This ordering isn't arbitrary — it reflects the actual dependency chain of the system: authentication has to exist before anything can be "owned"; repositories have to exist before files/folders can belong to something; the file tree has to exist before an editor has anything to open; and the editor has to exist before real-time sync has content worth syncing.
Goal: A user can register, log in, and have a persistent session.
Usermodel (username, email, password hash, profileImage)POST /api/auth/register,POST /api/auth/login,GET /api/auth/profile- JWT issuance on login;
protectmiddleware for guarded REST routes - Frontend:
AuthContext,LoginForm,RegisterForm,ProtectedRoute/PublicOnlyRoute, token persisted client-side and restored on load
Exit criteria: A user can register, log in, refresh the page, and remain logged in; unauthenticated users are redirected away from protected routes.
Goal: A logged-in user can create and manage repositories.
Repositorymodel (name, description, owner, isPublic, collaborators)- Full CRUD:
POST/GET/PUT/DELETE /api/repositories, plusGET /api/repositories/:id - Ownership-based authorization inline in each controller (owner-only for update/delete)
- Frontend:
DashboardPage,useDashboard,RepoList,RepoCard,CreateRepoModal
Exit criteria: A user can create a repository, see it on their dashboard, update its details, and delete it; only the owner can modify or delete it.
Goal: Repositories can hold a real file/folder structure, fully through REST first.
Foldermodel (name, repository, parentFolder, createdBy) andFilemodel (name, content, repository, createdBy, folder)- Full CRUD for both:
POST/GET/PUT/DELETE /api/folders/...,/api/files/... - Authorization extended to "owner or collaborator" across file/folder endpoints
- Frontend:
FileTree,TreeNode,FileContextMenu,useFileTree
Exit criteria: A collaborator can build out a folder/file structure inside a repository and see it correctly nested in the sidebar. Real-time propagation of these changes comes in a later stage — at this point the tree updates only for the user performing the action.
Bug resolved during this stage: the File schema was initially missing its folder reference field, so newly created files couldn't be correctly nested under a folder. Fixed by adding folder: { type: ObjectId, ref: 'Folder', default: null }.
Goal: File/folder operations and online presence become visible live to every collaborator, not just the person who performed the action.
- Socket.IO server attached to the same HTTP server as Express; JWT-based handshake authentication (
socketAuthMiddleware) - Room model: one room per repository (
repo:<repositoryId>), joined viaworkspace:join workspace.socket.js— room lifecycle;presence.socket.js— in-memory presence tracking andpresence:updatebroadcasts- REST controllers for file/folder create/rename/delete extended to emit their matching socket event after a successful write
- Frontend:
useSockethook owns the socket connection lifecycle end-to-end;usePresence,PresenceList,UserPresenceRow
Exit criteria: Two browser sessions in the same repository see each other's file/folder changes and each other's online status without refreshing.
Goal: The Monaco editor supports live, multi-user text synchronization.
editor.socket.js—editor:join/editor:change→editor:updatebroadcast (never echoed back to the sender)- Frontend:
useEditorhook — local keystrokes emit immediately over the socket for real-time feel, plus a debounced (~800ms) REST auto-save (PUT /api/files/:fileId) for persistence ignoreRemoteChangeref guard to prevent a remotely-applied update from being re-broadcast as if it were a local editEditorPane,TabBar,EditorPlaceHolder, sync status indicator inStatusBar
Exit criteria: Two users with the same file open see each other's keystrokes appear live, and the content is durably saved regardless of socket connectivity.
Bug resolved during this stage: a timing bug where useEditor initialized before useSocket had created the socket instance, so getSocket() returned null and the editor:update listener never registered. Fixed by having useSocket hold the socket instance in state and pass it down explicitly as a parameter, so dependent effects re-run once the real instance exists.
Goal: Bring the interface to a consistent, professional baseline before adding further features.
- Tailwind v4 migration, including moving custom design tokens into
@themeCSS blocks (a v4 requirement, not a stylistic choice) - Shared component layer (
Button,Input,Modal,AvatarBadge,EmptyState,FileIcon) to remove one-off styled elements AppShell,Navbar,Sidebar,StatusBarfinalized as the persistent layout frame- Various import-path and API base-URL mismatches identified and corrected across the frontend (
AuthContextimport paths, Axios base URL config)
Exit criteria: The app has one consistent visual language end-to-end, with no ad-hoc inline styling competing with the design tokens.
Goal: Replace ad-hoc, direct collaborator addition with a proper invite/accept flow.
Invitationmodel withpending/accepted/rejectedstatus, 7-day expiry, and three purpose-built indexes (see5_BackendSchema.md)POST /api/invitations(send),GET /api/invitations/received(list mine),PUT /api/invitations/:id/accept,PUT /api/invitations/:id/reject- Ownership, duplicate-invite, self-invite, and already-collaborator checks in the send flow; ownership and expiry checks in accept/reject
- Frontend:
InviteForm,ReceivedInvitations,UserPresenceRow,useInvitations,invitation.service.js
Status: Backend controller and routes are implemented; frontend hook and components are wired for the received-invitations list and the invite form. Remaining work: final UI polish on the invite/accept experience, and reconciling this flow with the older direct addCollaborator endpoint on repositoryController (see Section 10).
Goal: Let a user download a repository's current file/folder structure as a ZIP archive.
utils/zipRepository.jsandservices/exportService.jsare scaffolded but currently empty — this is the next concrete implementation task.- Planned approach: walk the repository's
Folder/Filedocuments to reconstruct the tree in-memory, stream it into a ZIP (e.g. viaarchiveror a similar library), and serve it as a file download from a new authenticated endpoint (e.g.GET /api/repositories/:id/export). - Frontend
ExportButtonalready exists in the workspace toolbar and simply needs to be wired to the new endpoint once it exists.
Exit criteria: A collaborator can click Export and receive a ZIP that, when extracted, reproduces the repository's folder/file structure and contents.
These are known rough edges, explicitly tracked rather than silently left in the codebase:
- Duplicate collaborator-add paths.
repositoryController.addCollaborator(direct, by email, owner-only) still exists alongside the newer invitation flow. Decide whether direct-add is kept as an owner shortcut or removed in favor of invitations being the only path. Collaborator.jsmodel file is empty and unused. Either remove it or repurpose it if per-collaborator roles are introduced later.- No cascade delete for repositories. Deleting a repository currently orphans its files, folders, and invitations at the database level.
- Socket handler placeholders.
handlers/file.socket.jsandhandlers/folder.socket.jsexist as empty placeholder modules — real-time file/folder broadcasting is currently implemented directly inside the REST controllers instead. Either remove these placeholder files or formally document that this is the intended final architecture (per2_TRD.md, Section 3).
For placement/interview prep purposes, the stages above map directly onto talking points already prepared:
- Feature-Sliced Design → Stage 6
- Socket singleton pattern (
useSocketowning the connection lifecycle) → Stage 4/5 ignoreRemoteChangeref pattern → Stage 5- REST-as-source-of-truth / sockets-only-broadcast architecture rule → Stage 4 (TRD Section 3)
- JWT reused across REST and socket auth → Stage 1 (TRD Section 4)