refactor: restructure project into source/ and build/; add source/pages/ + copy-pages build step

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Rokojori 2026-07-12 14:01:01 +02:00
parent f871804e5b
commit 0b91bdcc90
64 changed files with 307 additions and 33 deletions

5
.gitignore vendored
View File

@ -1,7 +1,4 @@
node_modules/ node_modules/
data/ build/
storage/
public/
!public/*.html
tsconfig.client.tsbuildinfo tsconfig.client.tsbuildinfo
.claude/ .claude/

2
.gitmodules vendored
View File

@ -1,3 +1,3 @@
[submodule "src/library-ts"] [submodule "src/library-ts"]
path = src/library-ts path = source/library-ts
url = git@development.rokojori.com:Josef/library-ts.git url = git@development.rokojori.com:Josef/library-ts.git

View File

@ -1,10 +1,10 @@
{ {
"name": "roject", "name": "roject",
"version": "1.0.0", "version": "1.0.0",
"main": "server/index.ts", "main": "source/server/index.ts",
"scripts": { "scripts": {
"start": "npm run build && ts-node --project tsconfig.ts-node.json server/index.ts", "start": "npm run build && ts-node --project tsconfig.ts-node.json source/server/index.ts",
"build": "tsc --build tsconfig.client.json" "build": "tsc --build tsconfig.client.json && node scripts/copy-pages.js"
}, },
"dependencies": { "dependencies": {
"@langchain/core": "^1.2.2", "@langchain/core": "^1.2.2",

13
scripts/copy-pages.js Normal file
View File

@ -0,0 +1,13 @@
const fs = require('fs');
const path = require('path');
const src = path.join(__dirname, '..', 'source', 'pages');
const dest = path.join(__dirname, '..', 'build', 'app');
fs.mkdirSync(dest, { recursive: true });
for (const file of fs.readdirSync(src)) {
if (file.endsWith('.html')) {
fs.copyFileSync(path.join(src, file), path.join(dest, file));
}
}

View File

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dashboard — Roject</title>
<link rel="stylesheet" href="/components/app-nav/app-nav.css">
</head>
<body>
<app-nav></app-nav>
<main>
<h1>Dashboard</h1>
<nav class="dashboard-links">
<a href="/groups.html">Manage Groups</a>
<a href="/projects.html">Manage Projects</a>
</nav>
</main>
<script type="module" src="/components/app-nav/app-nav.js"></script>
</body>
</html>

39
source/pages/editor.html Normal file
View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Editor — Roject</title>
<link rel="stylesheet" href="/components/context-menu/context-menu.css">
<link rel="stylesheet" href="/components/editor-shell/editor-shell.css">
<link rel="stylesheet" href="/components/tab-container/tab-container.css">
<link rel="stylesheet" href="/components/file-tree-panel/file-tree-panel.css">
<link rel="stylesheet" href="/components/html-editor-panel/html-editor-panel.css">
<link rel="stylesheet" href="/components/confirm-dialog/confirm-dialog.css">
<link rel="stylesheet" href="/components/rojo-chat-panel/rojo-chat-panel.css">
<link rel="stylesheet" href="/vendor/codemirror.min.css">
<link rel="stylesheet" href="/components/code-panel/code-panel.css">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; overflow: hidden; }
body { font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif; }
</style>
</head>
<body>
<editor-shell></editor-shell>
<script type="module" src="/components/editor-shell/editor-shell.js"></script>
<script type="module" src="/components/tab-container/tab-container.js"></script>
<script type="module" src="/components/file-tree-panel/file-tree-panel.js"></script>
<script type="module" src="/components/html-editor-panel/html-editor-panel.js"></script>
<script type="module" src="/components/confirm-dialog/confirm-dialog.js"></script>
<script src="/vendor/markdown-it.min.js"></script>
<script type="module" src="/components/rojo-chat-panel/rojo-chat-panel.js"></script>
<script src="/vendor/codemirror.min.js"></script>
<script src="/vendor/cm-mode-xml.min.js"></script>
<script src="/vendor/cm-mode-javascript.min.js"></script>
<script src="/vendor/cm-mode-css.min.js"></script>
<script src="/vendor/cm-mode-htmlmixed.min.js"></script>
<script src="/vendor/cm-mode-markdown.min.js"></script>
<script type="module" src="/components/code-panel/code-panel.js"></script>
</body>
</html>

16
source/pages/groups.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Groups — Roject</title>
<link rel="stylesheet" href="/components/app-nav/app-nav.css">
<link rel="stylesheet" href="/components/group-editor/group-editor.css">
</head>
<body>
<app-nav></app-nav>
<group-editor></group-editor>
<script type="module" src="/components/app-nav/app-nav.js"></script>
<script type="module" src="/components/group-editor/group-editor.js"></script>
</body>
</html>

13
source/pages/login.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Login — Roject</title>
<link rel="stylesheet" href="/components/login-form/login-form.css">
</head>
<body>
<login-form></login-form>
<script type="module" src="/components/login-form/login-form.js"></script>
</body>
</html>

View File

@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Projects — Roject</title>
<link rel="stylesheet" href="/components/app-nav/app-nav.css">
<link rel="stylesheet" href="/components/project-editor/project-editor.css">
<link rel="stylesheet" href="/components/confirm-dialog/confirm-dialog.css">
</head>
<body>
<app-nav></app-nav>
<project-editor></project-editor>
<script type="module" src="/components/app-nav/app-nav.js"></script>
<script type="module" src="/components/project-editor/project-editor.js"></script>
</body>
</html>

View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Register — Roject</title>
<link rel="stylesheet" href="/components/register-form/register-form.css">
</head>
<body>
<register-form></register-form>
<script type="module" src="/components/register-form/register-form.js"></script>
</body>
</html>

View File

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 16 KiB

View File

@ -2,7 +2,7 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
import { randomUUID } from 'crypto'; import { randomUUID } from 'crypto';
const DATA_DIR = path.join(__dirname, '..', 'data'); const DATA_DIR = path.join(__dirname, '..', '..', 'build', 'data', 'db');
if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR); if (!fs.existsSync(DATA_DIR)) fs.mkdirSync(DATA_DIR);
export interface User { export interface User {

View File

@ -19,14 +19,14 @@ app.use( express.json() );
app.use( express.text( { type: 'text/plain' } ) ); app.use( express.text( { type: 'text/plain' } ) );
app.use( session( app.use( session(
{ {
store: new JsonSessionStore( path.join( __dirname, '..', 'storage', 'sessions' ) ), store: new JsonSessionStore( path.join( __dirname, '..', '..', 'build', 'data', 'storage', 'sessions' ) ),
secret: 'roject-secret-key', secret: 'roject-secret-key',
resave: false, resave: false,
saveUninitialized: false, saveUninitialized: false,
cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 } cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 }
} ) ); } ) );
app.use( express.static( path.join( __dirname, '..', 'public' ) ) ); app.use( express.static( path.join( __dirname, '..', '..', 'build', 'app' ) ) );
app.use( '/api/auth', authRouter ); app.use( '/api/auth', authRouter );
app.use( '/api/groups', groupsRouter ); app.use( '/api/groups', groupsRouter );

View File

@ -1,8 +1,8 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
const LOCALES_DIR = path.join( __dirname, '..', 'locales', 'en' ); const LOCALES_DIR = path.join( __dirname, '..', 'locale-data', 'en' );
const GENERATED_DIR = path.join( __dirname, '..', 'src', 'locales', 'generated' ); const GENERATED_DIR = path.join( __dirname, '..', 'locales', 'generated' );
function toPascalCase( name: string ): string function toPascalCase( name: string ): string
{ {

View File

@ -2,12 +2,12 @@ import { Router } from 'express';
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
import { requireAuth } from '../middleware/auth'; import { requireAuth } from '../middleware/auth';
import { RJLog } from '../../src/library-ts/node/log/RJLog'; import { RJLog } from '../../library-ts/node/log/RJLog';
const router = Router(); const router = Router();
router.use( requireAuth ); router.use( requireAuth );
const LAYOUTS_DIR = path.join( __dirname, '..', '..', 'storage', 'layouts' ); const LAYOUTS_DIR = path.join( __dirname, '..', '..', '..', 'build', 'data', 'storage', 'layouts' );
function layoutFilePath( userId: string, deviceId: string ): string function layoutFilePath( userId: string, deviceId: string ): string
{ {

View File

@ -3,7 +3,7 @@ import fs from 'fs';
import path from 'path'; import path from 'path';
const router = Router(); const router = Router();
const LOCALES_DIR = path.join(__dirname, '..', '..', 'locales'); const LOCALES_DIR = path.join(__dirname, '..', '..', 'locale-data');
router.get('/:locale/*', (req, res) => { router.get('/:locale/*', (req, res) => {
const locale = req.params.locale; const locale = req.params.locale;

View File

@ -2,7 +2,7 @@ import { Router } from 'express';
import { projects, projectMembers } from '../db'; import { projects, projectMembers } from '../db';
import { requireAuth } from '../middleware/auth'; import { requireAuth } from '../middleware/auth';
import { createProjectStorage } from '../storage'; import { createProjectStorage } from '../storage';
import { RJLog } from '../../src/library-ts/node/log/RJLog'; import { RJLog } from '../../library-ts/node/log/RJLog';
const router = Router(); const router = Router();
router.use(requireAuth); router.use(requireAuth);

View File

@ -1,7 +1,7 @@
import { Router } from "express"; import { Router } from "express";
import { requireAuth } from "../middleware/auth"; import { requireAuth } from "../middleware/auth";
import { getAgentStream, updateAgentConversation } from "../rojos/RojosAgent"; import { getAgentStream, updateAgentConversation } from "../rojos/RojosAgent";
import { RJLog } from "../../src/library-ts/node/log/RJLog"; import { RJLog } from "../../library-ts/node/log/RJLog";
const router = Router(); const router = Router();
router.use( requireAuth ); router.use( requireAuth );

View File

@ -1,7 +1,7 @@
import fs from 'fs'; import fs from 'fs';
import path from 'path'; import path from 'path';
const STORAGE_DIR = path.join(__dirname, '..', 'storage'); const STORAGE_DIR = path.join(__dirname, '..', '..', 'build', 'data', 'storage');
if (!fs.existsSync(STORAGE_DIR)) fs.mkdirSync(STORAGE_DIR); if (!fs.existsSync(STORAGE_DIR)) fs.mkdirSync(STORAGE_DIR);
const INITIAL_HTML = `<!DOCTYPE html> const INITIAL_HTML = `<!DOCTYPE html>

View File

@ -8,12 +8,12 @@
"strictNullChecks": false, "strictNullChecks": false,
"skipLibCheck": true, "skipLibCheck": true,
"types": [], "types": [],
"outDir": "public", "outDir": "build/app",
"rootDir": "src" "rootDir": "source"
}, },
"include": ["src/**/*"], "include": ["source/**/*"],
"exclude": ["src/library-ts/**/*"], "exclude": ["source/library-ts/**/*"],
"references": [ "references": [
{ "path": "./src/library-ts/browser/tsconfig.roject.json" } { "path": "./source/library-ts/browser/tsconfig.roject.json" }
] ]
} }

View File

@ -8,5 +8,5 @@
"skipLibCheck": true, "skipLibCheck": true,
"outDir": "dist" "outDir": "dist"
}, },
"include": ["server/**/*"] "include": ["source/server/**/*"]
} }

View File

@ -3,5 +3,5 @@
"compilerOptions": { "compilerOptions": {
"strictNullChecks": false "strictNullChecks": false
}, },
"include": ["server/**/*", "src/library-ts/node/**/*"] "include": ["source/server/**/*", "source/library-ts/node/**/*"]
} }

View File

@ -35,6 +35,7 @@ var NAV_DATA = {
title: 'History', title: 'History',
path: 'history/index.html', path: 'history/index.html',
children: [ children: [
{ title: 'Sunday, 12 July 2026', path: 'history/2026/07-July/12-Sunday/index.html' },
{ title: 'Saturday, 11 July 2026', path: 'history/2026/07-July/11-Saturday/index.html' }, { title: 'Saturday, 11 July 2026', path: 'history/2026/07-July/11-Saturday/index.html' },
{ title: 'Friday, 10 July 2026', path: 'history/2026/07-July/10-Friday/index.html' }, { title: 'Friday, 10 July 2026', path: 'history/2026/07-July/10-Friday/index.html' },
{ title: 'Thursday, 9 July 2026', path: 'history/2026/07-July/09-Thursday/index.html' }, { title: 'Thursday, 9 July 2026', path: 'history/2026/07-July/09-Thursday/index.html' },

View File

@ -0,0 +1,138 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Session Summary — 12 July 2026</title>
<link rel="stylesheet" href="../../../../_assets_/styles.css">
<link rel="stylesheet" href="../../../../_assets_/nav.css">
</head>
<body>
<div class="page">
<header>
<p class="date">Sunday, 12 July 2026</p>
<h1>Roject — Session Summary</h1>
<p class="subtitle">
Full project directory restructure — source/, build/app/, build/data/,
source/pages/ with a copy-pages build step.
</p>
</header>
<section>
<h2>What we built</h2>
<div class="card">
<h3>Directory restructure</h3>
<p>
The project root was reorganised into two top-level directories that
cleanly separate source from build artefacts and runtime data:
</p>
<ul style="line-height:1.9;margin-top:0.75rem">
<li><code>source/</code> — all TypeScript source (components, editor, server, rojos, locales, library-ts submodule) plus locale data and HTML pages</li>
<li><code>build/app/</code> — compiled frontend output (was <code>public/</code>)</li>
<li><code>build/data/db/</code> — JSON user/project data (was <code>data/</code>)</li>
<li><code>build/data/storage/</code> — project files, sessions, layouts (was <code>storage/</code>)</li>
</ul>
<p style="margin-top:0.75rem">
The git submodule (<code>library-ts</code>) was moved with <code>git mv</code>
so <code>.gitmodules</code> stayed in sync automatically. The entire
<code>build/</code> tree is gitignored; <code>source/</code> is fully tracked.
</p>
<div class="tags">
<span class="tag">source/</span>
<span class="tag">build/app/</span>
<span class="tag">build/data/</span>
<span class="tag">git mv</span>
</div>
</div>
<div class="card">
<h3>source/pages/ + copy-pages build step</h3>
<p>
HTML pages (<code>editor.html</code>, <code>dashboard.html</code>, etc.)
moved from <code>build/app/</code> into <code>source/pages/</code> so they
are tracked in git as source files. A new <code>scripts/copy-pages.js</code>
(plain Node.js, no extra dependencies) copies them to <code>build/app/</code>
as the second step of <code>npm run build</code>:
</p>
<pre style="margin-top:0.75rem"><code>tsc --build tsconfig.client.json &amp;&amp; node scripts/copy-pages.js</code></pre>
<div class="tags">
<span class="tag">source/pages/</span>
<span class="tag">scripts/copy-pages.js</span>
<span class="tag">npm run build</span>
</div>
</div>
</section>
<section>
<h2>Key Decisions</h2>
<div class="decision">
<strong>Two locale directories: source/locales/ vs source/locale-data/</strong>
<p>
<code>src/locales/</code> (TypeScript locale management code) moved to
<code>source/locales/</code>. The root <code>locales/</code> (raw locale
data files — <code>en/</code>) moved to <code>source/locale-data/</code>.
Merging them into one directory would have mixed TypeScript source with data
files; keeping them separate makes each directory's purpose unambiguous.
</p>
</div>
<div class="decision">
<strong>build/ fully gitignored, source/pages/ tracked</strong>
<p>
The old setup had <code>public/</code> gitignored with a <code>!public/*.html</code>
exception that never actually tracked the files. The new setup is cleaner:
all hand-written HTML pages live in <code>source/pages/</code> (tracked),
<code>build/</code> is entirely ignored, and the copy step bridges the two
at build time.
</p>
</div>
<div class="decision">
<strong>Plain Node.js copy script, no npm dependency</strong>
<p>
<code>scripts/copy-pages.js</code> uses only <code>fs</code> and
<code>path</code> from the Node.js standard library. Adding a package like
<code>copyfiles</code> or <code>cpx</code> for a four-line operation would
be unnecessary complexity.
</p>
</div>
</section>
<section>
<h2>Structural Changes</h2>
<div class="card">
<p>
<code>src/</code><code>source/</code> (git mv, including library-ts submodule)<br>
<code>server/</code><code>source/server/</code> (git mv)<br>
<code>locales/</code><code>source/locale-data/</code> (git mv)<br>
<code>public/</code><code>build/app/</code> (filesystem move, gitignored)<br>
<code>data/</code><code>build/data/db/</code> (filesystem move, gitignored)<br>
<code>storage/</code><code>build/data/storage/</code> (filesystem move, gitignored)<br>
<code>source/pages/</code> — new: hand-written HTML pages (tracked)<br>
<code>scripts/copy-pages.js</code> — new: copies pages to build/app/ on build<br>
<code>tsconfig.client.json</code> — rootDir, outDir, include, exclude, references updated<br>
<code>tsconfig.json</code> — include updated<br>
<code>tsconfig.ts-node.json</code> — include updated<br>
<code>source/library-ts/browser/tsconfig.roject.json</code> — outDir updated<br>
<code>package.json</code> — main and build script updated<br>
<code>.gitignore</code> — replaced data/, storage/, public/ with build/<br>
<code>source/server/</code> — all __dirname paths and library-ts imports updated
</p>
</div>
</section>
<footer>
Roject &mdash; session log &mdash; 12 July 2026
</footer>
</div>
<script>var NAV_ROOT = '../../../../';</script>
<script src="../../../../_assets_/nav-data.js"></script>
<script src="../../../../_assets_/nav.js"></script>
</body>
</html>

View File

@ -19,6 +19,11 @@
<section> <section>
<h2>2026 — July</h2> <h2>2026 — July</h2>
<div class="card">
<h3><a href="2026/07-July/12-Sunday/index.html">Sunday, 12 July 2026</a></h3>
<p>Full directory restructure — source/, build/app/, build/data/db/, build/data/storage/, source/pages/ with copy-pages build step.</p>
</div>
<div class="card"> <div class="card">
<h3><a href="2026/07-July/11-Saturday/index.html">Saturday, 11 July 2026</a></h3> <h3><a href="2026/07-July/11-Saturday/index.html">Saturday, 11 July 2026</a></h3>
<p>nginx reverse proxy on Server A — Gitea moved to a local HTTP port, TLS termination handed to nginx, development.rokojori.com restored. Project directory restructure planned.</p> <p>nginx reverse proxy on Server A — Gitea moved to a local HTTP port, TLS termination handed to nginx, development.rokojori.com restored. Project directory restructure planned.</p>

View File

@ -279,11 +279,13 @@
<p> <p>
Vanilla HTML, raw CSS (no Tailwind, no framework). Every UI component is a Vanilla HTML, raw CSS (no Tailwind, no framework). Every UI component is a
custom element with its own <code>.ts</code> and <code>.css</code> file in custom element with its own <code>.ts</code> and <code>.css</code> file in
<code>src/components/&lt;name&gt;/</code>. CSS uses the element tag as root <code>source/components/&lt;name&gt;/</code>. CSS uses the element tag as root
selector with <code>display: block</code>. TypeScript compiles to selector with <code>display: block</code>. TypeScript compiles to
<code>public/components/</code> via <code>tsconfig.client.json</code> <code>build/app/components/</code> via <code>tsconfig.client.json</code>
(<code>module: ESNext</code>, <code>moduleResolution: bundler</code>, no (<code>module: ESNext</code>, <code>moduleResolution: bundler</code>, no
bundler). HTML pages load components with 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 <code>&lt;script type="module"&gt;</code>. Shared state uses a module-level
singleton (<code>editor-state.ts</code>) rather than globals. singleton (<code>editor-state.ts</code>) rather than globals.
Build with <code>npm run build</code>. Build with <code>npm run build</code>.
@ -301,16 +303,16 @@
<h3>Shared Library</h3> <h3>Shared Library</h3>
<p> <p>
A personal TypeScript library lives as a git submodule at A personal TypeScript library lives as a git submodule at
<code>src/library-ts/</code>. It has two parts: <code>browser/</code> <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 (DOM-capable) and <code>node/</code> (Node.js only). The browser part is
compiled separately via TypeScript project references compiled separately via TypeScript project references
(<code>src/library-ts/browser/tsconfig.roject.json</code>, <code>strict: false</code>) (<code>source/library-ts/browser/tsconfig.roject.json</code>, <code>strict: false</code>)
into <code>public/library-ts/browser/</code>. The node part is included by 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>). <code>tsconfig.ts-node.json</code> (extends server config, <code>strictNullChecks: false</code>).
</p> </p>
<div class="tags"> <div class="tags">
<span class="tag">git submodule</span> <span class="tag">git submodule</span>
<span class="tag">src/library-ts/</span> <span class="tag">source/library-ts/</span>
<span class="tag">project references</span> <span class="tag">project references</span>
<span class="tag">composite: true</span> <span class="tag">composite: true</span>
</div> </div>