2026-07-14 11:31:11 +00:00
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html lang="en">
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
|
<title>Monday, 14 July 2026 — Roject</title>
|
|
|
|
|
|
<link rel="stylesheet" href="../../../../_assets_/styles.css">
|
|
|
|
|
|
<link rel="stylesheet" href="../../../../_assets_/nav.css">
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<div class="page">
|
|
|
|
|
|
|
|
|
|
|
|
<header>
|
|
|
|
|
|
<p class="date">Monday, 14 July 2026</p>
|
|
|
|
|
|
<h1>Electron desktop app shell</h1>
|
|
|
|
|
|
<p class="subtitle">
|
|
|
|
|
|
Roject packaged as a standalone Windows desktop app with JWT-based auth,
|
|
|
|
|
|
token persistence, Authorization header injection, and a local Express server
|
|
|
|
|
|
running in-process.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</header>
|
|
|
|
|
|
|
|
|
|
|
|
<section>
|
|
|
|
|
|
<h2>What we built</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Electron main process (<code>electron/main.ts</code>)</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
The Express server starts in-process inside Electron's main process via a
|
|
|
|
|
|
compiled <code>startServer(port)</code> export. A <code>BrowserWindow</code>
|
|
|
|
|
|
points at <code>http://localhost:3000</code>. The server's static file,
|
|
|
|
|
|
data, and locale paths all previously relied on <code>__dirname</code>
|
|
|
|
|
|
relative to the TypeScript source; a new <code>source/server/rootDir.ts</code>
|
|
|
|
|
|
module resolves these using <code>process.env.ROJECT_ROOT</code> (set by
|
|
|
|
|
|
Electron before starting the server) with a fallback that preserves the
|
|
|
|
|
|
existing ts-node behaviour.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div class="tags">
|
|
|
|
|
|
<span class="tag">electron/main.ts</span>
|
|
|
|
|
|
<span class="tag">startServer(port)</span>
|
|
|
|
|
|
<span class="tag">source/server/rootDir.ts</span>
|
|
|
|
|
|
<span class="tag">ROJECT_ROOT</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Electron login window</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
A minimal <code>electron/login.html</code> form collects email and password.
|
|
|
|
|
|
Credentials are sent to the main process via IPC (<code>contextBridge</code> +
|
|
|
|
|
|
<code>ipcRenderer.invoke</code>). The main process calls
|
|
|
|
|
|
<code>POST https://account.rokojori.com/api/auth/login</code> directly using
|
|
|
|
|
|
Node.js <code>https</code> — no browser redirect, no CORS, no cookie.
|
|
|
|
|
|
Tokens are written to <code>userData/tokens.json</code> and reloaded on
|
|
|
|
|
|
the next app start.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div class="tags">
|
|
|
|
|
|
<span class="tag">electron/login.html</span>
|
|
|
|
|
|
<span class="tag">electron/preload.ts</span>
|
|
|
|
|
|
<span class="tag">contextBridge</span>
|
|
|
|
|
|
<span class="tag">tokens.json</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Authorization header injection</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<code>session.defaultSession.webRequest.onBeforeSendHeaders</code> intercepts
|
|
|
|
|
|
every request from the BrowserWindow to <code>http://localhost:3000/*</code>
|
|
|
|
|
|
and adds <code>Authorization: Bearer <accessToken></code>. The
|
|
|
|
|
|
existing <code>extractToken</code> middleware already checked this header
|
|
|
|
|
|
(designed for Electron from the start), so the frontend required zero changes.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Token refresh + navigation guard</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
Server-side <code>302</code> redirects to
|
|
|
|
|
|
<code>/api/auth/refresh-session</code> are intercepted by
|
|
|
|
|
|
<code>will-redirect</code> — the main process calls
|
|
|
|
|
|
<code>POST /api/auth/refresh</code> with the refresh token, updates the
|
|
|
|
|
|
stored tokens, and reloads the original URL. A <code>will-navigate</code>
|
|
|
|
|
|
guard prevents the BrowserWindow from ever leaving <code>localhost</code>:
|
|
|
|
|
|
any external navigation (e.g. the app's login link pointing to
|
|
|
|
|
|
<code>account.rokojori.com</code>) is intercepted and replaced with the
|
|
|
|
|
|
Electron login window.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Build pipeline additions</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
Three new tsconfigs: <code>tsconfig.electron.json</code> (compiles
|
|
|
|
|
|
<code>electron/</code> → <code>build/electron/</code>),
|
|
|
|
|
|
<code>tsconfig.electron-server.json</code> (compiles
|
|
|
|
|
|
<code>source/server/</code> → <code>build/server/</code> for Electron's
|
|
|
|
|
|
in-process require). New npm scripts: <code>electron:build</code>,
|
|
|
|
|
|
<code>electron:dev</code>, <code>electron:dist</code>.
|
|
|
|
|
|
<code>scripts/copy-electron-assets.js</code> copies <code>login.html</code>
|
|
|
|
|
|
into <code>build/electron/</code>.
|
|
|
|
|
|
<code>scripts/launch-electron.js</code> spawns the Electron binary with
|
|
|
|
|
|
<code>ELECTRON_RUN_AS_NODE</code> deleted from the environment (VS Code /
|
|
|
|
|
|
Claude Code set this variable, which otherwise makes Electron behave as
|
|
|
|
|
|
plain Node.js with no GUI or API).
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div class="tags">
|
|
|
|
|
|
<span class="tag">electron-builder</span>
|
|
|
|
|
|
<span class="tag">scripts/launch-electron.js</span>
|
|
|
|
|
|
<span class="tag">ELECTRON_RUN_AS_NODE workaround</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section>
|
|
|
|
|
|
<h2>Key decisions</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="decision">
|
|
|
|
|
|
<strong>Express runs in-process, not as a child process</strong>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
The outline originally described the server as a child process. Running it
|
|
|
|
|
|
in-process is simpler, removes IPC overhead, and is equivalent for the first
|
|
|
|
|
|
iteration. The compiled server JS is required at runtime via a dynamic
|
|
|
|
|
|
<code>require(serverPath)</code> call.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="decision">
|
|
|
|
|
|
<strong>ELECTRON_RUN_AS_NODE is set by the VS Code environment</strong>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
When this env var is set, the Electron binary runs as plain Node.js: no GUI,
|
|
|
|
|
|
no Electron API, <code>require('electron')</code> returns the binary path
|
|
|
|
|
|
string. The launcher script deletes it before spawning so the binary
|
|
|
|
|
|
initialises as a real Electron app. This was discovered by writing debug
|
|
|
|
|
|
output to a log file from inside the Electron process.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="decision">
|
|
|
|
|
|
<strong>rootDir.ts centralises all server path resolution</strong>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
Six server files used <code>path.join(__dirname, '..', '..', ...)</code>
|
|
|
|
|
|
relative to the TypeScript source depth. Compiled output is one level deeper
|
|
|
|
|
|
(<code>build/server/server/</code>), breaking all paths. A single
|
|
|
|
|
|
<code>rootDir.ts</code> module exposes <code>ROOT</code> that all files
|
|
|
|
|
|
import, using <code>ROJECT_ROOT</code> when set (Electron) or the
|
|
|
|
|
|
<code>__dirname</code> fallback (ts-node).
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="decision">
|
|
|
|
|
|
<strong>.env loaded by the Electron main process</strong>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
The Express server reads <code>JWT_SECRET</code> from the environment.
|
|
|
|
|
|
When started via <code>npm start</code> this is provided by the shell; when
|
|
|
|
|
|
started from Electron there is no shell. A small inline <code>loadEnv()</code>
|
|
|
|
|
|
function in <code>electron/main.ts</code> parses the project-root
|
|
|
|
|
|
<code>.env</code> file and sets missing variables before <code>startServer</code>
|
|
|
|
|
|
is called.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<section>
|
2026-07-14 12:34:47 +00:00
|
|
|
|
<h2>Workspace boards system</h2>
|
2026-07-14 11:31:11 +00:00
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
2026-07-14 12:34:47 +00:00
|
|
|
|
<h3>Task boards (<code>workspace/boards/</code>)</h3>
|
2026-07-14 11:31:11 +00:00
|
|
|
|
<p>
|
2026-07-14 12:34:47 +00:00
|
|
|
|
A new Boards section added to the workspace with three boards:
|
|
|
|
|
|
<strong>Tasks</strong> (current sprint, To Do / In Progress / Done),
|
|
|
|
|
|
<strong>Bugs</strong> (Critical and Visual/UI/UX lanes),
|
|
|
|
|
|
<strong>Backlog</strong> (MVP / Nice To Have / To The Moon).
|
|
|
|
|
|
Each board uses custom HTML elements — <code><task-item></code>,
|
|
|
|
|
|
<code><task-title></code>, <code><task-content></code> — styled
|
|
|
|
|
|
to match the workspace visual language (CSS variables, same spacing rhythm,
|
|
|
|
|
|
lane headers matching <code>section h2</code>). Clicking a title toggles
|
|
|
|
|
|
the content open/closed via <code>hide-content</code> class.
|
|
|
|
|
|
Shared styles extracted to <code>workspace/_assets_/boards.css</code>.
|
2026-07-14 11:31:11 +00:00
|
|
|
|
</p>
|
2026-07-14 12:34:47 +00:00
|
|
|
|
<div class="tags">
|
|
|
|
|
|
<span class="tag">workspace/boards/</span>
|
|
|
|
|
|
<span class="tag">task-item</span>
|
|
|
|
|
|
<span class="tag">boards.css</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Outline shortened</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<code>workspace/outline/index.html</code> was rewritten to remove the long
|
|
|
|
|
|
numbered to-do list and the "Smaller open improvements" bullet list.
|
|
|
|
|
|
Those items were distributed across the three boards.
|
|
|
|
|
|
The outline now covers only: what Roject is, what exists now (condensed into
|
|
|
|
|
|
named cards), a one-card pointer to the boards, and the technical implementation
|
|
|
|
|
|
reference. About half the original length.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Workspace index improved</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
<code>workspace/index.html</code> gained a Boards card and a
|
|
|
|
|
|
"Quick orientation" section with separate guidance for humans and agents:
|
|
|
|
|
|
read Outline → Guides → Reference before making changes; check Boards and
|
|
|
|
|
|
the most recent History entry for current context.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-07-14 20:27:03 +00:00
|
|
|
|
<section>
|
|
|
|
|
|
<h2>Project home redesign</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>Backend: user settings & server cleanup</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
Added <code>UserSetting</code> interface and <code>userSettings</code> CRUD module
|
|
|
|
|
|
to <code>source/server/db.ts</code> (persisted in <code>user_settings.json</code>).
|
|
|
|
|
|
Created <code>source/server/routes/userSettings.ts</code> with
|
|
|
|
|
|
<code>GET / PUT /api/user/settings</code> (requires auth).
|
|
|
|
|
|
Cleaned up <code>source/server/index.ts</code>: removed the groups router and
|
|
|
|
|
|
explicit <code>/</code> redirect; added <code>/edit</code> → <code>/editor.html</code>
|
|
|
|
|
|
redirect and wired the new userSettings route.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div class="tags">
|
|
|
|
|
|
<span class="tag">user_settings.json</span>
|
|
|
|
|
|
<span class="tag">/api/user/settings</span>
|
|
|
|
|
|
<span class="tag">groups router removed</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>project-home component</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
New <code>source/pages/index.html</code> entry point loads Barlow 900 Italic
|
|
|
|
|
|
from Google Fonts and the <code>project-home</code> component.
|
|
|
|
|
|
<code>project-home</code> checks auth: logged-out → full-screen dark hero with
|
|
|
|
|
|
brand text and login link; logged-in → fetches user settings, reads
|
|
|
|
|
|
<code>settings.theme</code>, and dynamically imports the matching theme component
|
|
|
|
|
|
(<code>project-list-default</code> by default; <code>project-list-italic-neon</code>
|
|
|
|
|
|
planned).
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div class="tags">
|
|
|
|
|
|
<span class="tag">source/pages/index.html</span>
|
|
|
|
|
|
<span class="tag">project-home</span>
|
|
|
|
|
|
<span class="tag">dynamic import</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<h3>project-list-default component (default theme)</h3>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
Full default theme: dark radial-gradient background, fixed nav with Roject logo
|
|
|
|
|
|
(webp, inner area positioned at 70% scale) and a user-group element where hover
|
|
|
|
|
|
slides in "Log out" beneath the email — clicking anywhere triggers logout.
|
|
|
|
|
|
Each project row: 7em×7em rounded badge with project colour (hue from UUID via
|
|
|
|
|
|
31-hash), radial-gradient fill (centre at 51% alpha), Barlow 900 Italic skewed
|
|
|
|
|
|
name in project colour. Hover reveals delete (×) and members buttons over the
|
|
|
|
|
|
badge corners. A "New Project…" dashed-outline card sits first in the list and
|
|
|
|
|
|
opens a name dialog on click. Rows animate in staggered on load.
|
|
|
|
|
|
Back arrow in the editor updated to <code>/</code>.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
<div class="tags">
|
|
|
|
|
|
<span class="tag">project-list-default</span>
|
|
|
|
|
|
<span class="tag">hueFromId (31-hash)</span>
|
|
|
|
|
|
<span class="tag">Barlow skew matrix</span>
|
|
|
|
|
|
<span class="tag">new-project dialog</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="decision">
|
|
|
|
|
|
<strong>Groups removed from the UI</strong>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
The redesign spec removed groups entirely from the frontend and server routes.
|
|
|
|
|
|
The data layer (<code>groups</code> / <code>groupMembers</code> in db.ts) is
|
|
|
|
|
|
retained in case groups return later via rokojori-auth; only the router and all
|
|
|
|
|
|
UI references were deleted.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="decision">
|
|
|
|
|
|
<strong>Theme preference stored server-side, not in localStorage</strong>
|
|
|
|
|
|
<p>
|
|
|
|
|
|
JWT settings from rokojori-auth are read-only. A local
|
|
|
|
|
|
<code>user_settings.json</code> DB file with a generic
|
|
|
|
|
|
<code>Record<string, unknown></code> value map provides per-user persistence
|
|
|
|
|
|
for any app setting (starting with <code>theme</code>) without requiring changes
|
|
|
|
|
|
to the auth service.
|
|
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
2026-07-14 12:34:47 +00:00
|
|
|
|
<section>
|
|
|
|
|
|
<h2>What's next</h2>
|
|
|
|
|
|
|
|
|
|
|
|
<div class="card">
|
|
|
|
|
|
<p>
|
2026-07-14 20:27:03 +00:00
|
|
|
|
Italic Neon theme (batch 2): skewed cards, warm→cold gradient, hand-crafted SVG
|
|
|
|
|
|
pattern icons (13 files already drawn), daily auto-rotation + user toggle.
|
|
|
|
|
|
See <a href="../../../../boards/tasks.html">Tasks board</a> for the full sprint.
|
2026-07-14 11:31:11 +00:00
|
|
|
|
</p>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
|
|
<footer>
|
|
|
|
|
|
Roject — session history
|
|
|
|
|
|
</footer>
|
|
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<script>var NAV_ROOT = '../../../../';</script>
|
|
|
|
|
|
<script src="../../../../_assets_/nav-data.js"></script>
|
|
|
|
|
|
<script src="../../../../_assets_/nav.js"></script>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|