- TypeScript 70.4%
- Svelte 27.2%
- JavaScript 1.3%
- Typst 0.5%
- CSS 0.3%
- Other 0.3%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
|
||
| .forgejo/workflows | ||
| .vscode | ||
| assets/fonts | ||
| drizzle | ||
| scripts | ||
| src | ||
| static | ||
| .dockerignore | ||
| .env.example | ||
| .env.test | ||
| .envrc | ||
| .gitignore | ||
| .prettierignore | ||
| docker-compose.yml | ||
| Dockerfile | ||
| drizzle.config.ts | ||
| eslint.config.js | ||
| package.json | ||
| PLAN.md | ||
| pnpm-lock.yaml | ||
| pnpm-workspace.yaml | ||
| prettier.config.js | ||
| README.md | ||
| renovate.json | ||
| shell.nix | ||
| tsconfig.json | ||
| vite.config.ts | ||
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 aslocal.db.ORIGIN: Local application URL, normallyhttp://localhost:5173.BETTER_AUTH_SECRET: At least 32 high-entropy characters.SETTINGS_ENCRYPTION_KEY: A base64-encoded 256-bit key fromopenssl rand -base64 32.TYPST_PATH: Optional path to the Typst binary; defaults totypstonPATH.TYPST_FONT_PATH: Optional invoice font directory; defaults to./assets/fonts.SMTP_HOST,SMTP_PORT, andSMTP_TLS_MODE: Optional invoice email delivery. Useimplicitfor TLS from connection start orstarttlsto require a TLS upgrade.plaintextis restricted to loopback hosts for local test servers.SMTP_USERNAMEandSMTP_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
- Create a Compose application from this repository.
- Configure the domain to route to service
appon port3000with HTTPS enabled. - Add
ORIGIN,BETTER_AUTH_SECRET, andSETTINGS_ENCRYPTION_KEYas secret environment values; add the optional SMTP variables when invoice email delivery is required. - Keep the generated
work_planner_datavolume attached across deployments. - Deploy exactly one replica.
- Confirm that
/healthreturns{"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:
- Stop the app service.
- Preserve the current database and its
-waland-shmfiles for forensic recovery. - Replace
/app/data/work-planner.dbwith the selected backup. - Ensure the restored file is writable by the container's
nodeuser. - 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