Wednesday, 30 July 2026

Session History

Local filesystem access, remote projects proxy, file tree custom elements + icons, project-list-default tab UI.

What we built

project-list-default — tab UI and folder rows

The navigation in project-list-default now uses proper custom elements: <pld-tabs> and <pld-tab> (previously <div class="pld-tabs"> / <button class="pld-tab">). The tabs are centred in the nav bar via position: absolute; left: 50%; transform: translateX(-50%) without interfering with the logo.

The This PC tab row was rewritten to use the same style as online project rows: pld-name class, Barlow 5em weight, and a seeded hue via hueFromId(folder) so each local folder gets a deterministic colour. The subtitle / path line was removed for visual consistency.

Local Filesystem Access — completed

The Electron app can now open any host directory as an editor workspace. The This PC tab lets the user browse and select a local folder; clicking opens the editor with a localRoot URL param.

Editor.ts gained a localRoot field and private _readUrl / _writeUrl helpers that branch on localRootremoteProjectprojectId. editor-shell.ts reads the localRoot URL param and sets it on the Editor singleton. file-tree-panel.ts branches on localRoot for all tree, read, write, rename, and delete operations, routing through the /api/local/ routes (Node.js fs).

Root cause of the prior /api/files//tree 404 error: projectId was '' when only localRoot was set, producing a double slash in the URL. Fixed by the three-branch URL helper.

Remote Projects in Electron — proxy approach

Online projects from roject.rokojori.com are now accessible in the Electron app via a server-side reverse proxy. source/server/routes/remoteProxy.ts is a catch-all router mounted at /api/remote: it strips the prefix, prepends /api, and forwards the request to roject.rokojori.com over HTTPS.

Authentication is automatic: the Electron onBeforeSendHeaders interceptor already injects Authorization: Bearer <token> into every localhost:3000 request, so the proxy receives the header and forwards it upstream with no new IPC channel needed.

project-list-default gained an _isRemoteOnline flag and a _projectUrl() helper that routes all API calls to /api/remote/projects when the Online tab is active. Row clicks open the editor with a remoteProject URL param; Editor.ts / editor-shell.ts read and apply it the same way as localRoot.

file-tree-panel — custom elements, icons, closed-by-default dirs

All structural HTML in the file tree was converted to hyphenated custom elements: <ftp-header>, <ftp-tree>, <ftp-list>, <ftp-dir>, <ftp-dir-label>, <ftp-file>, <ftp-inline-create>, <ftp-inline-label>, <ftp-type-error>, <ftp-empty>, <ftp-up>. CSS selectors, DOM queries, and closest() calls updated throughout (e.g. el.closest('ftp-dir') instead of el.closest('li')).

Directories now start closed. A CSS triangle indicator (border trick, no character) on ftp-dir-label::before points right when closed and rotates 90° on .open. Directories are sorted before files in renderNodes. Files and directories get SVG icons via <img src="/icons/...">; the same icons appear in tab-container header tabs. SVG sources live in source/icons/ and are copied to build/app/icons/ by scripts/copy-pages.js.

Key decisions

Server-side proxy over dual-window approach for remote projects. Option A (opening a second BrowserWindow pointed at the live site) would have introduced version skew risk when the local Electron build diverges from the server. Option B (proxy at /api/remote/**) keeps a single frontend, single server, and lets onBeforeSendHeaders handle auth automatically.

Three-branch URL helper instead of hardcoded paths. _readUrl / _writeUrl check localRoot first, then remoteProject, then fall back to projectId. This keeps all routing in one place and prevents the double-slash /api/files//tree error that occurred when projectId was empty.

Hyphenated custom element names for all structural markup. No <div class="name"> pattern anywhere in the file tree or project list. CSS uses the element tag as the root selector, keeping selectors short and scoped without class name collisions.