Repository navigation
Releases
v0.3.0
What's Changed
A security hardening release: nginx no longer runs as root, password reset and activation codes can't be used twice, the app sends stricter browser security headers, and the published images are checked for known vulnerabilities every week.
⚠️ Deployment: nginx listens on 8080/8443 inside the containers
nginx in the nginx and frontend images now runs as an unprivileged user and listens on container ports 8080 (HTTP) and 8443 (HTTPS) instead of 80 and 443. The ports on the host stay 80 and 443.
- Standard update (download the new
docker-compose.yml, rundocker compose up -d): nothing else to do. - Custom setup: if you changed the nginx ports in your own compose file or
docker-compose.override.yml, or put your own reverse proxy in front of thefrontendcontainer, point them to8080/8443. The new images don't work with the olddocker-compose.yml. - First start after the update: HTTPS may be unavailable for about a minute. The
certbotcontainer now gives nginx read access to the certificates in theletsencryptvolume; until it does, nginx serves HTTP only, then switches to HTTPS by itself.
Security
- Codes can't be used twice. Two simultaneous requests with the same password reset or activation code could both succeed. Now only the first one does; the second gets
400with an invalid code error. Wrong-code attempts are also counted atomically, so parallel requests can't go over the attempt limit. - Password reset checks the new password first.
POST /api/auth/password/resetvalidates the new password before it looks up the email and the code. A weak password now gets its own error (e.g.password_too_short) whatever the email and code are. The status is still400. - Content-Security-Policy and Permissions-Policy headers. Scripts, styles and fonts load only from the site itself, and the site can't be embedded in a frame. The API docs (
/api/docs,/api/swagger,/api/redoc) get a looser policy for their CDN assets. Browser features the app doesn't use (camera, microphone, geolocation, payment and others) are turned off. If you changed the frontend to load scripts, styles or fonts from other domains, update the policy ingateway/nginx/default.conf.template. - Leaner images. OS packages are upgraded on every build. The backend image no longer contains pip, and its dependencies are installed with exact versions from
uv.lock. FastAPI is installed without thestandardextras, which pulled in tools the app doesn't use. The certbot image is pinned to a certbot release instead oflatest.
CI
- Released images are scanned with Trivy every week.
- GitHub Actions are pinned by commit SHA; Dependabot keeps them and the certbot image up to date.
No database migrations.
Full Changelog: v0.2.0...v0.3.0
v0.2.0
What's Changed
An account must now be activated before its settings can be changed or the account deleted. The web app already worked this way; the API now enforces it as well.
⚠️ Breaking: activation required for account changes
These endpoints now return 403 with code access_denied for an account that is not activated:
PATCH /api/users/me/passwordPATCH /api/users/me/emailPATCH /api/users/me/localePATCH /api/users/me/time-formatDELETE /api/users/me
Before this change they worked for any signed-in user. Projects and timers already required activation.
A user who is not activated can still:
- load their profile with
GET /api/users/me - activate the account, request a new code and check the resend cooldown
- list and revoke sessions, and sign out
API clients that let users change settings or delete the account before activation need to handle the new 403.
Internal
- The activation check moved from the adapters layer into the application layer.
- Use cases for creating a project, starting a timer and adding a manual time entry now take the user id as a separate parameter instead of inside the request DTO with the rest of the data.
No database migrations.
Full Changelog: v0.1.12...v0.2.0
v0.1.12
What's Changed
The Projects and Timers pages now use the same compact card layout as the mobile app.
Timers
- Cards instead of a table. Each time entry is a card: project color and name, start and end time, duration, and amount for billable entries. On narrow screens the list no longer scrolls sideways.
- Entries grouped by day. A date header with the weekday, e.g.
Tue, 22.09.2026, comes before each day's entries, as in the mobile app. The date is no longer repeated on every entry. - 10 entries per page instead of 15.
- Selecting entries for bulk delete works as before: use the checkbox on a card, or Select all above the list. Running timers are shown in red and can't be selected or edited.
Projects
- Smaller cards. Project cards take less vertical space.
- 10 projects per page instead of 6.
Both pages
- Pagination follows the list. The page buttons now come right after the last card instead of staying at a fixed height. On a page with fewer entries they move up.
No backend changes and no database migrations.
Full Changelog: v0.1.11...v0.1.12
v0.1.11
What's Changed
Fixed
- Report CSV filename. The period dates in the exported filename now use the user's local time instead of UTC. Previously, in time zones east of UTC (e.g. Moscow) the start date was shifted one day back: a September report was saved as
report_2026-08-31_2026-09-30.csv. West of UTC, the end date was shifted one day forward. The filename now matches the selected period:report_2026-09-01_2026-09-30.csv.
Report contents were not affected: reports always included exactly the entries for the selected period.
Full Changelog: v0.1.10...v0.1.11
v0.1.10
What's Changed
SEO
localeinseo.config.json. Setsog:localein thelanguage_TERRITORYform Open Graph expects, e.g.ru_RUoren_US. Before this changeog:localewas taken fromlang, so"lang": "ru"producedru. Withoutlocale, the old behavior is kept:langwith-replaced by_.imageWidthandimageHeightinseo.config.json. Emitted asog:image:widthandog:image:height.- Validation.
localemust be a string, andimageWidthandimageHeightmust be positive integers. An invalid value stops the frontend container, anddocker compose logs frontendshows why. seo.config.example.jsonand the SEO table indocs/DEPLOY.mddescribe the new fields.
Full Changelog: v0.1.9...v0.1.10
v0.1.9
What's Changed
TimeGrip now ships as ready-made Docker images. A server needs three files from the release and one command. The repository is no longer needed to deploy, and HTTPS certificates are issued without manual steps.
Deployment
- Published images for amd64 and arm64.
timegrip-backend,timegrip-frontend,timegrip-nginxandtimegrip-certbotare published to GitHub Container Registry forlinux/amd64andlinux/arm64, so TimeGrip also runs on a Raspberry Pi with a 64-bit OS. - Deploy with
docker compose up -d. Each release carriesdocker-compose.yml,env.exampleandseo.config.example.json. Put your.envandseo.config.jsonnext to the compose file and start the stack. The compose file runs the images of its release. - Automatic HTTPS. certbot requests the Let's Encrypt certificate on the first start and retries every 15 minutes if the request fails. nginx switches to HTTPS within a minute after the certificate appears and picks up renewed certificates by itself. Running
docker compose run --rm certbot obtainand restarting nginx are no longer needed. After changingDOMAIN_ALIASESor turning offCERTBOT_STAGING,docker compose up -dreissues the certificate. - SEO is applied when the frontend starts.
seo.config.jsonis mounted into the frontend container instead of being built into it, so one image serves any instance. The file now lives next todocker-compose.yml, and its format is unchanged. After editing it, rundocker compose restart frontend. - Postgres is no longer published on the host, so the stack starts on servers that already run their own Postgres. To reach the database from the host, see Database in
docs/DEPLOY.md.
Documentation
- Quick start in the README now deploys from the release files. Building from source moved to Try it locally and is marked as for evaluation and development only.
docs/DEPLOY.mdis rewritten for image-based deployment: server requirements, configuration, updates, certificates, database access and backups.
Upgrade notes
No application changes and no database migrations. To update an instance that runs from a repository clone:
- Move
frontend/seo.config.jsontoseo.config.jsonin the repository root, or setSEO_CONFIG_FILEin.env. Without the file,docker compose upstops with an error. SetSEO_CONFIG_FILE=/dev/nullto run without SEO tags. - The Compose project name is now fixed to
timegrip. If your checkout directory has a different name, addCOMPOSE_PROJECT_NAME=<directory name>to.env, so the existing database and certificate volumes are reused. - If anything on the host connected to Postgres on
127.0.0.1:5432, publish the port again indocker-compose.override.yml(see Database indocs/DEPLOY.md). - Update and restart:
git pull docker compose up -d
v0.1.8
What's Changed
This release reduces how many requests the web client sends to the server and fixes two stale-data issues.
Improvements
- Faster dashboard filters. Changing the project or billable filter on the dashboard now updates the charts right away. The server is queried again only when the date range changes.
- Fewer requests when starting or stopping a timer. The client uses the server's response directly and no longer asks again for the running timer.
- Fewer requests when editing timers. Adding, editing or deleting a timer reloads only the timer lists, because these actions can't affect the running timer.
- No extra profile reload. The profile is not fetched again after you sign up, activate your account, or change your email or time format.
- Revoking a session removes it from the list without reloading the list.
- Faster loading of large datasets. When the dashboard or a report spans many pages of timers or projects, the pages now load in parallel instead of one after another.
Fixes
- Signing out now clears cached data. Before this fix, another account signing in on the same browser tab could briefly see the previous account's projects and timers.
- Timer lists now update after a project is deleted (its timers are deleted with it) or archived and restored.
Full Changelog: v0.1.7...v0.1.8
v0.1.7
What's Changed
Documentation
- New README. It now opens with what TimeGrip is, dashboard screenshots
for the light and dark themes, a feature list and a short "Why TimeGrip"
section, followed by a quick start with Docker and sample data. - Russian README.
README.ru.mdis added, with a language badge at the
top of each README to switch between the two. - Guides moved to
docs/. Deployment, SEO configuration and HTTPS setup
are now indocs/DEPLOY.md. Running the backend and frontend without
Docker, tests and linting are indocs/DEVELOPMENT.md. - Quick start covers local
.envsetup: setSECRET_KEYand
EMAIL_SENDER_BACKEND=console, so sign-up activation codes are written to
theoutbox_emaillog instead of being sent.
Upgrade notes
Documentation only. The backend version is bumped to 0.1.7 with no
functional changes, no migrations and no new configuration. Running
instances don't need to be updated.
Full Changelog: v0.1.6...v0.1.7
v0.1.6
What's Changed
Improvements
- Web: fewer redundant API requests. Switching browser tabs or moving
between pages no longer refetches timers and projects loaded within the
last 30 seconds (projects: 5 minutes). Changes you make in the app still
refresh the data immediately, and the running timer is still updated
every minute, so timers started on another device show up as before.
Maintenance
- Backend: SQLAlchemy is now pinned to
sqlalchemy[asyncio]>=2.0,<2.1.
Full Changelog: v0.1.5...v0.1.6
v0.1.5
What's Changed
Starting a timer offline
POST /timer/start now accepts an optional start_time field. A client that started a timer while offline can send the time it was actually started once it's back online. If the field is left out, the timer starts now, as before.
Full Changelog: v0.1.4...v0.1.5