Add Timelog: show the timer in the tab title and simplify the code

This is the first commit of the project, at the state that runs on
timelog.minhtrannhat.com.

Changes in this state:
- Remove redundant UI text from the sidebar, timer and insights pages.
- Show the remaining time and tag in the browser tab title while a
  session runs.
- Put the shared record type and the due and end helpers in
  src/shared/record.ts.
- Remove the startup tag migration and the one-time Forest import
  script. The live data does not need them.
- Remove unused CSS, Playwright ignore rules and duplicate helpers.
- Do not track Forest CSV exports, because they contain private data.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
minhtrannhatandClaude Opus 5.5 committed 2026-11-05 00:42:58 -05:00
commit a6ca255835
38 files changed
+2069

No files matched your search

+23
View File
@@ -0,0 +1,23 @@
import { mkdir } from "node:fs/promises";
import { dirname } from "node:path";
import { Database } from "bun:sqlite";
const source = process.env.DB_PATH || "./dev.sqlite";
const destination = Bun.argv[2];
if (!destination) {
console.error("Usage: bun scripts/backup.ts /path/to/new-backup.sqlite");
process.exit(1);
}
if (await Bun.file(destination).exists()) {
console.error(`Backup already exists: ${destination}`);
process.exit(1);
}
await mkdir(dirname(destination), { recursive: true });
const db = new Database(source);
try {
db.query("VACUUM INTO ?").run(destination);
console.log(`SQLite backup written to ${destination}`);
} finally {
db.close();
}
+24
View File
@@ -0,0 +1,24 @@
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
await mkdir("dist/assets", { recursive: true });
const result = await Bun.build({
entrypoints: ["src/client/main.ts"],
outdir: "dist/assets",
target: "browser",
minify: true,
naming: "app.js",
});
if (!result.success) {
for (const log of result.logs) console.error(log);
process.exit(1);
}
const buildId = Date.now().toString(36);
const html = await readFile("src/client/index.html", "utf8");
await writeFile("dist/index.html", html.replaceAll("__BUILD_ID__", buildId));
await copyFile("src/client/styles.css", "dist/styles.css");
await copyFile("src/client/manifest.webmanifest", "dist/manifest.webmanifest");
await copyFile("src/client/icon.svg", "dist/icon.svg");
for (const size of [180, 192, 512]) await copyFile(`src/client/icon-${size}.png`, `dist/icon-${size}.png`);
const worker = await readFile("src/client/sw.js", "utf8");
await writeFile("dist/sw.js", worker.replaceAll("__BUILD_ID__", buildId));
console.log("Built PWA in dist/");
+5
View File
@@ -0,0 +1,5 @@
import webpush from "web-push";
const keys = webpush.generateVAPIDKeys();
console.log(`VAPID_PUBLIC_KEY=${keys.publicKey}`);
console.log(`VAPID_PRIVATE_KEY=${keys.privateKey}`);