Friday, 18 July 2026
CodeMirror syntax highlighting system, smart file-tree open, rokojori-auth login fix. Electron local dev fixes: stale cookie auth, clock tolerance, quit-on-login, credential persistence. Nav z-index fix.
Clicking a file in the file tree now opens it intelligently rather than always targeting any available panel:
_pinned property on the panel element) are
excluded from the available pool entirely.
Four minified CodeMirror 5 mode files added to source/vendor/ and
loaded in editor.html:
cm-mode-clike.min.js — C/C++/Java; used for GLSL and GDShadercm-mode-python.min.js — Python; used for GDScript (.gd)cm-mode-shell.min.js — shell scripts (.sh)cm-mode-yaml.min.js — YAML and YML
Extension mappings in code-panel's _resolveMode:
.yaml/.yml → yaml,
.sh → shell,
.gd → python,
.glsl/.gdshader/.gdshaderinc → clike,
.cs → rokojori-cs.
A custom, self-contained browser lexer and CodeMirror mode wrapper built in
source/components/code-panel/:
BrowserLexer.ts — zero external imports. Inlines
makeSticky() (adds /y flag to regexes).
BrowserMatcher uses sticky regex + lastIndex for
positional matching. BrowserLexer holds named mode lists of
matchers. Exports a cLikeLexer() factory with matchers for all
C-like token types (comments, strings, numbers, operators, keywords,
identifiers, etc.).CodeMirrorLexerMode.ts — wraps any BrowserLexer
into a CodeMirror 5 mode object. Supports multi-line block definitions (start
regex → end regex → CSS class; state preserved across lines). Supports named
keyword sets: sets of words that override the base CSS class for a given token
type (e.g. mapping C# keywords from CWORD → keyword).
Keyword sets are mutable at runtime — add/remove/update without recreating
the mode. refresh(cm) forces CodeMirror to re-tokenize by
re-setting the mode option.CSharpMode.ts — creates csharpMode using
cLikeLexer() with a multi-line /* ... */ comment
block and a keyword set of ~70 C# keywords. Registered in CodeMirror as
'rokojori-cs'.
The browser-only design (no library-ts dependency) avoids the
moduleResolution: "bundler" / ts-node conflict:
library-ts compiles without .js extensions (works for
ts-node), while browser ES modules require explicit extensions.
Keeping the lexer self-contained in code-panel/ eliminates the
tension entirely.
The page-level middleware in rokojori-auth/source/server/index.ts
was redirecting expired accessToken requests to
/api/auth/refresh-session?redirect=... — a route that no longer
exists. This blocked login entirely (redirect loop on first visit after token
expiry). Fixed by replacing the entire error branch with next():
the middleware now passes through on any token error. Transparent refresh for
Roject API calls is handled server-side by Roject's own
jwtMiddleware.
Self-contained BrowserLexer instead of reusing library-ts CLikeLexer.
Importing from library-ts pulled in extensionless relative imports that break the
browser ES module loader (NS_ERROR_CORRUPTED_CONTENT). Adding
.js extensions to library-ts imports broke ts-node
(CommonJS cannot remap .js → .ts). The cleanest fix
was a purpose-built, dependency-free browser lexer duplicating only what the
code editor needs.
Single-click open, not double-click. The board task said double-click, but single-click is more natural for an IDE file tree (matches VS Code, JetBrains). The smart-targeting logic (focus existing, skip pinned) makes single-click safe — it never disrupts an intentionally pinned panel.
.pld-nav in project-list-default.css has
position: fixed but no z-index. Stacking contexts
created by position: relative project rows on mobile buried the nav
underneath them. Fixed with z-index: 10. Overlays remain above at
z-index: 200.
extractToken
The root cause of Electron auth failures: extractToken() in
source/auth-connector/source/server/auth.ts was checking the
accessToken cookie before the Authorization header.
Electron's Chromium session had a stale accessToken cookie that took
priority over the fresh Bearer token injected via
session.defaultSession.webRequest.onBeforeSendHeaders. Fixed by
reversing the check order: Bearer header wins, cookie is the fallback.
Additionally, createMainWindow() now calls
session.defaultSession.clearStorageData({ storages: ['cookies'] })
before creating the window, preventing the stale cookie from accumulating across
Electron restarts.
JWT_CLOCK_TOLERANCE
Fresh tokens issued by the production auth server (1 h TTL) appeared expired
immediately on the Windows dev machine because the local clock was ~65 minutes
ahead of the server. Every jwt.verify call returned
TokenExpiredError seconds after login.
Fix: jwt.verify accepts a clockTolerance option. An env
var JWT_CLOCK_TOLERANCE (integer, seconds; default 0) is now read and
passed as clockTolerance when non-zero. Set to 7200 in
.env for local development. The underlying audit task (time must never
depend on the user's clock) is on the board.
Two issues fixed in electron/main.ts:
POST account.rokojori.com/api/auth/refresh before opening
the main window. Fresh tokens are saved; if refresh fails the login window is
shown instead. This prevents using expired access tokens on startup.createMainWindow() was made
async (to await the cookie clear), but loginWindow?.close()
was called before awaiting it. Zero open windows → window-all-closed
→ app.quit(). Fixed by:
createMainWindow().then(() => loginWindow?.close()).
The login window (electron/login.html) was extended:
userData/last-email.txt and
userData/last-password.txt on successful login; when unchecked, any
saved files are deleted.auth:clear-credentials IPC, wipes the fields, and disables itself.
Hidden when no credentials are saved.
IPC surface added to electron/preload.ts:
lastEmail(), lastPassword(), clearCredentials().