No description
  • TypeScript 70.4%
  • Svelte 27.2%
  • JavaScript 1.3%
  • Typst 0.5%
  • CSS 0.3%
  • Other 0.3%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Richard Ivánek 0399b8a255
All checks were successful
Check / check (push) Successful in 1m41s
chore(deps): update dependency zod to v4.6.6 (#16)
2026-10-14 22:53:39 +02:00
.forgejo/workflows fix: remove absolute import path, add unit testing to ci 2026-09-17 23:54:25 +02:00
.vscode feat: Dockerfile updates, dependency update, formatter pass 2026-07-19 11:53:32 +02:00
assets/fonts feat: invoicing support 2026-09-04 14:47:28 +02:00
drizzle fix: migrate to drizzle-orm@rc 2026-09-17 23:07:20 +02:00
scripts feat: move to node:sqlite 2026-09-17 23:51:32 +02:00
src chore(deps): update dependency @sveltejs/kit to v3.0.0-next.28 (#14) 2026-10-13 11:51:33 +02:00
static feat: initial commit 2026-07-18 09:44:03 +02:00
.dockerignore feat(deploy): add Dokploy container stack 2026-07-18 11:05:21 +02:00
.env.example feat: send invoices via smtp 2026-09-06 23:45:03 +02:00
.env.test feat(sync): add safe manual reconciliation 2026-07-18 11:31:53 +02:00
.envrc dx: direnv 2026-09-06 10:59:48 +02:00
.gitignore dx: direnv 2026-09-06 10:59:48 +02:00
.prettierignore chore: add .pnpm-store to .prettierignore 2026-09-17 23:56:40 +02:00
docker-compose.yml feat: send invoices via smtp 2026-09-06 23:45:03 +02:00
Dockerfile chore(deps): update dependency @sveltejs/kit to v3.0.0-next.28 (#14) 2026-10-13 11:51:33 +02:00
drizzle.config.ts feat: Dockerfile updates, dependency update, formatter pass 2026-07-19 11:53:32 +02:00
eslint.config.js feat: Dockerfile updates, dependency update, formatter pass 2026-07-19 11:53:32 +02:00
package.json chore(deps): update dependency @sveltejs/kit to v3.0.0-next.28 (#14) 2026-10-13 11:51:33 +02:00
PLAN.md feat: built-in time tracking (#1) 2026-09-17 14:20:44 +02:00
pnpm-lock.yaml chore(deps): update dependency zod to v4.6.6 (#16) 2026-10-14 22:53:39 +02:00
pnpm-workspace.yaml feat: move to node:sqlite 2026-09-17 23:51:32 +02:00
prettier.config.js feat: Dockerfile updates, dependency update, formatter pass 2026-07-19 11:53:32 +02:00
README.md feat: move to node:sqlite 2026-09-17 23:51:32 +02:00
renovate.json fix: migrate to drizzle-orm@rc 2026-09-17 23:07:20 +02:00
shell.nix fix: provide fonts to Chromium in CI nix-shell 2026-10-13 09:39:58 +02:00
tsconfig.json chore: update better-auth and better-sqlite 2026-08-19 22:13:00 +02:00
vite.config.ts fix: prettier formatting 2026-10-13 09:24:44 +02:00

Work Planner

Note

This project uses generative AI as a writing aid. Its architecture is fully human-designed, and all UI/UX design is human-created.

A SvelteKit application that consolidates worked hours from Invoice Ninja, GitLab, YouTrack, and Solidtime; applies effective-dated project rates; tracks hour and earnings goals; copies reviewed Solidtime entries to YouTrack; and manages clients and invoices.

Requirements

  • Node.js 26
  • pnpm (managed via corepack)
  • SQLite 3 through Node's built-in node:sqlite
  • Typst for local invoice PDF generation; it is included in the Docker image and nix direnv

Local Development

Install dependencies and create the local environment file:

pnpm install
cp .env.example .env

Set the following values in .env:

  • DATABASE_URL: SQLite file path, such as local.db.
  • ORIGIN: Local application URL, normally http://localhost:5173.
  • BETTER_AUTH_SECRET: At least 32 high-entropy characters.
  • SETTINGS_ENCRYPTION_KEY: A base64-encoded 256-bit key from openssl rand -base64 32.
  • TYPST_PATH: Optional path to the Typst binary; defaults to typst on PATH.
  • TYPST_FONT_PATH: Optional invoice font directory; defaults to ./assets/fonts.
  • SMTP_HOST, SMTP_PORT, and SMTP_TLS_MODE: Optional invoice email delivery. Use implicit for TLS from connection start or starttls to require a TLS upgrade. plaintext is restricted to loopback hosts for local test servers.
  • SMTP_USERNAME and SMTP_PASSWORD: Optional SMTP authentication; set both or neither. Keep these values in server-side environment secrets.

Run the development server:

pnpm dev

The development command migrates the database and, when it is empty, seeds sample data. Sign in with dev@example.com and development-only. Existing databases are left unchanged. Run the seed step independently with pnpm db:seed.

Database migrations also run automatically when the server starts. Create a migration after changing the Drizzle schema with pnpm db:generate and verify it against a disposable database before committing it.

Invoice Templates

Open Settings > Billing > Edit invoice template to customize your invoice PDFs. Templates are stored per user in SQLite and included in database backups. The editor loads CodeMirror and Typst syntax highlighting only on this page, without a browser-side compiler or WebAssembly.

Saving compiles VAT, non-VAT, and zero-VAT sample invoices before replacing the active template. Compiler errors preserve your edits and the previous working template. The in-app help documents data.json, optional fields, and qr.svg. Reset requires confirmation and restores the bundled default. Existing invoice PDFs remain unchanged.

Invoice detail pages load PDF previews automatically. Draft previews refresh after saved changes or an issue-date change; finalized invoices display their stored PDF. Browsers without inline PDF support can use the Open PDF link.

PDF compilation requires Linux and /bin/sh with ulimit support, as provided by the Docker image. Each compiler process is limited to 512 MiB virtual memory, a 10 MiB output file, one worker, and 30 seconds. At most two compilations run at once; further requests return a busy error. External packages and files outside the temporary render directory are unavailable.

Invoice Email Delivery

Configure the optional SMTP variables and add sender details under Settings > Billing. New billing profiles persist editable starter subject and message templates; both must remain non-empty. Finalized invoice pages default the recipient from the immutable client snapshot and attach the persisted PDF only after verifying its SHA-256 hash. Every explicit send or resend is retained in delivery history. An accepted result means the SMTP server accepted the message; it does not prove delivery or that the recipient read it.

Missing or incomplete SMTP configuration disables only email sending. Invoice creation, finalization, preview, and PDF downloads continue to work. Delivery timeouts or connection loss with an uncertain outcome are shown as unknown and are never retried automatically.

Only one delivery can be pending for an invoice. A pending attempt is never released based on its age while the application is running. On application startup, pending records left by an interrupted previous process are conservatively changed to unknown; review the recipient mailbox before an explicit resend. This recovery model relies on the documented single-replica deployment invariant.

Solidtime To YouTrack Transfers

Connect and synchronize both providers, map their external projects to the same local project, then enable YouTrack transfer in that project's settings. The Transfers page lists unsubmitted Solidtime entries, validates issue searches against the configured YouTrack project, previews the exact date, rounded minutes, type, text, and reconciliation marker, and creates one YouTrack work item per source entry.

Transferred YouTrack replicas are suppressed from dashboard and invoice totals. Transfer-enabled projects block unresolved work from invoicing in both duration modes; invoices can use either exact Solidtime seconds or submitted YouTrack minutes. Failed writes require an explicit retry, uncertain writes are reconciled by marker, and later Solidtime edits are shown as out of sync instead of being silently overwritten.

Verification

pnpm check
pnpm lint
pnpm test
pnpm build

Docker Compose

Create a deployment .env next to docker-compose.yml:

ORIGIN=https://work.example.com
BETTER_AUTH_SECRET=replace-with-at-least-32-high-entropy-characters
SETTINGS_ENCRYPTION_KEY=replace-with-output-from-openssl-rand-base64-32
# Optional SMTP secrets and settings:
# SMTP_HOST=smtp.example.com
# SMTP_PORT=587
# SMTP_TLS_MODE=starttls
# SMTP_USERNAME=work-planner
# SMTP_PASSWORD=replace-with-secret

Build and run the application:

docker compose up --build -d
docker compose ps

The Compose stack contains only the application. It exposes port 3000 to the Compose network, persists SQLite in the work_planner_data volume, and expects the deployment platform to provide HTTPS ingress. The container runs as the unprivileged node user and applies committed migrations before it accepts application requests.

Dokploy

  1. Create a Compose application from this repository.
  2. Configure the domain to route to service app on port 3000 with HTTPS enabled.
  3. Add ORIGIN, BETTER_AUTH_SECRET, and SETTINGS_ENCRYPTION_KEY as secret environment values; add the optional SMTP variables when invoice email delivery is required.
  4. Keep the generated work_planner_data volume attached across deployments.
  5. Deploy exactly one replica.
  6. Confirm that /health returns {"status":"ok"} before opening the application.

Dokploy's proxy must provide X-Forwarded-Proto, X-Forwarded-Host, and X-Forwarded-For. The Compose environment tells adapter-node to trust those headers. Do not publish the container port directly to an untrusted network when these settings are enabled.

Upgrades apply forward-only migrations during startup. Before upgrading, create a backup and retain the previous image tag. To roll back application code, redeploy the previous image only when its schema is compatible with all migrations already applied. Restore the pre-upgrade database backup when a migration is not backward compatible.

Backup And Restore

Create a consistent online SQLite backup inside the persistent volume:

docker compose exec app node scripts/backup.mjs
docker compose cp app:/app/data/backups ./backups

The backup script uses SQLite's online backup API, so it is safe while the application is running. Copy backups away from the application volume and protect them together with SETTINGS_ENCRYPTION_KEY; encrypted provider tokens are unusable without that key.

For restore:

  1. Stop the app service.
  2. Preserve the current database and its -wal and -shm files for forensic recovery.
  3. Replace /app/data/work-planner.db with the selected backup.
  4. Ensure the restored file is writable by the container's node user.
  5. Start the service and verify /health, login, settings, and report totals.

Dokploy volume snapshots are also suitable if they capture a consistent filesystem state. Prefer the online backup command when the service cannot be stopped.

Scaling

This deployment intentionally supports one application replica. SQLite and the in-process scheduler must not be shared by multiple writers. Before horizontal scaling, migrate to an external database, replace SQLite-specific locking, and run scheduled synchronization through a singleton worker or distributed job queue.

TODO

  • Add manual time entries and an active timer if later required
  • Add broader provider write-back and two-way synchronization beyond reviewed YouTrack transfers
  • Webhooks