A SvelteKit web application that automates generating Georgian-format monthly timesheets. It computes worked days, vacation days, and public holidays, fills an official DOCX template via XML manipulation, and returns the completed document — ready for submission.
Available as a web app (browser-based) and a desktop app (macOS, Windows, Linux) via Tauri.
Built for organizations operating under Georgian labor regulations that require standardized monthly timesheet forms.
.docx or legacy .doc (via LibreOffice CLI conversion).8 (worked), შ (paid vacation), X (holiday/weekend)..nvmrc).doc export)git clone https://github.com/gati3478/timesheet-studio.git
cd timesheet-studio
npm start
npm start handles everything automatically:
node_modules/ missing).doc source (if not already built)Override host/port with environment variables:
HOST=0.0.0.0 PORT=3000 npm start
If you prefer step-by-step control:
# 1. Install dependencies
npm install
# 2. Convert the source .doc template to .docx (requires LibreOffice)
npm run prepare:template
# 3. Start the dev server
npm run dev
Verify your environment is ready:
npm run doctor
Timesheet Studio — Environment Check
─────────────────────────────────────
✓ Node.js 24.x.x
✓ npm 11.x.x
✓ Dependencies installed
✓ DOCX template ready
✓ Source template (.doc) present
✓ LibreOffice available (DOC export + template prep supported)
✓ Port 5173 available
─────────────────────────────────────
Results: 7 passed, 0 warnings, 0 failed
| Command | Description |
|---|---|
npm start |
Full launcher: install, prepare, start, open browser |
npm run dev |
Start Vite dev server on port 5173 |
npm run dev:open |
Start dev server and open browser automatically |
npm run build |
Production build |
npm run start:prod |
Run production build (node build) |
npm run preview |
Preview production build |
npm run check |
Type-check with svelte-check |
npm run lint |
Run Prettier + ESLint |
npm run format |
Auto-format all files with Prettier |
npm test |
Run all tests (alias for test:unit) |
npm run test:unit |
Run Vitest unit & integration tests |
npm run test:coverage |
Run unit tests with coverage report |
npm run test:e2e |
Run Playwright end-to-end tests |
npm run test:all |
Run unit + e2e tests sequentially |
npm run prepare:template |
Convert .doc → .docx template |
npm run doctor |
Environment health check |
npm run clean |
Remove build artifacts |
npm run tauri:build |
Build desktop app (macOS/Windows/Linux) |
npm run tauri:dev |
Launch Tauri dev window (needs npm run dev first) |
npm run bundle:sidecar |
Bundle SvelteKit server + Node.js for Tauri sidecar |
The project uses Prettier for formatting and ESLint for linting with TypeScript and Svelte support.
# Check formatting and lint rules
npm run lint
# Auto-format everything
npm run format
CI runs lint, type checking, and tests on every push and pull request.
npm run build
The build output goes to build/. SvelteKit uses adapter-node for server-side deployment.
| Platform | Adapter | Notes |
|---|---|---|
| Node.js | adapter-node |
Default — self-hosted, run node build |
| Vercel | adapter-vercel |
Swap adapter in svelte.config.js |
| Netlify | adapter-netlify |
Swap adapter in svelte.config.js |
| Cloudflare | adapter-cloudflare |
Swap adapter in svelte.config.js |
Note: The
.docexport requires LibreOffice on the server. DOCX export works everywhere. Serverless/edge adapters (Vercel, Netlify, Cloudflare Workers) need compatibility review — DOC export useschild_processand holiday fetching uses Node.js-specific streaming APIs not available in edge runtimes.
# DOCX-only (lightweight)
docker compose up app
# With .doc export support (includes LibreOffice)
docker compose --profile full up app-full
The app is available at http://localhost:3000. To use a different host port: PORT=8080 docker compose up app (app then at http://localhost:8080).
For custom builds:
docker build -t timesheet-studio .
docker run -p 3000:3000 timesheet-studio
Test the production build before deploying:
npm run build
npm run preview
Timesheet Studio is also available as a standalone desktop application powered by Tauri v2. No Node.js or browser required — just download and run.
Pre-built binaries for macOS, Windows, and Linux are available on the Releases page.
| Platform | Format | Size |
|---|---|---|
| macOS (Apple Silicon) | .dmg |
~45 MB |
| Windows | .msi |
~40 MB |
| Linux (Fedora/RHEL) | .rpm |
~50 MB |
| Linux (universal) | .AppImage |
~125 MB |
The
.AppImageis larger because it bundles a portable runtime and all shared libraries so it runs on any distro. The.rpmdefers those to system packages.
Prerequisites: Node.js v24 LTS, Rust toolchain, platform-specific dependencies (Tauri prerequisites).
# One command builds everything: SvelteKit → sidecar bundle → Tauri app
npm run tauri:build
The output is in src-tauri/target/release/bundle/ — a .dmg on macOS, .msi on Windows, or .rpm/.AppImage on Linux.
The desktop app uses Tauri's sidecar architecture: Tauri provides a lightweight native window using the OS webview (WebKit on macOS, WebView2 on Windows), while the SvelteKit server runs as a bundled Node.js process inside the app. The webview connects to the server via localhost on a random port. On exit, Tauri gracefully shuts down the server.
┌─────────────────────────────────────┐
│ Tauri App (.app / .exe / .AppImage)│
│ │
│ ┌───────────────────────────────┐ │
│ │ Native Webview (OS WebKit) │ │
│ │ → http://127.0.0.1:<port> │ │
│ └──────────────┬────────────────┘ │
│ │ │
│ ┌──────────────▼────────────────┐ │
│ │ Node.js Sidecar │ │
│ │ (SvelteKit server, bundled │ │
│ │ with esbuild into ~3 MB) │ │
│ └───────────────────────────────┘ │
└─────────────────────────────────────┘
For working on the desktop app itself:
# Terminal 1: Start the SvelteKit dev server
npm run dev
# Terminal 2: Launch Tauri with hot-reload
npm run tauri:dev
┌─────────────────────────────────────────────────────────┐
│ Browser (Svelte 5) │
│ │
│ ┌──────────────┐ ┌────────────────────────────────┐ │
│ │ Control Panel │ │ Interactive Calendar │ │
│ │ │ │ │ │
│ │ • Year/Month │ │ Mon Tue Wed Thu Fri Sat Sun │ │
│ │ • Profile │ │ 1 2 3 4 5 6 7 │ │
│ │ • Format │ │ [8] [8] [8] [8] [8] [X] [X] │ │
│ │ • Generate │ │ 8 9 10 11 12 13 14 │ │
│ └──────────────┘ │ [8] [შ] [8] [X] [8] [X] [X] │ │
│ └────────────────────────────────┘ │
│ │
│ ┌─────────┐ ┌─────────┐ ┌──────────┐ ┌────────────┐ │
│ │ Worked │ │Vacation │ │ Holidays │ │Month Split │ │
│ │ 20 days │ │ 1 day │ │ 2 days │ │ 88 / 72 │ │
│ └─────────┘ └─────────┘ └──────────┘ └────────────┘ │
└──────────────────────────┬──────────────────────────────┘
│ POST /api/timesheet/generate
▼
┌─────────────────────────────────────────────────────────┐
│ SvelteKit Server │
│ │
│ 1. Validate request (dates, profile fields) │
│ 2. Fetch holidays (cached, dual-source fallback) │
│ 3. Compute day codes for each day of the month │
│ 4. Load DOCX template → extract XML via JSZip │
│ 5. Fill cells using XPath DOM manipulation │
│ 6. Set Sylfaen font for Georgian text rendering │
│ 7. Apply gray shading to holiday/weekend cells │
│ 8. Repack ZIP → return binary download │
│ 9. (Optional) Convert to .doc via LibreOffice CLI │
└─────────────────────────────────────────────────────────┘
| Code | Meaning | Hours | Cell Style |
|---|---|---|---|
8 |
Worked day | 8h | Default |
შ |
Paid vacation (Georgian letter) | 8h | Blue highlight |
X |
Holiday or weekend | 0h | Gray shading |
| (empty) | Day beyond month end (e.g., day 30 in Feb) | — | — |
The DOCX template is a ZIP archive containing XML documents. The filling process:
.docx file@xmldom/xmldom parses word/document.xml into a DOM tree.docx → .doc for legacy formatGET /api/holidays?year={yyyy}Returns Georgian public holidays for a given year. Results are cached for 6 hours. Fetches from date.nager.at (primary) with yell.ge as fallback.
Response:
{
"year": 2026,
"entries": [
{ "date": "2026-01-01", "title": "New Year's Day", "isStateOnly": false },
{ "date": "2026-01-07", "title": "Christmas Day", "isStateOnly": false }
]
}
GET /api/capabilitiesReturns runtime capabilities of the server (e.g., whether LibreOffice is installed for .doc export). The frontend uses this to show or hide format options.
Response:
{
"docExportAvailable": true
}
POST /api/timesheet/generateGenerates a filled timesheet document from the template.
Request:
{
"year": 2026,
"month": 1,
"companyCode": "123456789",
"employeeName": "ნინო ბერიძე, პროგრამისტი",
"employeeId": "12345678901",
"vacationDates": ["2026-01-15"],
"outputFormat": "docx"
}
Response: Binary file download with Content-Disposition header.
| Field | Type | Validation |
|---|---|---|
year |
number |
Required |
month |
number |
1–12 |
companyCode |
string |
6–12 digits |
employeeName |
string |
Non-empty, must contain text |
employeeId |
string |
Exactly 11 digits |
vacationDates |
string[] |
ISO dates, must be weekdays and non-holidays |
outputFormat |
"docx" | "doc" |
doc requires LibreOffice |
POST /api/system/shutdownGracefully shuts down the local server via SIGTERM (used by the npm start launcher). Only available in development mode — returns 404 in production builds. Restricted to localhost clients (127.0.0.1 / ::1).
src/
├── app.html # SvelteKit HTML entry point
├── app.css # Global styles & design tokens
├── app.d.ts # Ambient TypeScript declarations
├── hooks.server.ts # Security headers hook
├── routes/
│ ├── +page.svelte # Page orchestration & state management
│ ├── +page.server.ts # Server-side load (capabilities detection)
│ ├── +layout.svelte # Root layout
│ └── api/
│ ├── capabilities/+server.ts # Runtime capability detection
│ ├── holidays/+server.ts # Holiday fetching endpoint
│ ├── timesheet/generate/+server.ts # Document generation endpoint
│ └── system/shutdown/+server.ts # Local server shutdown (dev only)
└── lib/
├── constants.ts # Shared constants (months, weekdays)
├── calendar-types.ts # DayItem & CalendarCell types
├── content-disposition.ts # Content-Disposition header parsing
├── filename.ts # Output filename generation
├── profile.ts # Profile persistence (localStorage)
├── slugify.ts # Unicode-safe string slugification
├── tauri.ts # Tauri webview detection
├── components/
│ ├── MonthPicker.svelte # Year/month navigation controls
│ ├── ProfileEditor.svelte # Employee profile form
│ ├── StatusMessage.svelte # Error/success/info banners
│ ├── SummaryMetrics.svelte # Worked/vacation/holiday counters
│ ├── TitleBar.svelte # Custom window title bar (Tauri)
│ └── VacationCalendar.svelte # Interactive vacation day picker
└── server/
├── types.ts # Server-side TypeScript types
├── capabilities.ts # LibreOffice / DOC export detection
├── parse-payload.ts # Request validation & length limits
├── timesheet.ts # Day-code computation logic
├── docx.ts # DOCX XML template filling
├── doc-conversion.ts # DOCX → DOC via LibreOffice
├── holidays.ts # Holiday fetching & caching
├── georgian-holidays.json # Static holiday fallback data
└── template.ts # Template buffer loader
scripts/
├── start.mjs # Unified launcher (npm start)
├── prepare-template.mjs # .doc → .docx template conversion
├── bundle-sidecar.mjs # Tauri sidecar bundler (esbuild + Node download)
├── doctor.mjs # Environment health check
├── clean.mjs # Build artifact cleanup
├── capture-ui-docs.mjs # Screenshot capture for documentation
└── take-screenshot.mjs # README hero screenshot capture
src-tauri/ # Tauri desktop app (Rust)
├── src/
│ ├── main.rs # Entry point
│ └── lib.rs # Sidecar lifecycle (port, spawn, health, cleanup)
├── build.rs # Tauri build script
├── tauri.conf.json # App config (CSP, resources, sidecar)
├── capabilities/default.json # Permission scope (shell:allow-spawn only)
├── Cargo.toml # Rust dependencies
├── icons/ # Generated app icons (all platforms)
├── binaries/ # Node.js sidecar binary (gitignored)
└── resources/ # Bundled server + assets (gitignored)
static/
├── favicon.svg # App icon
└── templates/
└── timesheet_template.docx # Compiled DOCX template
tests/
├── helpers/
│ ├── docx-assertions.ts # DOCX content assertion utilities
│ ├── fixtures.ts # Shared test fixtures
│ └── mock-kit-json.ts # SvelteKit json() mock for unit tests
├── unit/ # Unit tests (one per module)
│ ├── timesheet.test.ts # Day code computation
│ ├── holidays.test.ts # Holiday parsing & caching
│ ├── holidays-endpoint.test.ts # Holiday API endpoint
│ ├── filename.test.ts # Filename generation
│ ├── parse-payload.test.ts # Input validation
│ ├── doc-conversion.test.ts # DOC conversion
│ ├── template.test.ts # Template loader
│ ├── template-env.test.ts # TEMPLATE_DIR env var
│ ├── capabilities.test.ts # LibreOffice detection
│ ├── capabilities-endpoint.test.ts # Capabilities API endpoint
│ ├── hooks.test.ts # Security headers
│ ├── server-endpoint.test.ts # Generate endpoint handler
│ ├── shutdown.test.ts # Shutdown endpoint
│ ├── page-server-load.test.ts # Page server load function
│ ├── calendar-types.test.ts # Calendar type helpers
│ ├── content-disposition.test.ts # Content-Disposition parsing
│ ├── profile.test.ts # Profile persistence
│ └── docx-errors.test.ts # DOCX error handling
├── integration/
│ ├── docx-fill.test.ts # Template filling
│ └── real-template.test.ts # Full template scenarios
└── e2e/
├── helpers.ts # Shared e2e test utilities
├── app.spec.ts # App UI smoke tests
├── calendar.spec.ts # Calendar interaction
├── api.spec.ts # API endpoint tests
├── api-validation.spec.ts # API validation
├── security.spec.ts # Security headers
├── accessibility.spec.ts # WCAG 2.1 AA compliance
├── docx-download.spec.ts # DOCX download flow
├── doc-export-ui.spec.ts # DOC export UI behavior
├── doc-format.spec.ts # DOC format conversion
├── profile-persistence.spec.ts # Profile localStorage
└── vacation-validation.spec.ts # Vacation date validation
| Layer | Technology |
|---|---|
| Framework | SvelteKit 2 + Svelte 5 |
| Language | TypeScript |
| Build | Vite 8 |
| Linting | ESLint + Prettier |
| Document Processing | JSZip · @xmldom/xmldom · xpath |
| Date Handling | date-fns |
| HTML Parsing | Cheerio (holiday fallback source) |
| Testing | Vitest (unit/integration) · Playwright (e2e) |
| Desktop App | Tauri 2 (Rust shell + OS native webview) |
| Sidecar Bundling | esbuild (server → single-file ESM bundle) |
| CI | GitHub Actions |