Rojects

Project Documentation

Roject

Developer reference for human and agent contributors. Keep this file up to date as the project evolves.

Project Outline

What it is

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.

What exists now

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). An EmailService static facade (source/server/email/) provides a single sendEmail() entry point backed by a swappable EmailSender interface; the default implementation is SMTPEmailSender (Nodemailer), configured via environment variables.

The app is deployed and publicly accessible at https://roject.rokojori.com on Server A. nginx handles TLS termination and reverse-proxies to the Node.js process managed by a systemd service. Deployment is currently manual (git pull && npm run build && systemctl restart roject); CI automation is the remaining open item.

What still needs work

Items below are ordered by priority. Major planned features first, then smaller open improvements.

1 — Local filesystem access

The Electron shell is done (see below). The remaining work is extending the file tree 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.

Node.js fs file tree extension local mode

2 — Remote projects in Electron

The Electron app currently runs a fully local Express server with its own data store — it shares the same identity as the web version (via rokojori-auth) but not the same projects. The next step is to allow the Electron app to also connect to a remote Roject server (e.g. roject.rokojori.com) and list, open, and edit projects hosted there, alongside any local filesystem projects.

The Electron app already holds a valid JWT and can send it as an Authorization: Bearer header. Connecting to a remote server is therefore a matter of pointing a second BrowserWindow (or a panel in the existing window) at the remote URL and injecting the token — no new auth work needed.

remote Roject server Authorization header mixed local + remote

3 — Local git repository integration

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.

simple-git git CLI new panel

4 — Internet tunnel / port pass-through relay

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.

WebSocket relay HTTP proxy secret key / auth local agent

5 — Mobile app (PWA first, native shell later)

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.

PWA manifest + service worker Capacitor (later) no React Native

Done — Electron desktop app shell

Roject runs as a standalone desktop application on Windows. The Express server starts in-process inside Electron's main process. A custom login window (electron/login.html) collects credentials and calls POST https://account.rokojori.com/api/auth/login directly from the main process — no browser redirect, no cookie. Tokens are stored in userData/tokens.json and re-used across sessions.

All HTTP requests from the BrowserWindow to localhost have Authorization: Bearer <accessToken> injected automatically via session.webRequest.onBeforeSendHeaders — the frontend requires zero changes. Expired tokens are refreshed via POST /api/auth/refresh and the page is reloaded transparently. Any navigation away from localhost is intercepted and redirected to the Electron login window instead.

Known issue: ELECTRON_RUN_AS_NODE=1 is set by VS Code / Claude Code, which makes Electron behave as plain Node.js. The launcher script (scripts/launch-electron.js) deletes this variable before spawning the binary. Run with npm run electron:dev or node scripts/launch-electron.js after the build.

electron/main.ts webRequest header injection token persistence ELECTRON_RUN_AS_NODE workaround

Done — CI/CD pipeline

Auto-deploy on push to main via a Gitea webhook calling POST /api/deploy on the Roject server. The endpoint verifies the X-Gitea-Signature HMAC-SHA256 signature, checks the branch is main, responds immediately, then spawns a detached bash process that runs git pull && npm run build && systemctl restart roject. The detached process survives the systemctl restart that kills the parent.

Gitea webhook HMAC-SHA256 detached spawn

Done — Centralized auth: rokojori-auth

The standalone auth service rokojori-auth is built and live at account.rokojori.com. It handles registration, login, JWT issuance, refresh token rotation, password reset, roles (user / admin / superadmin), products, global settings, rate limiting, and account deletion. Roject has not yet been integrated — that is the next step.

See auth-restructure for the full plan and the rokojori-auth workspace for implementation details.

rokojori-auth account.rokojori.com JWT centralized identity

Done — Roject integration with rokojori-auth

Roject is now a JWT-validating client. The local session-based user system has been removed and replaced with a JWT verification middleware that reads the shared accessToken cookie on .rokojori.com. Expired tokens are transparently refreshed via account.rokojori.com/api/auth/refresh-session?redirect=.... Login and logout links in app-nav point to account.rokojori.com. All data references use userId from the JWT payload. A GET /api/auth/logout?redirect=... endpoint was also added to rokojori-auth to support browser-based logout links.

JWT middleware cookie-parser jsonwebtoken account.rokojori.com

Smaller open improvements

  • Unauthenticated users should land on a landing screen that explains the app and shows a login link, rather than crashing on dashboard components.
  • Components that fetch data (groups, projects, etc.) do not handle 401 responses gracefully — they crash when the API returns an error object instead of an array.
  • The Right panel has no default content and relies on manual tab dragging to populate.
  • Portrait mode's secondary section switcher (when a panel has multiple side-by-side sections) is not yet wired up.
  • The member list UI shows raw UUIDs instead of usernames.
  • The group editor and account delete button still use the browser confirm() instead of the custom <confirm-dialog>.
  • Non-text files (images, PDFs) are visible in the tree but not openable — a MediaViewerPanel is planned.
  • No real-time multi-user collaboration yet.
  • CI deploy endpoint (/api/deploy) should send an email notification after each restart so deploys are visible without checking logs.
  • The Gitea webhook currently triggers on pushes to main — switch to a dev branch so every commit doesn't redeploy.

Technical Implementation

Backend

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 is handled by rokojori-auth at account.rokojori.com; Roject verifies the shared accessToken JWT cookie using jsonwebtoken + cookie-parser. All entity IDs are UUIDs via crypto.randomUUID() — no central counter, safe for parallel instances. Start the server with npm start.

Node.js Express ts-node jsonwebtoken cookie-parser UUID IDs

Frontend — Editor Singleton & Client/Server Split

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.

Frontend

Vanilla HTML, raw CSS (no Tailwind, no framework). Every UI component is a custom element with its own .ts and .css file in source/components/<name>/. CSS uses the element tag as root selector with display: block. TypeScript compiles to build/app/components/ via tsconfig.client.json (module: ESNext, moduleResolution: bundler, no bundler). HTML pages live in source/pages/ and are copied to build/app/ by scripts/copy-pages.js as part of the build. 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.

Web Components raw CSS module: ESNext no bundler tsc --build

Shared Library

A personal TypeScript library lives as a git submodule at source/library-ts/. It has two parts: browser/ (DOM-capable) and node/ (Node.js only). The browser part is compiled separately via TypeScript project references (source/library-ts/browser/tsconfig.roject.json, strict: false) into build/app/library-ts/browser/. The node part is included by tsconfig.ts-node.json (extends server config, strictNullChecks: false).

git submodule source/library-ts/ project references composite: true