2026-07-06 17:28:59 +00:00
<!DOCTYPE html>
< html lang = "en" >
< head >
< meta charset = "UTF-8" >
< meta name = "viewport" content = "width=device-width, initial-scale=1.0" >
< title > Roject — Developer Documentation< / title >
< link rel = "stylesheet" href = "../_assets_/styles.css" >
< link rel = "stylesheet" href = "../_assets_/nav.css" >
< / head >
< body >
< div class = "page" >
2026-07-12 14:14:58 +00:00
< div style = "width:100%;height:400px;overflow:hidden;background:#000;margin-bottom:2rem;border-radius:8px;" >
< img src = "../_assets_/roject.svg" alt = "Rojects"
2026-07-11 12:06:55 +00:00
style="width:100%;height:100%;object-fit:cover;object-position:center;">
< / div >
2026-07-06 17:28:59 +00:00
< header >
< p class = "date" > Project Documentation< / p >
< h1 style = "font-size: 300%;" > Roject< / h1 >
< p class = "subtitle" > Developer reference for human and agent contributors. Keep this file up to date as the project evolves.< / p >
< / header >
< section >
< h2 > Project Outline< / h2 >
< div class = "card" >
< h3 > What it is< / h3 >
< p >
2026-07-11 12:06:55 +00:00
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.
2026-07-06 17:28:59 +00:00
< / p >
< / div >
< div class = "card" >
< h3 > What exists now< / h3 >
< p >
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 < code > storage/< uuid> /root/< / code >
with a default < code > index.html< / code > on creation.
< / p >
< p style = "margin-top:0.75rem" >
A full editor page (< code > /editor.html< / code > ) with a 3-panel resizable layout
(Left / Center / Right). Each panel holds one or more sections side by side,
each section holds a < code > < tab-container> < / code > . Tabs are draggable
2026-07-11 11:21:14 +00:00
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, < code > contenteditable< / code > , MutationObserver, undo/redo,
Ctrl+S save). The Right panel is empty by default and receives dropped tabs.
2026-07-06 17:28:59 +00:00
< / p >
< p style = "margin-top:0.75rem" >
2026-07-11 11:21:14 +00:00
A < code > FileEditorRegistry< / code > routes files to the correct panel by suffix.
HTML files open in < code > html-editor-panel< / code > ; all other known text formats
open in < code > code-panel< / code > (CodeMirror 5, syntax highlighting, dark theme,
Pin/Undo/Redo/Save toolbar). The registry checks an optional project-level
< code > workspace/editor/file-editors.json< / code > first, then falls back to
in-memory defaults. Unknown extensions show an error in the file tree instead
of attempting to open.
< / p >
< p style = "margin-top:0.75rem" >
A < code > rojo-chat-panel< / code > provides a streaming AI chat interface backed
by a LangChain + OpenAI-compatible model. Each panel instance holds its own
conversation session in memory.
2026-07-06 17:28:59 +00:00
A reusable < code > < confirm-dialog> < / code > component replaces browser
2026-07-11 11:21:14 +00:00
< code > confirm()< / code > for destructive actions (currently project deletion).
2026-07-12 16:50:04 +00:00
An < code > EmailService< / code > static facade (< code > source/server/email/< / code > )
provides a single < code > sendEmail()< / code > entry point backed by a swappable
< code > EmailSender< / code > interface; the default implementation is
< code > SMTPEmailSender< / code > (Nodemailer), configured via environment variables.
2026-07-06 17:28:59 +00:00
< / p >
2026-07-12 14:14:58 +00:00
< p style = "margin-top:0.75rem" >
The app is deployed and publicly accessible at
< code > https://roject.rokojori.com< / code > on Server A. nginx handles TLS
termination and reverse-proxies to the Node.js process managed by a
systemd service. Deployment is currently manual
(< code > git pull & & npm run build & & systemctl restart roject< / code > );
CI automation is the remaining open item.
< / p >
2026-07-06 17:28:59 +00:00
< / div >
< div class = "card" >
< h3 > What still needs work< / h3 >
2026-07-11 12:06:55 +00:00
< p > Items below are ordered by priority. Major planned features first, then smaller open improvements.< / p >
< / div >
< div class = "card" >
2026-07-12 14:14:58 +00:00
< h3 > 1 — CI pipeline (server is live)< / h3 >
2026-07-11 12:06:55 +00:00
< p >
2026-07-12 14:14:58 +00:00
The server is deployed and running at < code > https://roject.rokojori.com< / code >
(nginx + systemd + Let's Encrypt on Server A). What remains is automating the
deploy on push: a Gitea Actions runner installed on Server A registers with the
repo on < code > community.rokojori.com< / code > and runs a workflow that pulls,
builds, and restarts the service on every push to main. This is the last piece
that turns the manual deploy into a proper CI/CD pipeline.
2026-07-11 12:06:55 +00:00
< / p >
< div class = "tags" >
2026-07-12 14:14:58 +00:00
< span class = "tag" > Gitea Actions runner< / span >
< span class = "tag" > act_runner< / span >
< span class = "tag" > auto-deploy on push< / span >
2026-07-11 12:06:55 +00:00
< / div >
< / div >
< div class = "card" >
< h3 > 2 — Electron desktop app + local filesystem access< / h3 >
< p >
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.
< / p >
< p style = "margin-top:0.75rem" >
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
< code > fs< / code > directly, rather than being restricted to the
< code > storage/< uuid> /root/< / code > 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.
< / p >
< p style = "margin-top:0.75rem" >
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.
< / p >
< div class = "tags" >
< span class = "tag" > Electron< / span >
< span class = "tag" > child_process< / span >
< span class = "tag" > Node.js fs< / span >
< span class = "tag" > BrowserWindow< / span >
< / div >
< / div >
< div class = "card" >
< h3 > 3 — Local git repository integration< / h3 >
< p >
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.
< / p >
< p style = "margin-top:0.75rem" >
The implementation uses < code > simple-git< / code > , 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 < code > isomorphic-git< / code > (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.
< / p >
< div class = "tags" >
< span class = "tag" > simple-git< / span >
< span class = "tag" > git CLI< / span >
< span class = "tag" > new panel< / span >
< / div >
< / div >
< div class = "card" >
< h3 > 4 — Internet tunnel / port pass-through relay< / h3 >
< p >
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.
< / p >
< p style = "margin-top:0.75rem" >
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.
< / p >
< p style = "margin-top:0.75rem" >
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.
< / p >
< p style = "margin-top:0.75rem" >
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.
< / p >
< div class = "tags" >
< span class = "tag" > WebSocket relay< / span >
< span class = "tag" > HTTP proxy< / span >
< span class = "tag" > secret key / auth< / span >
< span class = "tag" > local agent< / span >
< / div >
< / div >
< div class = "card" >
< h3 > 5 — Mobile app (PWA first, native shell later)< / h3 >
2026-07-06 17:28:59 +00:00
< p >
2026-07-11 12:06:55 +00:00
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.
2026-07-06 17:28:59 +00:00
< / p >
2026-07-11 12:06:55 +00:00
< p style = "margin-top:0.75rem" >
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.
< / p >
< p style = "margin-top:0.75rem" >
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.
< / p >
< div class = "tags" >
< span class = "tag" > PWA< / span >
< span class = "tag" > manifest + service worker< / span >
< span class = "tag" > Capacitor (later)< / span >
< span class = "tag" > no React Native< / span >
< / div >
2026-07-06 17:28:59 +00:00
< / div >
2026-07-11 12:06:55 +00:00
< div class = "card" >
< h3 > Smaller open improvements< / h3 >
< ul style = "line-height:1.9" >
< li > The Right panel has no default content and relies on manual tab dragging to populate.< / li >
< li > Portrait mode's secondary section switcher (when a panel has multiple side-by-side sections) is not yet wired up.< / li >
< li > The member list UI shows raw UUIDs instead of usernames.< / li >
< li > The group editor and account delete button still use the browser < code > confirm()< / code > instead of the custom < code > < confirm-dialog> < / code > .< / li >
< li > Non-text files (images, PDFs) are visible in the tree but not openable — a < code > MediaViewerPanel< / code > is planned.< / li >
< li > No real-time multi-user collaboration yet.< / li >
< / ul >
< / div >
2026-07-06 17:28:59 +00:00
< / section >
< section >
< h2 > Technical Implementation< / h2 >
< div class = "card" >
< h3 > Backend< / h3 >
< p >
Node.js + Express, TypeScript compiled on the fly with < code > ts-node< / code > .
No database — all data lives as JSON files in < code > data/< / code >
(auto-created on first run). Auth uses < code > express-session< / code > +
< code > bcryptjs< / code > . All entity IDs are UUIDs via
< code > crypto.randomUUID()< / code > — no central counter, safe for parallel
instances. Start the server with < code > npm start< / code > .
< / p >
< div class = "tags" >
< span class = "tag" > Node.js< / span >
< span class = "tag" > Express< / span >
< span class = "tag" > ts-node< / span >
< span class = "tag" > express-session< / span >
< span class = "tag" > bcryptjs< / span >
< span class = "tag" > UUID IDs< / span >
< / div >
< / div >
2026-07-11 12:24:17 +00:00
< div class = "card" >
< h3 > Frontend — Editor Singleton & Client/Server Split< / h3 >
< p >
The < code > Editor< / code > singleton (< code > src/editor/Editor.ts< / code > ) is the
central hub of the frontend editor — it owns all open document state, the
< code > FileEditorRegistry< / code > , and the five events that panels and the tab
container subscribe to (< code > onDocumentOpened< / code > ,
< code > onDocumentDirty< / code > , < code > onDocumentSaved< / code > ,
< code > onFilesChanged< / code > , < code > onFileTypeUnknown< / code > ). For the full
event reference and the TypeScript compilation split between client and server,
see the
< a href = "../reference/editor-singleton/index.html" > Editor Singleton reference< / a > .
< / p >
< / div >
2026-07-06 17:28:59 +00:00
< div class = "card" >
< h3 > Frontend< / h3 >
< p >
Vanilla HTML, raw CSS (no Tailwind, no framework). Every UI component is a
custom element with its own < code > .ts< / code > and < code > .css< / code > file in
2026-07-12 12:01:01 +00:00
< code > source/components/< name> /< / code > . CSS uses the element tag as root
2026-07-06 17:28:59 +00:00
selector with < code > display: block< / code > . TypeScript compiles to
2026-07-12 12:01:01 +00:00
< code > build/app/components/< / code > via < code > tsconfig.client.json< / code >
2026-07-06 17:28:59 +00:00
(< code > module: ESNext< / code > , < code > moduleResolution: bundler< / code > , no
2026-07-12 12:01:01 +00:00
bundler). HTML pages live in < code > source/pages/< / code > and are copied to
< code > build/app/< / code > by < code > scripts/copy-pages.js< / code > as part of the
build. HTML pages load components with
2026-07-06 17:28:59 +00:00
< code > < script type="module"> < / code > . Shared state uses a module-level
singleton (< code > editor-state.ts< / code > ) rather than globals.
Build with < code > npm run build< / code > .
< / p >
< div class = "tags" >
< span class = "tag" > Web Components< / span >
< span class = "tag" > raw CSS< / span >
< span class = "tag" > module: ESNext< / span >
< span class = "tag" > no bundler< / span >
< span class = "tag" > tsc --build< / span >
< / div >
< / div >
< div class = "card" >
< h3 > Shared Library< / h3 >
< p >
A personal TypeScript library lives as a git submodule at
2026-07-12 12:01:01 +00:00
< code > source/library-ts/< / code > . It has two parts: < code > browser/< / code >
2026-07-06 17:28:59 +00:00
(DOM-capable) and < code > node/< / code > (Node.js only). The browser part is
compiled separately via TypeScript project references
2026-07-12 12:01:01 +00:00
(< code > source/library-ts/browser/tsconfig.roject.json< / code > , < code > strict: false< / code > )
into < code > build/app/library-ts/browser/< / code > . The node part is included by
2026-07-06 17:28:59 +00:00
< code > tsconfig.ts-node.json< / code > (extends server config, < code > strictNullChecks: false< / code > ).
< / p >
< div class = "tags" >
< span class = "tag" > git submodule< / span >
2026-07-12 12:01:01 +00:00
< span class = "tag" > source/library-ts/< / span >
2026-07-06 17:28:59 +00:00
< span class = "tag" > project references< / span >
< span class = "tag" > composite: true< / span >
< / div >
< / div >
< / section >
< footer >
Roject — developer documentation
< / footer >
< / div >
< script > var NAV _ROOT = '../' ; < / script >
< script src = "../_assets_/nav-data.js" > < / script >
< script src = "../_assets_/nav.js" > < / script >
< / body >
< / html >