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

315 lines
14 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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>