boards: task/bug/backlog boards, outline shortened, workspace index improved

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Rokojori 2026-07-14 14:34:47 +02:00
parent 7b9f7a1fb9
commit 5bcabe2c67
12 changed files with 935 additions and 329 deletions

View File

@ -0,0 +1,86 @@
.board-page
{
max-width: 75em;
margin: 0 auto;
}
.board
{
display: flex;
gap: calc( var( --font-size ) * 1.5 );
align-items: flex-start;
}
.lane
{
flex: 1;
min-width: 0;
}
.lane-header
{
font-size: calc( var( --font-size ) * 1.1 );
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var( --accent );
margin-bottom: calc( var( --font-size ) * 1 );
padding-bottom: calc( var( --font-size ) * 0.5 );
border-bottom: 1px solid var( --border );
}
task-item
{
display: block;
background: var( --surface );
border: 1px solid var( --item-color, var( --border ) );
border-radius: 8px;
margin-bottom: calc( var( --font-size ) * 0.75 );
overflow: hidden;
}
task-title
{
display: block;
padding: calc( var( --font-size ) * 0.5 ) calc( var( --font-size ) * 0.85 );
font-size: calc( var( --font-size ) * 0.9 );
font-weight: 600;
color: var( --item-color, var( --text ) );
cursor: pointer;
user-select: none;
line-height: 1.5;
}
task-title::before
{
content: '▾ ';
font-size: 0.75em;
opacity: 0.6;
}
task-item.hide-content task-title::before
{
content: '▸ ';
}
task-content
{
display: block;
padding: calc( var( --font-size ) * 0.4 ) calc( var( --font-size ) * 0.85 ) calc( var( --font-size ) * 0.65 );
font-size: calc( var( --font-size ) * 0.85 );
color: var( --muted );
border-top: 1px solid var( --border );
line-height: 1.6;
}
task-item.hide-content task-content
{
display: none;
}
task-item.red { --item-color: #c96b6b; }
task-item.green { --item-color: #57a870; }
task-item.blue { --item-color: #7c8cff; }
task-item.yellow { --item-color: #c4a040; }
task-item.purple { --item-color: #9060d0; }
task-item.orange { --item-color: #c47840; }

View File

@ -2,6 +2,15 @@ var NAV_DATA = {
title: 'Workspace',
path: 'index.html',
children: [
{
title: 'Boards',
path: 'boards/index.html',
children: [
{ title: 'Tasks', path: 'boards/tasks.html' },
{ title: 'Bugs', path: 'boards/bugs.html' },
{ title: 'Backlog', path: 'boards/backlog.html' },
]
},
{
title: 'Outline',
path: 'outline/index.html',

View File

@ -26,8 +26,8 @@
inkscape:document-units="px"
showgrid="false"
inkscape:zoom="0.20890503"
inkscape:cx="1969.7946"
inkscape:cy="1179.962"
inkscape:cx="1605.9929"
inkscape:cy="1093.7985"
inkscape:window-width="1920"
inkscape:window-height="1017"
inkscape:window-x="-8"
@ -35,6 +35,34 @@
inkscape:window-maximized="1"
inkscape:current-layer="g17" /><defs
id="defs2"><linearGradient
id="linearGradient19"
inkscape:collect="never"><stop
style="stop-color:#040bff;stop-opacity:0;"
offset="0.37807184"
id="stop19" /><stop
style="stop-color:#28ece5;stop-opacity:0.26666668;"
offset="0.71330941"
id="stop22" /><stop
style="stop-color:#15ff04;stop-opacity:0.5333333;"
offset="0.86158413"
id="stop21" /><stop
style="stop-color:#f8de48;stop-opacity:1;"
offset="0.93746954"
id="stop23" /><stop
style="stop-color:#ff0404;stop-opacity:0;"
offset="1"
id="stop20" /></linearGradient><linearGradient
id="linearGradient3"
inkscape:collect="never"><stop
style="stop-color:#0495ff;stop-opacity:0;"
offset="0"
id="stop3" /><stop
style="stop-color:#0495ff;stop-opacity:1;"
offset="0.46630496"
id="stop15" /><stop
style="stop-color:#0495ff;stop-opacity:0;"
offset="1"
id="stop4" /></linearGradient><linearGradient
id="linearGradient12"
inkscape:collect="never"><stop
style="stop-color:#00bcf5;stop-opacity:1;"
@ -157,7 +185,102 @@
height="2.6460531"><feGaussianBlur
inkscape:collect="always"
stdDeviation="329.39922"
id="feGaussianBlur16" /></filter></defs><g
id="feGaussianBlur16" /></filter><linearGradient
inkscape:collect="never"
xlink:href="#linearGradient3"
id="linearGradient4"
x1="564.68774"
y1="994.08865"
x2="2793.1636"
y2="994.08865"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.7990724,0,0,0.1301594,-1341.5831,915.47103)" /><linearGradient
inkscape:collect="never"
xlink:href="#linearGradient3"
id="linearGradient15"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.7990724,0,0,0.03619034,-109.50713,1057.1184)"
x1="564.68774"
y1="994.08865"
x2="2793.1636"
y2="994.08865" /><linearGradient
inkscape:collect="never"
xlink:href="#linearGradient3"
id="linearGradient16"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.7990724,0,0,0.03619034,-1260.748,958.98764)"
x1="564.68774"
y1="994.08865"
x2="2793.1636"
y2="994.08865" /><linearGradient
inkscape:collect="never"
xlink:href="#linearGradient5"
id="linearGradient17"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(1.4051037,0,0,1.4051038,-1136.1343,-483.68581)"
x1="2094.8511"
y1="824.97723"
x2="2175.8127"
y2="1376.4214" /><radialGradient
inkscape:collect="never"
xlink:href="#linearGradient19"
id="radialGradient20"
cx="1119.998"
cy="950.70941"
fx="1119.998"
fy="950.70941"
r="624.87445"
gradientTransform="matrix(1,0,0,1.0015514,816.16034,91.868989)"
gradientUnits="userSpaceOnUse" /><radialGradient
inkscape:collect="never"
xlink:href="#linearGradient19"
id="radialGradient23"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.50283735,0,0,0.50361745,772.23007,617.91495)"
cx="1119.998"
cy="950.70941"
fx="1119.998"
fy="950.70941"
r="624.87445" /><radialGradient
inkscape:collect="never"
xlink:href="#linearGradient19"
id="radialGradient24"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.75180429,0,0,0.75297064,658.53439,589.08112)"
cx="1119.998"
cy="950.70941"
fx="1119.998"
fy="950.70941"
r="624.87445" /><filter
inkscape:collect="always"
style="color-interpolation-filters:sRGB"
id="filter24"
x="-0.166101"
y="-0.16584373"
width="1.332202"
height="1.3316875"><feGaussianBlur
inkscape:collect="always"
stdDeviation="65.026231"
id="feGaussianBlur24" /></filter><linearGradient
inkscape:collect="never"
xlink:href="#linearGradient3"
id="linearGradient24"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.55162383,0,0,0.03990891,-214.34719,-1814.9918)"
x1="564.68774"
y1="994.08865"
x2="2793.1636"
y2="994.08865" /><radialGradient
inkscape:collect="never"
xlink:href="#linearGradient19"
id="radialGradient25"
gradientUnits="userSpaceOnUse"
gradientTransform="matrix(0.75180429,0,0,0.75297064,811.71404,129.54216)"
cx="1119.998"
cy="950.70941"
fx="1119.998"
fy="950.70941"
r="624.87445" /></defs><g
inkscape:label="Content"
inkscape:groupmode="layer"
id="layer1"
@ -274,4 +397,91 @@
id="tspan14"
x="1480.9904"
y="1344.5404"
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Kode Mono';-inkscape-font-specification:'Kode Mono Bold';fill:#00cbf5;fill-opacity:1;stroke-width:16.8787">COMPUTER! ZOOM IN!</tspan></text></g></g></g></svg>
style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-family:'Kode Mono';-inkscape-font-specification:'Kode Mono Bold';fill:#00cbf5;fill-opacity:1;stroke-width:16.8787">COMPUTER! ZOOM IN!</tspan></text></g><rect
style="opacity:0.208678;mix-blend-mode:screen;fill:url(#linearGradient4);stroke:none;stroke-width:9.67817;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect3"
width="4009.1892"
height="51.572651"
x="-325.66898"
y="1019.0746"
ry="0.16230841" /><rect
style="opacity:0.053719;mix-blend-mode:screen;fill:url(#linearGradient15);stroke:none;stroke-width:5.10331;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect15"
width="4009.1892"
height="14.339585"
x="906.40698"
y="1085.9249"
ry="0.045129254" /><rect
style="opacity:0.163223;mix-blend-mode:screen;fill:url(#linearGradient16);stroke:none;stroke-width:5.10331;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect16"
width="4009.1892"
height="14.339585"
x="-244.83385"
y="987.79419"
ry="0.045129254" /><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:642.307px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;display:inline;opacity:0.05319149;mix-blend-mode:screen;fill:#04ff1d;fill-opacity:1;stroke-width:16.0577;stroke-linecap:round;stroke-linejoin:round"
x="1045.7648"
y="1203.9724"
id="text17"
transform="matrix(0.95321043,0,-0.10226778,1.0490863,0,0)"><tspan
sodipodi:role="line"
id="tspan17"
x="1045.7648"
y="1203.9724"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';fill:#04ff1d;fill-opacity:1;stroke-width:16.0577">ROJECT</tspan></text><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:650.011px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;display:inline;opacity:0.07021277;mix-blend-mode:screen;fill:#ea04ff;fill-opacity:1;stroke-width:16.2503;stroke-linecap:round;stroke-linejoin:round"
x="1042.5992"
y="1194.2871"
id="text18"
transform="matrix(0.95321043,0,-0.10226778,1.0490863,0,0)"><tspan
sodipodi:role="line"
id="tspan18"
x="1042.5992"
y="1194.2871"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';fill:#ea04ff;fill-opacity:1;stroke-width:16.2503">ROJECT</tspan></text><text
xml:space="preserve"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-size:650.011px;line-height:1.4;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';writing-mode:lr-tb;direction:ltr;display:inline;opacity:0.02553191;mix-blend-mode:screen;fill:#f7ff04;fill-opacity:1;stroke-width:16.2503;stroke-linecap:round;stroke-linejoin:round"
x="1056.5159"
y="1201.1315"
id="text19"
transform="matrix(0.95321043,0,-0.10226778,1.0490863,0,0)"><tspan
sodipodi:role="line"
id="tspan19"
x="1056.5159"
y="1201.1315"
style="font-style:italic;font-variant:normal;font-weight:900;font-stretch:normal;font-family:Barlow;-inkscape-font-specification:'Barlow Heavy Italic';fill:#f7ff04;fill-opacity:1;stroke-width:16.2503">ROJECT</tspan></text><ellipse
style="opacity:0.01652893;mix-blend-mode:screen;fill:url(#radialGradient20);fill-opacity:1;stroke:none;stroke-width:18.4735;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="path19"
cx="1936.1583"
cy="1044.0532"
rx="624.87445"
ry="625.84381" /><ellipse
style="opacity:0.0785124;mix-blend-mode:screen;fill:url(#radialGradient23);fill-opacity:1;stroke:none;stroke-width:9.28917;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="ellipse23"
cx="1335.4069"
cy="1096.7087"
rx="314.21021"
ry="314.69763" /><ellipse
style="opacity:0.10638298;mix-blend-mode:screen;fill:url(#radialGradient24);fill-opacity:1;stroke:none;stroke-width:13.8885;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter24)"
id="ellipse24"
cx="1500.5537"
cy="1304.9374"
rx="469.78329"
ry="470.51205" /><rect
style="opacity:0.208678;mix-blend-mode:screen;fill:url(#linearGradient24);stroke:none;stroke-width:2.96748;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
id="rect24"
width="1229.2803"
height="15.812984"
x="97.148048"
y="-1783.2252"
ry="0.049766306"
transform="rotate(100.78149)" /><ellipse
style="opacity:0.10743802;mix-blend-mode:screen;fill:url(#radialGradient25);fill-opacity:1;stroke:none;stroke-width:13.8885;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;filter:url(#filter24)"
id="ellipse25"
cx="1653.7334"
cy="845.39844"
rx="469.78329"
ry="470.51205"
transform="matrix(0.34298317,0,0,0.34298317,1105.6782,569.80159)" /></g></g></svg>

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -198,3 +198,90 @@ pre code
padding: 0;
font-size: inherit;
}
.board-page
{
max-width: 75em;
margin: 0 auto;
}
.board
{
display: flex;
gap: calc( var( --font-size ) * 1.5 );
align-items: flex-start;
}
.lane
{
flex: 1;
min-width: 0;
}
.lane-header
{
font-size: calc( var( --font-size ) * 1.1 );
font-weight: 600;
letter-spacing: 0.12em;
text-transform: uppercase;
color: var( --accent );
margin-bottom: calc( var( --font-size ) * 1 );
padding-bottom: calc( var( --font-size ) * 0.5 );
border-bottom: 1px solid var( --border );
}
task-item
{
display: block;
background: var( --surface );
border: 1px solid var( --item-color, var( --border ) );
border-radius: 8px;
margin-bottom: calc( var( --font-size ) * 0.75 );
overflow: hidden;
}
task-title
{
display: block;
padding: calc( var( --font-size ) * 0.5 ) calc( var( --font-size ) * 0.85 );
font-size: calc( var( --font-size ) * 0.9 );
font-weight: 600;
color: var( --item-color, var( --text ) );
cursor: pointer;
user-select: none;
line-height: 1.5;
}
task-title::before
{
content: '▾ ';
font-size: 0.75em;
opacity: 0.6;
}
task-item.hide-content task-title::before
{
content: '▸ ';
}
task-content
{
display: block;
padding: calc( var( --font-size ) * 0.4 ) calc( var( --font-size ) * 0.85 ) calc( var( --font-size ) * 0.65 );
font-size: calc( var( --font-size ) * 0.85 );
color: var( --muted );
border-top: 1px solid var( --border );
line-height: 1.6;
}
task-item.hide-content task-content
{
display: none;
}
task-item.red { --item-color: #c96b6b; }
task-item.green { --item-color: #57a870; }
task-item.blue { --item-color: #7c8cff; }
task-item.yellow { --item-color: #c4a040; }
task-item.purple { --item-color: #9060d0; }
task-item.orange { --item-color: #c47840; }

View File

@ -0,0 +1,98 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Backlog - Roject</title>
<link rel="stylesheet" href="../_assets_/styles.css">
<link rel="stylesheet" href="../_assets_/nav.css">
<link rel="stylesheet" href="../_assets_/boards.css">
</head>
<body>
<div class="board-page">
<header>
<p class="date">Board</p>
<h1>Backlog</h1>
<p class="subtitle">Big visions and user stories. Too complex or vague for tasks, but important for direction.</p>
</header>
<div class="board">
<div class="lane">
<div class="lane-header">MVP</div>
<task-item class="blue hide-content">
<task-title>Local Git Repository Integration</task-title>
<task-content>
Git integration inside the editor: file status indicators in the tree, staging,
commit, push, pull, diffs, and history. Depends on local filesystem access.
Once Roject can open an arbitrary local directory, the git repo is already there.
Implementation via simple-git (thin Node.js wrapper around the git CLI).
</task-content>
</task-item>
</div>
<div class="lane">
<div class="lane-header">Nice To Have</div>
<task-item class="purple hide-content">
<task-title>Internet Tunnel / Port Relay</task-title>
<task-content>
Allow local devices — a Stable Diffusion instance, a local LLM, a GDScript language
server — to be accessible to authorised Roject users over the internet, routed through
the Roject server via a persistent WebSocket. Primary use case: phone as a thin client
while the main machine handles all heavy processing.
</task-content>
</task-item>
<task-item class="purple hide-content">
<task-title>Real-Time Multi-User Collaboration</task-title>
<task-content>
Multiple users editing the same file simultaneously with presence indicators
and conflict resolution. Likely via operational transforms or CRDTs.
Significant architecture work; not planned for the near term.
</task-content>
</task-item>
</div>
<div class="lane">
<div class="lane-header">To The Moon</div>
<task-item class="orange hide-content">
<task-title>Mobile App (PWA first, native shell later)</task-title>
<task-content>
Make Roject usable on a phone or tablet. Quickest path: PWA with a manifest
and service worker — works in Safari and Chrome on Android and iOS without an
app store. If native capabilities are later needed, Capacitor can wrap the same
web app. A React Native or Flutter rewrite is not planned.
The realistic mobile workflow is browsing files, reading output, and light edits
rather than heavy coding.
</task-content>
</task-item>
</div>
</div>
<footer>
Roject &mdash; backlog
</footer>
</div>
<script>var NAV_ROOT = '../';</script>
<script src="../_assets_/nav-data.js"></script>
<script src="../_assets_/nav.js"></script>
<script>
document.querySelectorAll( 'task-title' ).forEach( title =>
{
title.addEventListener( 'click', () =>
{
title.closest( 'task-item' ).classList.toggle( 'hide-content' );
} );
} );
</script>
</body>
</html>

102
workspace/boards/bugs.html Normal file
View File

@ -0,0 +1,102 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Bugs - Roject</title>
<link rel="stylesheet" href="../_assets_/styles.css">
<link rel="stylesheet" href="../_assets_/nav.css">
<link rel="stylesheet" href="../_assets_/boards.css">
</head>
<body>
<div class="board-page">
<header>
<p class="date">Board</p>
<h1>Bugs</h1>
<p class="subtitle">Click a task title to expand or collapse its content.</p>
</header>
<div class="board">
<div class="lane">
<div class="lane-header">Critical</div>
<task-item class="red hide-content">
<task-title>Projects Lookup/Editing Broken</task-title>
<task-content>
The projects a user can view should only be the ones the user created
or is a member of. Currently a second account can open and edit the content
of a project belonging to the first account.
</task-content>
</task-item>
<task-item class="red hide-content">
<task-title>401 Not Handled in Data-Fetching Components</task-title>
<task-content>
Components that fetch data (groups, projects, etc.) do not handle 401 responses
gracefully — they crash when the API returns an error object instead of an array.
Should show an appropriate message or redirect to login.
</task-content>
</task-item>
</div>
<div class="lane">
<div class="lane-header">Visual / UI / UX</div>
<task-item class="yellow hide-content">
<task-title>Code Editor Needs Click to Show Content</task-title>
<task-content>
The code editor's content is not rendered immediately — it needs a click to show up.
This happens when selecting a fresh new tab or when a file is selected.
</task-content>
</task-item>
<task-item class="yellow hide-content">
<task-title>Right Panel Has No Default Content</task-title>
<task-content>
The Right panel is empty by default and relies on manual tab dragging to populate.
Should show a placeholder or hint explaining what to do.
</task-content>
</task-item>
<task-item class="yellow hide-content">
<task-title>Portrait Mode Section Switcher Not Wired</task-title>
<task-content>
The secondary section switcher in portrait mode (when a panel has multiple
side-by-side sections) is not yet functional.
</task-content>
</task-item>
<task-item class="yellow hide-content">
<task-title>Member List Shows Raw UUIDs</task-title>
<task-content>
The member list UI displays raw UUIDs instead of usernames.
Should resolve user IDs to display names via the auth service.
</task-content>
</task-item>
</div>
</div>
<footer>
Roject &mdash; bugs
</footer>
</div>
<script>var NAV_ROOT = '../';</script>
<script src="../_assets_/nav-data.js"></script>
<script src="../_assets_/nav.js"></script>
<script>
document.querySelectorAll( 'task-title' ).forEach( title =>
{
title.addEventListener( 'click', () =>
{
title.closest( 'task-item' ).classList.toggle( 'hide-content' );
} );
} );
</script>
</body>
</html>

View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Boards — 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">Boards</p>
<h1>Roject</h1>
<p class="subtitle">Planning and tracking boards for development work.</p>
</header>
<section>
<div class="card">
<h3><a href="tasks.html">Tasks</a></h3>
<p>
Current development sprint. Active work items with To Do, In Progress, and Done lanes.
We try to keep this focused — only things being worked on now or very soon.
</p>
</div>
<div class="card">
<h3><a href="bugs.html">Bugs</a></h3>
<p>
Known issues we want to fix later. Organized by severity: Critical for things that
break correctness or security, Visual/UI/UX for things that look or feel wrong.
</p>
</div>
<div class="card">
<h3><a href="backlog.html">Backlog</a></h3>
<p>
Big user stories and visions. Too complex or vague to be tasks, but important for
understanding the direction. Lanes: MVP for the core product vision,
Nice To Have for quality-of-life additions, To The Moon for longer-horizon ambitions.
</p>
</div>
</section>
<footer>
Roject &mdash; boards
</footer>
</div>
<script>var NAV_ROOT = '../';</script>
<script src="../_assets_/nav-data.js"></script>
<script src="../_assets_/nav.js"></script>
</body>
</html>

122
workspace/boards/tasks.html Normal file
View File

@ -0,0 +1,122 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tasks - Roject</title>
<link rel="stylesheet" href="../_assets_/styles.css">
<link rel="stylesheet" href="../_assets_/nav.css">
<link rel="stylesheet" href="../_assets_/boards.css">
</head>
<body>
<div class="board-page">
<header>
<p class="date">Board</p>
<h1>Tasks</h1>
<p class="subtitle">Click a task title to expand or collapse its content.</p>
</header>
<div class="board">
<div class="lane">
<div class="lane-header">To Do</div>
<task-item class="blue hide-content">
<task-title>Remote Projects in Electron</task-title>
<task-content>
Allow the Electron app to connect to roject.rokojori.com and list remote projects
alongside local ones. The JWT is already available;
it's a matter of pointing requests at the remote URL with the token.
</task-content>
</task-item>
<task-item class="blue hide-content">
<task-title>Unauthenticated Landing Screen</task-title>
<task-content>
Unauthenticated users currently crash on dashboard components.
They should land on a screen that explains the app and shows a login link.
</task-content>
</task-item>
<task-item class="blue hide-content">
<task-title>Replace browser confirm() in group editor and account delete</task-title>
<task-content>
The group editor and account delete button still use the browser confirm() dialog.
Replace with the custom &lt;confirm-dialog&gt; component already used elsewhere.
</task-content>
</task-item>
<task-item class="blue hide-content">
<task-title>MediaViewerPanel for images and PDFs</task-title>
<task-content>
Non-text files (images, PDFs) are visible in the file tree but not openable.
Add a MediaViewerPanel and register it in FileEditorRegistry for common media types.
</task-content>
</task-item>
<task-item class="blue hide-content">
<task-title>CI deploy email notification</task-title>
<task-content>
The /api/deploy endpoint should send an email notification after each successful
restart so deploys are visible without checking server logs.
</task-content>
</task-item>
<task-item class="blue hide-content">
<task-title>Switch Gitea webhook to dev branch</task-title>
<task-content>
The Gitea webhook currently triggers on pushes to main, redeploying on every commit.
Switch to a dev branch so main is the stable release target.
</task-content>
</task-item>
</div>
<div class="lane">
<div class="lane-header">In Progress</div>
<task-item class="yellow hide-content">
<task-title>Local Filesystem Access</task-title>
<task-content>
Extend the file tree to browse arbitrary directories on the host
machine using Node.js fs rather than the server's JSON-backed project storage.
</task-content>
</task-item>
</div>
<div class="lane">
<div class="lane-header">Done</div>
<task-item class="green hide-content">
<task-title>Electron Desktop App Shell</task-title>
<task-content>
Login window, JWT auth via API, Authorization header injection, token persistence,
ROJECT_ROOT path fix, ELECTRON_RUN_AS_NODE workaround. Full local-only Electron app working.
</task-content>
</task-item>
</div>
</div>
<footer>
Roject &mdash; tasks
</footer>
</div>
<script>var NAV_ROOT = '../';</script>
<script src="../_assets_/nav-data.js"></script>
<script src="../_assets_/nav.js"></script>
<script>
document.querySelectorAll( 'task-title' ).forEach( title =>
{
title.addEventListener( 'click', () =>
{
title.closest( 'task-item' ).classList.toggle( 'hide-content' );
} );
} );
</script>
</body>
</html>

View File

@ -161,20 +161,60 @@
</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>What's next</h2>
<div class="card">
<p>
Local filesystem access — extend the file tree to browse arbitrary directories
on the host machine using Node.js <code>fs</code> rather than the server's
JSON-backed project storage.
</p>
<p style="margin-top:0.75rem">
Remote projects in Electron — allow the Electron app to connect to a remote
Roject server (<code>roject.rokojori.com</code>) and list projects hosted
there alongside local ones. The JWT is already available; it's a matter of
pointing a request (or a BrowserView panel) at the remote URL with the token.
See <a href="../../../../boards/tasks.html">Tasks board</a> for the current sprint.
Local filesystem access and remote projects in Electron are the two active items.
</p>
</div>
</section>

View File

@ -21,7 +21,7 @@
<div class="card">
<h3><a href="2026/07-July/14-Monday/index.html">Monday, 14 July 2026</a></h3>
<p>Electron desktop app shell — login window, JWT auth via API, Authorization header injection, token persistence, ROJECT_ROOT path fix, ELECTRON_RUN_AS_NODE workaround.</p>
<p>Electron desktop app shell — login window, JWT auth via API, Authorization header injection, token persistence, ROJECT_ROOT path fix, ELECTRON_RUN_AS_NODE workaround. Workspace boards system (Tasks/Bugs/Backlog) with custom task-item elements; outline shortened; workspace index quick orientation added.</p>
</div>
<div class="card">

View File

@ -18,14 +18,23 @@
<section>
<div class="card">
<h3>Outline</h3>
<p>
Explains Roject's goals, features and future plans for the project.
What Roject is, what exists now, and the technical implementation.
Start here for the big picture.
<br>
<a href="./outline/index.html">Read the outline</a>
</p>
</div>
<a href="./outline/index.html">Read more about the outline</a>
<div class="card">
<h3>Boards</h3>
<p>
Current state at a glance: active tasks, known bugs, and longer-horizon backlog.
Check here to know what is being worked on right now.
<br>
<a href="./boards/index.html">Open the boards</a>
</p>
</div>
@ -34,8 +43,7 @@
<p>
Context, rules, workflows and examples for how to do things.
<br>
<a href="./guides/index.html">Read more about the guides</a>
<a href="./guides/index.html">Read the guides</a>
</p>
</div>
@ -44,18 +52,16 @@
<p>
Named tasks and steps for recurring topics.
<br>
<a href="./actions/index.html">Read more about the actions</a>
<a href="./actions/index.html">Read the actions</a>
</p>
</div>
<div class="card">
<h3>History</h3>
<p>
Per-day history that gives insights about the daily work.
Per-day session log. Good for understanding what changed and why.
<br>
<a href="./history/index.html">Read more about the history</a>
<a href="./history/index.html">Read the history</a>
</p>
</div>
@ -64,18 +70,27 @@
<p>
Technical reference for core systems: the Editor singleton, events, and the client/server compilation split.
<br>
<a href="./reference/index.html">Read more about the reference</a>
<a href="./reference/index.html">Read the reference</a>
</p>
</div>
<div>
To get the full picture, follow each page (as human or agent).
</div>
</section>
<section>
<h2>Quick orientation</h2>
<div class="card">
<p>
<strong>New here?</strong> Read Outline for the big picture, then Guides for how things are done.
Check Boards for what is active right now.
</p>
<p style="margin-top: 0.75rem">
<strong>Agents:</strong> read Outline → Guides → Reference before making changes.
Check Boards and the most recent History entry to understand the current context.
Follow all coding conventions in the Guides — do not infer style from existing code alone.
</p>
</div>
</section>
<footer>
Roject documentation

View File

@ -22,317 +22,106 @@
</header>
<section>
<h2>Project Outline</h2>
<h2>What it is</h2>
<div class="card">
<h3>What it is</h3>
<p>
Roject is a self-hosted, agent-based IDE for working with files and projects.
It provides specialised editors for different file types — a WYSIWYG HTML editor,
a CodeMirror-backed code editor, and more editors to come — all within a
multi-panel, tab-based workspace. Projects can be hosted on the Roject server
(remote) or opened directly from the local filesystem (local), making it usable
both as a lightweight self-hosted CMS and as a full desktop development environment.
It is designed for individual developers or small teams and has no external database
dependency.
a CodeMirror-backed code editor, and more to come — all within a multi-panel,
tab-based workspace. Projects can be hosted on a Roject server (remote) or opened
directly from the local filesystem (local), making it usable both as a lightweight
self-hosted CMS and as a full desktop development environment.
Designed for individual developers or small teams; no external database dependency.
</p>
</div>
</section>
<section>
<h2>What exists now</h2>
<div class="card">
<h3>Auth &amp; accounts</h3>
<p>
User accounts are managed by <strong>rokojori-auth</strong> at
<code>account.rokojori.com</code> — registration, login, JWT issuance, refresh
token rotation, roles, and account deletion. Roject verifies the shared
<code>accessToken</code> JWT cookie and transparently refreshes expired tokens
via <code>account.rokojori.com/api/auth/refresh-session</code>.
</p>
</div>
<div class="card">
<h3>What exists now</h3>
<h3>Groups, projects &amp; storage</h3>
<p>
User accounts with registration, login, logout, and account deletion.
Groups and projects with member management (viewer / editor / admin roles).
Each project gets a real directory on disk at <code>storage/&lt;uuid&gt;/root/</code>
with a default <code>index.html</code> on creation.
with a default <code>index.html</code> on creation. All data lives as JSON files
in <code>data/</code> — no external database.
</p>
<p style="margin-top:0.75rem">
</div>
<div class="card">
<h3>Editor</h3>
<p>
A full editor page (<code>/editor.html</code>) with a 3-panel resizable layout
(Left / Center / Right). Each panel holds one or more sections side by side,
each section holds a <code>&lt;tab-container&gt;</code>. Tabs are draggable
between containers. The Left panel shows the file tree with create, rename,
and delete for files and folders. The Center panel holds the WYSIWYG HTML
editor (iframe, <code>contenteditable</code>, MutationObserver, undo/redo,
Ctrl+S save). The Right panel is empty by default and receives dropped tabs.
</p>
<p style="margin-top:0.75rem">
A <code>FileEditorRegistry</code> routes files to the correct panel by suffix.
HTML files open in <code>html-editor-panel</code>; all other known text formats
open in <code>code-panel</code> (CodeMirror 5, syntax highlighting, dark theme,
Pin/Undo/Redo/Save toolbar). The registry checks an optional project-level
<code>workspace/editor/file-editors.json</code> first, then falls back to
in-memory defaults. Unknown extensions show an error in the file tree instead
of attempting to open.
</p>
<p style="margin-top:0.75rem">
A <code>rojo-chat-panel</code> provides a streaming AI chat interface backed
by a LangChain + OpenAI-compatible model. Each panel instance holds its own
conversation session in memory.
A reusable <code>&lt;confirm-dialog&gt;</code> component replaces browser
<code>confirm()</code> for destructive actions (currently project deletion).
An <code>EmailService</code> static facade (<code>source/server/email/</code>)
provides a single <code>sendEmail()</code> entry point backed by a swappable
<code>EmailSender</code> interface; the default implementation is
<code>SMTPEmailSender</code> (Nodemailer), configured via environment variables.
</p>
<p style="margin-top:0.75rem">
The app is deployed and publicly accessible at
<code>https://roject.rokojori.com</code> on Server A. nginx handles TLS
termination and reverse-proxies to the Node.js process managed by a
systemd service. Deployment is currently manual
(<code>git pull &amp;&amp; npm run build &amp;&amp; systemctl restart roject</code>);
CI automation is the remaining open item.
(Left / Center / Right). Each panel holds one or more sections side by side;
each section holds a <code>&lt;tab-container&gt;</code> with drag-and-drop tabs.
The Left panel shows the file tree (create, rename, delete).
A <code>FileEditorRegistry</code> routes files to the correct panel by extension:
HTML → <code>html-editor-panel</code> (iframe, contenteditable, MutationObserver,
undo/redo, Ctrl+S save); all other text formats → <code>code-panel</code>
(CodeMirror 5, syntax highlighting, dark theme). An optional project-level
<code>workspace/editor/file-editors.json</code> overrides the defaults.
</p>
</div>
<div class="card">
<h3>What still needs work</h3>
<p>Items below are ordered by priority. Major planned features first, then smaller open improvements.</p>
</div>
<div class="card">
<h3>1 — Local filesystem access</h3>
<h3>AI chat &amp; utilities</h3>
<p>
The Electron shell is done (see below). The remaining work is extending the
file tree to browse arbitrary directories on the host machine using Node.js
<code>fs</code> directly, rather than being restricted to the
<code>storage/&lt;uuid&gt;/root/</code> paths managed by the server. This is
what turns Roject from a hosted CMS into something closer to VS Code — the
user can open any folder on their machine as a project.
A <code>rojo-chat-panel</code> provides a streaming AI chat interface backed by
LangChain + OpenAI-compatible models. A reusable <code>&lt;confirm-dialog&gt;</code>
component replaces browser <code>confirm()</code> for destructive actions.
An <code>EmailService</code> facade (<code>source/server/email/</code>) provides
a <code>sendEmail()</code> entry point backed by a swappable <code>EmailSender</code>
interface; the default is <code>SMTPEmailSender</code> (Nodemailer).
</p>
<div class="tags">
<span class="tag">Node.js fs</span>
<span class="tag">file tree extension</span>
<span class="tag">local mode</span>
</div>
</div>
<div class="card">
<h3>2 — Remote projects in Electron</h3>
<h3>Electron desktop app</h3>
<p>
The Electron app currently runs a fully local Express server with its own
data store — it shares the same identity as the web version (via rokojori-auth)
but not the same projects. The next step is to allow the Electron app to also
connect to a remote Roject server (e.g. <code>roject.rokojori.com</code>) and
list, open, and edit projects hosted there, alongside any local filesystem
projects.
Roject runs as a standalone Windows desktop application. The Express server starts
in-process inside Electron's main process. A custom login window calls the auth
API directly; tokens are stored in <code>userData/tokens.json</code> and re-used
across sessions. All requests to <code>localhost</code> have
<code>Authorization: Bearer</code> injected automatically via
<code>session.webRequest.onBeforeSendHeaders</code>. Run with
<code>npm run electron:dev</code>.
</p>
<p style="margin-top:0.75rem">
The Electron app already holds a valid JWT and can send it as an
<code>Authorization: Bearer</code> header. Connecting to a remote server is
therefore a matter of pointing a second BrowserWindow (or a panel in the
existing window) at the remote URL and injecting the token — no new auth
work needed.
</p>
<div class="tags">
<span class="tag">remote Roject server</span>
<span class="tag">Authorization header</span>
<span class="tag">mixed local + remote</span>
</div>
</div>
<div class="card">
<h3>3 — Local git repository integration</h3>
<h3>Deployment &amp; CI</h3>
<p>
Git integration inside the editor: file status indicators in the tree, staging,
commit, push and pull, and eventually diffs and history. This depends on local
filesystem access (feature 2) and follows naturally from it — once Roject can
open an arbitrary local directory, the git repo that directory belongs to is
already there.
Live at <code>https://roject.rokojori.com</code> on Server A. nginx handles TLS
and reverse-proxies to a Node.js process managed by systemd. Auto-deploy on push
to <code>main</code> via a Gitea webhook calling <code>POST /api/deploy</code>
(HMAC-SHA256 verified). The deploy script runs detached so it survives the
<code>systemctl restart</code> that kills the parent process.
</p>
<p style="margin-top:0.75rem">
The implementation uses <code>simple-git</code>, a thin Node.js wrapper around
the git CLI, which keeps the dependency surface small and relies on the user's
existing git installation. An alternative is <code>isomorphic-git</code> (pure
JS, works in the browser too), but the CLI wrapper is simpler for a first
iteration. A new panel in the editor displays repo status and exposes the
common operations.
</p>
<div class="tags">
<span class="tag">simple-git</span>
<span class="tag">git CLI</span>
<span class="tag">new panel</span>
</div>
</div>
</section>
<section>
<h2>What's next</h2>
<div class="card">
<h3>4 — Internet tunnel / port pass-through relay</h3>
<p>
A tunneling feature that allows local devices — a main workstation running
Stable Diffusion, a local LLM, a GDScript language server, or any other
service — to be accessible to authorised Roject users over the internet,
routed through the Roject server.
Current tasks, known bugs, and longer-horizon features are tracked on the
<a href="../boards/index.html">Boards</a>.
</p>
<p style="margin-top:0.75rem">
The architecture: a small local agent (a Node.js script, or a built-in Roject
feature) connects to the Roject server via a persistent WebSocket, identifying
itself with a secret key. The server maps that key to a Roject user and
permission record. When an authorised Roject user (e.g. on their phone) makes
a request to the relay endpoint, the server forwards it through the WebSocket
to the local agent, which proxies it to the configured local port, and returns
the response the same way.
</p>
<p style="margin-top:0.75rem">
The primary use case is mobile-to-desktop: use a phone as a thin client while
the main machine handles all heavy processing (image generation, inference,
language server completions). The secret key is the only credential needed —
it is registered once in Roject's user system and then shared with the local
agent. A single pass-through maps one local port to one authorised user.
</p>
<p style="margin-top:0.75rem">
This feature is architecturally independent of Electron and mobile and lives
entirely on the server, so it can be developed in parallel with the desktop
work. It is placed here because its primary value is unlocked only once mobile
access (feature 5) also exists.
</p>
<div class="tags">
<span class="tag">WebSocket relay</span>
<span class="tag">HTTP proxy</span>
<span class="tag">secret key / auth</span>
<span class="tag">local agent</span>
</div>
</div>
<div class="card">
<h3>5 — Mobile app (PWA first, native shell later)</h3>
<p>
Make Roject usable on a phone or tablet. The quickest path given the existing
web frontend is a Progressive Web App (PWA) — a manifest file and a service
worker. This costs almost nothing to add, works in Safari and Chrome on both
Android and iOS without an app store, and covers the core use case of reaching
the editor and the tunnel relay from a mobile browser.
</p>
<p style="margin-top:0.75rem">
If native capabilities are later needed (background processing, push
notifications, deeper OS integration), Capacitor can wrap the same web app
in a native shell without requiring a framework rewrite. A full React Native
or Flutter rewrite would be a significant departure from the existing Web
Components architecture and is not planned.
</p>
<p style="margin-top:0.75rem">
The main challenge of a mobile editing experience is the code editor — CodeMirror
on a touchscreen is not great for authoring. The realistic mobile workflow is
lighter interaction: browsing files, reading output, triggering generation
requests through the tunnel relay, and simple edits rather than heavy coding.
</p>
<div class="tags">
<span class="tag">PWA</span>
<span class="tag">manifest + service worker</span>
<span class="tag">Capacitor (later)</span>
<span class="tag">no React Native</span>
</div>
</div>
<div class="card">
<h3>Done — Electron desktop app shell</h3>
<p>
Roject runs as a standalone desktop application on Windows. The Express server
starts in-process inside Electron's main process. A custom login window
(<code>electron/login.html</code>) collects credentials and calls
<code>POST https://account.rokojori.com/api/auth/login</code> directly from
the main process — no browser redirect, no cookie. Tokens are stored in
<code>userData/tokens.json</code> and re-used across sessions.
</p>
<p style="margin-top:0.75rem">
All HTTP requests from the BrowserWindow to <code>localhost</code> have
<code>Authorization: Bearer &lt;accessToken&gt;</code> injected automatically
via <code>session.webRequest.onBeforeSendHeaders</code> — the frontend requires
zero changes. Expired tokens are refreshed via
<code>POST /api/auth/refresh</code> and the page is reloaded transparently.
Any navigation away from <code>localhost</code> is intercepted and redirected
to the Electron login window instead.
</p>
<p style="margin-top:0.75rem">
Known issue: <code>ELECTRON_RUN_AS_NODE=1</code> is set by VS Code / Claude
Code, which makes Electron behave as plain Node.js. The launcher script
(<code>scripts/launch-electron.js</code>) deletes this variable before
spawning the binary. Run with <code>npm run electron:dev</code> or
<code>node scripts/launch-electron.js</code> after the build.
</p>
<div class="tags">
<span class="tag">electron/main.ts</span>
<span class="tag">webRequest header injection</span>
<span class="tag">token persistence</span>
<span class="tag">ELECTRON_RUN_AS_NODE workaround</span>
</div>
</div>
<div class="card">
<h3>Done — CI/CD pipeline</h3>
<p>
Auto-deploy on push to main via a Gitea webhook calling
<code>POST /api/deploy</code> on the Roject server. The endpoint verifies the
<code>X-Gitea-Signature</code> HMAC-SHA256 signature, checks the branch is
<code>main</code>, responds immediately, then spawns a detached bash process
that runs <code>git pull &amp;&amp; npm run build &amp;&amp; systemctl restart roject</code>.
The detached process survives the <code>systemctl restart</code> that kills the parent.
</p>
<div class="tags">
<span class="tag">Gitea webhook</span>
<span class="tag">HMAC-SHA256</span>
<span class="tag">detached spawn</span>
</div>
</div>
<div class="card">
<h3>Done — Centralized auth: rokojori-auth</h3>
<p>
The standalone auth service <strong>rokojori-auth</strong> is built and live at
<code>account.rokojori.com</code>. It handles registration, login, JWT issuance,
refresh token rotation, password reset, roles (user / admin / superadmin),
products, global settings, rate limiting, and account deletion.
Roject has not yet been integrated — that is the next step.
</p>
<p style="margin-top:0.75rem">
See <a href="auth-restructure.html">auth-restructure</a> for the full plan and
the rokojori-auth workspace for implementation details.
</p>
<div class="tags">
<span class="tag">rokojori-auth</span>
<span class="tag">account.rokojori.com</span>
<span class="tag">JWT</span>
<span class="tag">centralized identity</span>
</div>
</div>
<div class="card">
<h3>Done — Roject integration with rokojori-auth</h3>
<p>
Roject is now a JWT-validating client. The local session-based user system has
been removed and replaced with a JWT verification middleware that reads the shared
<code>accessToken</code> cookie on <code>.rokojori.com</code>. Expired tokens are
transparently refreshed via
<code>account.rokojori.com/api/auth/refresh-session?redirect=...</code>.
Login and logout links in <code>app-nav</code> point to
<code>account.rokojori.com</code>. All data references use <code>userId</code>
from the JWT payload. A <code>GET /api/auth/logout?redirect=...</code> endpoint
was also added to rokojori-auth to support browser-based logout links.
</p>
<div class="tags">
<span class="tag">JWT middleware</span>
<span class="tag">cookie-parser</span>
<span class="tag">jsonwebtoken</span>
<span class="tag">account.rokojori.com</span>
</div>
</div>
<div class="card">
<h3>Smaller open improvements</h3>
<ul style="line-height:1.9">
<li>Unauthenticated users should land on a landing screen that explains the app and shows a login link, rather than crashing on dashboard components.</li>
<li>Components that fetch data (groups, projects, etc.) do not handle <code>401</code> responses gracefully — they crash when the API returns an error object instead of an array.</li>
<li>The Right panel has no default content and relies on manual tab dragging to populate.</li>
<li>Portrait mode's secondary section switcher (when a panel has multiple side-by-side sections) is not yet wired up.</li>
<li>The member list UI shows raw UUIDs instead of usernames.</li>
<li>The group editor and account delete button still use the browser <code>confirm()</code> instead of the custom <code>&lt;confirm-dialog&gt;</code>.</li>
<li>Non-text files (images, PDFs) are visible in the tree but not openable — a <code>MediaViewerPanel</code> is planned.</li>
<li>No real-time multi-user collaboration yet.</li>
<li>CI deploy endpoint (<code>/api/deploy</code>) should send an email notification after each restart so deploys are visible without checking logs.</li>
<li>The Gitea webhook currently triggers on pushes to <code>main</code> — switch to a <code>dev</code> branch so every commit doesn't redeploy.</li>
</ul>
</div>
</section>
<section>
@ -342,13 +131,10 @@
<h3>Backend</h3>
<p>
Node.js + Express, TypeScript compiled on the fly with <code>ts-node</code>.
No database — all data lives as JSON files in <code>data/</code>
(auto-created on first run). Auth is handled by <code>rokojori-auth</code>
at <code>account.rokojori.com</code>; Roject verifies the shared
Auth is handled by <code>rokojori-auth</code>; Roject verifies the shared
<code>accessToken</code> JWT cookie using <code>jsonwebtoken</code> +
<code>cookie-parser</code>. All entity IDs are UUIDs via
<code>crypto.randomUUID()</code> — no central counter, safe for parallel
instances. Start the server with <code>npm start</code>.
<code>crypto.randomUUID()</code>. Start the server with <code>npm start</code>.
</p>
<div class="tags">
<span class="tag">Node.js</span>
@ -364,13 +150,12 @@
<h3>Frontend — Editor Singleton &amp; Client/Server Split</h3>
<p>
The <code>Editor</code> singleton (<code>src/editor/Editor.ts</code>) is the
central hub of the frontend editor — it owns all open document state, the
<code>FileEditorRegistry</code>, and the five events that panels and the tab
container subscribe to (<code>onDocumentOpened</code>,
<code>onDocumentDirty</code>, <code>onDocumentSaved</code>,
<code>onFilesChanged</code>, <code>onFileTypeUnknown</code>). For the full
event reference and the TypeScript compilation split between client and server,
see the
central hub of the frontend — it owns all open document state, the
<code>FileEditorRegistry</code>, and the five events that panels and tab containers
subscribe to (<code>onDocumentOpened</code>, <code>onDocumentDirty</code>,
<code>onDocumentSaved</code>, <code>onFilesChanged</code>,
<code>onFileTypeUnknown</code>). For the full event reference and the TypeScript
compilation split between client and server, see the
<a href="../reference/editor-singleton/index.html">Editor Singleton reference</a>.
</p>
</div>
@ -383,13 +168,9 @@
<code>source/components/&lt;name&gt;/</code>. CSS uses the element tag as root
selector with <code>display: block</code>. TypeScript compiles to
<code>build/app/components/</code> via <code>tsconfig.client.json</code>
(<code>module: ESNext</code>, <code>moduleResolution: bundler</code>, no
bundler). HTML pages live in <code>source/pages/</code> and are copied to
<code>build/app/</code> by <code>scripts/copy-pages.js</code> as part of the
build. HTML pages load components with
<code>&lt;script type="module"&gt;</code>. Shared state uses a module-level
singleton (<code>editor-state.ts</code>) rather than globals.
Build with <code>npm run build</code>.
(<code>module: ESNext</code>, no bundler). HTML pages live in
<code>source/pages/</code> and are copied to <code>build/app/</code> by
<code>scripts/copy-pages.js</code>. Build with <code>npm run build</code>.
</p>
<div class="tags">
<span class="tag">Web Components</span>
@ -406,10 +187,9 @@
A personal TypeScript library lives as a git submodule at
<code>source/library-ts/</code>. It has two parts: <code>browser/</code>
(DOM-capable) and <code>node/</code> (Node.js only). The browser part is
compiled separately via TypeScript project references
(<code>source/library-ts/browser/tsconfig.roject.json</code>, <code>strict: false</code>)
into <code>build/app/library-ts/browser/</code>. The node part is included by
<code>tsconfig.ts-node.json</code> (extends server config, <code>strictNullChecks: false</code>).
compiled separately via TypeScript project references into
<code>build/app/library-ts/browser/</code>. The node part is included by
<code>tsconfig.ts-node.json</code>.
</p>
<div class="tags">
<span class="tag">git submodule</span>