434 lines
22 KiB
HTML
434 lines
22 KiB
HTML
<!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">
|
|
|
|
<div style="width:100%;height:400px;overflow:hidden;background:#000;margin-bottom:2rem;border-radius:8px;">
|
|
<img src="../_assets_/roject.svg" alt="Rojects"
|
|
style="width:100%;height:100%;object-fit:cover;object-position:center;">
|
|
</div>
|
|
|
|
<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>
|
|
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.
|
|
</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
|
|
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.
|
|
</p>
|
|
<p style="margin-top:0.75rem">
|
|
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.
|
|
A reusable <code><confirm-dialog></code> component replaces browser
|
|
<code>confirm()</code> for destructive actions (currently project deletion).
|
|
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.
|
|
</p>
|
|
<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>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>What still needs work</h3>
|
|
<p>Items below are ordered by priority. Major planned features first, then smaller open improvements.</p>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>1 — Local filesystem access</h3>
|
|
<p>
|
|
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
|
|
<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>
|
|
<div class="tags">
|
|
<span class="tag">Node.js fs</span>
|
|
<span class="tag">file tree extension</span>
|
|
<span class="tag">local mode</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>2 — Remote projects in Electron</h3>
|
|
<p>
|
|
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. <code>roject.rokojori.com</code>) and
|
|
list, open, and edit projects hosted there, alongside any local filesystem
|
|
projects.
|
|
</p>
|
|
<p style="margin-top:0.75rem">
|
|
The Electron app already holds a valid JWT and can send it as an
|
|
<code>Authorization: Bearer</code> 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.
|
|
</p>
|
|
<div class="tags">
|
|
<span class="tag">remote Roject server</span>
|
|
<span class="tag">Authorization header</span>
|
|
<span class="tag">mixed local + remote</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>
|
|
<p>
|
|
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.
|
|
</p>
|
|
<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>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Done — Electron desktop app shell</h3>
|
|
<p>
|
|
Roject runs as a standalone desktop application on Windows. The Express server
|
|
starts in-process inside Electron's main process. A custom login window
|
|
(<code>electron/login.html</code>) collects credentials and calls
|
|
<code>POST https://account.rokojori.com/api/auth/login</code> directly from
|
|
the main process — no browser redirect, no cookie. Tokens are stored in
|
|
<code>userData/tokens.json</code> and re-used across sessions.
|
|
</p>
|
|
<p style="margin-top:0.75rem">
|
|
All HTTP requests from the BrowserWindow to <code>localhost</code> have
|
|
<code>Authorization: Bearer <accessToken></code> injected automatically
|
|
via <code>session.webRequest.onBeforeSendHeaders</code> — the frontend requires
|
|
zero changes. Expired tokens are refreshed via
|
|
<code>POST /api/auth/refresh</code> and the page is reloaded transparently.
|
|
Any navigation away from <code>localhost</code> is intercepted and redirected
|
|
to the Electron login window instead.
|
|
</p>
|
|
<p style="margin-top:0.75rem">
|
|
Known issue: <code>ELECTRON_RUN_AS_NODE=1</code> is set by VS Code / Claude
|
|
Code, which makes Electron behave as plain Node.js. The launcher script
|
|
(<code>scripts/launch-electron.js</code>) deletes this variable before
|
|
spawning the binary. Run with <code>npm run electron:dev</code> or
|
|
<code>node scripts/launch-electron.js</code> after the build.
|
|
</p>
|
|
<div class="tags">
|
|
<span class="tag">electron/main.ts</span>
|
|
<span class="tag">webRequest header injection</span>
|
|
<span class="tag">token persistence</span>
|
|
<span class="tag">ELECTRON_RUN_AS_NODE workaround</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Done — CI/CD pipeline</h3>
|
|
<p>
|
|
Auto-deploy on push to main via a Gitea webhook calling
|
|
<code>POST /api/deploy</code> on the Roject server. The endpoint verifies the
|
|
<code>X-Gitea-Signature</code> HMAC-SHA256 signature, checks the branch is
|
|
<code>main</code>, responds immediately, then spawns a detached bash process
|
|
that runs <code>git pull && npm run build && systemctl restart roject</code>.
|
|
The detached process survives the <code>systemctl restart</code> that kills the parent.
|
|
</p>
|
|
<div class="tags">
|
|
<span class="tag">Gitea webhook</span>
|
|
<span class="tag">HMAC-SHA256</span>
|
|
<span class="tag">detached spawn</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Done — Centralized auth: rokojori-auth</h3>
|
|
<p>
|
|
The standalone auth service <strong>rokojori-auth</strong> is built and live at
|
|
<code>account.rokojori.com</code>. 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.
|
|
</p>
|
|
<p style="margin-top:0.75rem">
|
|
See <a href="auth-restructure.html">auth-restructure</a> for the full plan and
|
|
the rokojori-auth workspace for implementation details.
|
|
</p>
|
|
<div class="tags">
|
|
<span class="tag">rokojori-auth</span>
|
|
<span class="tag">account.rokojori.com</span>
|
|
<span class="tag">JWT</span>
|
|
<span class="tag">centralized identity</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Done — Roject integration with rokojori-auth</h3>
|
|
<p>
|
|
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
|
|
<code>accessToken</code> cookie on <code>.rokojori.com</code>. Expired tokens are
|
|
transparently refreshed via
|
|
<code>account.rokojori.com/api/auth/refresh-session?redirect=...</code>.
|
|
Login and logout links in <code>app-nav</code> point to
|
|
<code>account.rokojori.com</code>. All data references use <code>userId</code>
|
|
from the JWT payload. A <code>GET /api/auth/logout?redirect=...</code> endpoint
|
|
was also added to rokojori-auth to support browser-based logout links.
|
|
</p>
|
|
<div class="tags">
|
|
<span class="tag">JWT middleware</span>
|
|
<span class="tag">cookie-parser</span>
|
|
<span class="tag">jsonwebtoken</span>
|
|
<span class="tag">account.rokojori.com</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<h3>Smaller open improvements</h3>
|
|
<ul style="line-height:1.9">
|
|
<li>Unauthenticated users should land on a landing screen that explains the app and shows a login link, rather than crashing on dashboard components.</li>
|
|
<li>Components that fetch data (groups, projects, etc.) do not handle <code>401</code> responses gracefully — they crash when the API returns an error object instead of an array.</li>
|
|
<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>
|
|
<li>CI deploy endpoint (<code>/api/deploy</code>) should send an email notification after each restart so deploys are visible without checking logs.</li>
|
|
<li>The Gitea webhook currently triggers on pushes to <code>main</code> — switch to a <code>dev</code> branch so every commit doesn't redeploy.</li>
|
|
</ul>
|
|
</div>
|
|
|
|
</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 is handled by <code>rokojori-auth</code>
|
|
at <code>account.rokojori.com</code>; Roject verifies the shared
|
|
<code>accessToken</code> JWT cookie using <code>jsonwebtoken</code> +
|
|
<code>cookie-parser</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">jsonwebtoken</span>
|
|
<span class="tag">cookie-parser</span>
|
|
<span class="tag">UUID IDs</span>
|
|
</div>
|
|
</div>
|
|
|
|
<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>
|
|
|
|
<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
|
|
<code>source/components/<name>/</code>. CSS uses the element tag as root
|
|
selector with <code>display: block</code>. TypeScript compiles to
|
|
<code>build/app/components/</code> via <code>tsconfig.client.json</code>
|
|
(<code>module: ESNext</code>, <code>moduleResolution: bundler</code>, no
|
|
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
|
|
<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
|
|
<code>source/library-ts/</code>. It has two parts: <code>browser/</code>
|
|
(DOM-capable) and <code>node/</code> (Node.js only). The browser part is
|
|
compiled separately via TypeScript project references
|
|
(<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
|
|
<code>tsconfig.ts-node.json</code> (extends server config, <code>strictNullChecks: false</code>).
|
|
</p>
|
|
<div class="tags">
|
|
<span class="tag">git submodule</span>
|
|
<span class="tag">source/library-ts/</span>
|
|
<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>
|