rojects/workspace/history/2026/07-July/14-Monday/index.html

315 lines
14 KiB
HTML
Raw Normal View History

<!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 &lt;accessToken&gt;</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>
<h2>Workspace boards system</h2>
<div class="card">
<h3>Task boards (<code>workspace/boards/</code>)</h3>
<p>
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>&lt;task-item&gt;</code>,
<code>&lt;task-title&gt;</code>, <code>&lt;task-content&gt;</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>.
</p>
<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>
<section>
<h2>Project home redesign</h2>
<div class="card">
<h3>Backend: user settings &amp; 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&lt;string, unknown&gt;</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>
<section>
<h2>What's next</h2>
<div class="card">
<p>
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.
</p>
</div>
</section>
<footer>
Roject &mdash; session history
</footer>
</div>
<script>var NAV_ROOT = '../../../../';</script>
<script src="../../../../_assets_/nav-data.js"></script>
<script src="../../../../_assets_/nav.js"></script>
</body>
</html>