Project Documentation
Developer reference for human and agent contributors. Keep this file up to date as the project evolves.
Roject is a self-hosted, agent-based IDE for working with files and projects. It provides specialised editors for different file types — a WYSIWYG HTML editor, a CodeMirror-backed code editor, and more editors to come — all within a multi-panel, tab-based workspace. Projects can be hosted on the Roject server (remote) or opened directly from the local filesystem (local), making it usable both as a lightweight self-hosted CMS and as a full desktop development environment. It is designed for individual developers or small teams and has no external database dependency.
User accounts with registration, login, logout, and account deletion.
Groups and projects with member management (viewer / editor / admin roles).
Each project gets a real directory on disk at storage/<uuid>/root/
with a default index.html on creation.
A full editor page (/editor.html) with a 3-panel resizable layout
(Left / Center / Right). Each panel holds one or more sections side by side,
each section holds a <tab-container>. Tabs are draggable
between containers. The Left panel shows the file tree with create, rename,
and delete for files and folders. The Center panel holds the WYSIWYG HTML
editor (iframe, contenteditable, MutationObserver, undo/redo,
Ctrl+S save). The Right panel is empty by default and receives dropped tabs.
A FileEditorRegistry routes files to the correct panel by suffix.
HTML files open in html-editor-panel; all other known text formats
open in code-panel (CodeMirror 5, syntax highlighting, dark theme,
Pin/Undo/Redo/Save toolbar). The registry checks an optional project-level
workspace/editor/file-editors.json first, then falls back to
in-memory defaults. Unknown extensions show an error in the file tree instead
of attempting to open.
A rojo-chat-panel provides a streaming AI chat interface backed
by a LangChain + OpenAI-compatible model. Each panel instance holds its own
conversation session in memory.
A reusable <confirm-dialog> component replaces browser
confirm() for destructive actions (currently project deletion).
Items below are ordered by priority. Major planned features first, then smaller open improvements.
Deploy the Node.js/Express app to a publicly reachable server and wire up a CI pipeline that builds, tests, and deploys automatically on push. The app's file-based storage and no-database design make it straightforward to host on any VPS: nginx as reverse proxy, PM2 or a systemd service for the Node.js process. CI can run as a self-hosted Gitea/Forgejo Actions runner (matching the existing self-hosted git remote) or mirror to GitHub Actions. This is infrastructure work rather than feature work — it should be done once and early because everything else ships faster and more safely once a real deployment target and regression-catching pipeline exist.
Package Roject as a standalone desktop application using Electron. Since the frontend is already plain HTML/JS/CSS, the Electron integration is mostly structural: the Express server runs as a child process inside Electron's main process, and the BrowserWindow is pointed at it. No frontend changes are needed to make the existing UI run inside Electron.
Local filesystem access follows from this almost for free: the file tree is
extended to browse arbitrary directories on the host machine using Node.js
fs directly, rather than being restricted to the
storage/<uuid>/root/ paths managed by the server. This is
what turns Roject from a hosted CMS into something closer to VS Code — the
user can open any folder on their machine as a project.
These two features are treated as a single unit of work: there is no point shipping Electron without local filesystem access, and local filesystem access in the browser would require the File System Access API with significant UX friction. Electron is the cleaner path.
Git integration inside the editor: file status indicators in the tree, staging, commit, push and pull, and eventually diffs and history. This depends on local filesystem access (feature 2) and follows naturally from it — once Roject can open an arbitrary local directory, the git repo that directory belongs to is already there.
The implementation uses simple-git, a thin Node.js wrapper around
the git CLI, which keeps the dependency surface small and relies on the user's
existing git installation. An alternative is isomorphic-git (pure
JS, works in the browser too), but the CLI wrapper is simpler for a first
iteration. A new panel in the editor displays repo status and exposes the
common operations.
A tunneling feature that allows local devices — a main workstation running Stable Diffusion, a local LLM, a GDScript language server, or any other service — to be accessible to authorised Roject users over the internet, routed through the Roject server.
The architecture: a small local agent (a Node.js script, or a built-in Roject feature) connects to the Roject server via a persistent WebSocket, identifying itself with a secret key. The server maps that key to a Roject user and permission record. When an authorised Roject user (e.g. on their phone) makes a request to the relay endpoint, the server forwards it through the WebSocket to the local agent, which proxies it to the configured local port, and returns the response the same way.
The primary use case is mobile-to-desktop: use a phone as a thin client while the main machine handles all heavy processing (image generation, inference, language server completions). The secret key is the only credential needed — it is registered once in Roject's user system and then shared with the local agent. A single pass-through maps one local port to one authorised user.
This feature is architecturally independent of Electron and mobile and lives entirely on the server, so it can be developed in parallel with the desktop work. It is placed here because its primary value is unlocked only once mobile access (feature 5) also exists.
Make Roject usable on a phone or tablet. The quickest path given the existing web frontend is a Progressive Web App (PWA) — a manifest file and a service worker. This costs almost nothing to add, works in Safari and Chrome on both Android and iOS without an app store, and covers the core use case of reaching the editor and the tunnel relay from a mobile browser.
If native capabilities are later needed (background processing, push notifications, deeper OS integration), Capacitor can wrap the same web app in a native shell without requiring a framework rewrite. A full React Native or Flutter rewrite would be a significant departure from the existing Web Components architecture and is not planned.
The main challenge of a mobile editing experience is the code editor — CodeMirror on a touchscreen is not great for authoring. The realistic mobile workflow is lighter interaction: browsing files, reading output, triggering generation requests through the tunnel relay, and simple edits rather than heavy coding.
confirm() instead of the custom <confirm-dialog>.MediaViewerPanel is planned.
Node.js + Express, TypeScript compiled on the fly with ts-node.
No database — all data lives as JSON files in data/
(auto-created on first run). Auth uses express-session +
bcryptjs. All entity IDs are UUIDs via
crypto.randomUUID() — no central counter, safe for parallel
instances. Start the server with npm start.
The Editor singleton (src/editor/Editor.ts) is the
central hub of the frontend editor — it owns all open document state, the
FileEditorRegistry, and the five events that panels and the tab
container subscribe to (onDocumentOpened,
onDocumentDirty, onDocumentSaved,
onFilesChanged, onFileTypeUnknown). For the full
event reference and the TypeScript compilation split between client and server,
see the
Editor Singleton reference.
Vanilla HTML, raw CSS (no Tailwind, no framework). Every UI component is a
custom element with its own .ts and .css file in
src/components/<name>/. CSS uses the element tag as root
selector with display: block. TypeScript compiles to
public/components/ via tsconfig.client.json
(module: ESNext, moduleResolution: bundler, no
bundler). HTML pages load components with
<script type="module">. Shared state uses a module-level
singleton (editor-state.ts) rather than globals.
Build with npm run build.
A personal TypeScript library lives as a git submodule at
src/library-ts/. It has two parts: browser/
(DOM-capable) and node/ (Node.js only). The browser part is
compiled separately via TypeScript project references
(src/library-ts/browser/tsconfig.roject.json, strict: false)
into public/library-ts/browser/. The node part is included by
tsconfig.ts-node.json (extends server config, strictNullChecks: false).