Repository navigation
Releases: Sulaiman-Dauda/Tidetime
Release list
Tidetime 0.2.0
Upgrading from 0.1.x: admins see an Update available card at the foot of the sidebar. If the optional updater is enabled, Update now pulls the new image and restarts the stack. Otherwise back up the database and run this in the install folder:
git pull
docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -dIf you build the image from source rather than running the published one, pull cannot fetch it: run git pull and then docker compose -f docker-compose.prod.yml up -d --build. Two database migrations run on start: one adds a nullable column and one widens a column, and neither changes existing data. Nothing changes in .env or the Compose files.
Security
- Two-factor codes are now single-use.
verifyTotponly reported whether a code was valid, so a code stayed usable for the whole ±1-step acceptance window (roughly 90 seconds) and could be replayed to sign in again or to switch 2FA back off. RFC 6238 §5.2 requires a verifier to reject the second use of an OTP. A newverifyTotpStepreturns the 30-second step a code matched, the step is recorded on the user astotp_last_step, and a code is only accepted when its step is strictly greater than the one already consumed. Enrolment seeds the value so the setup code cannot be turned straight around against a login. Requires the0005migration; the column is nullable, so existing installs keep working and simply have no consumed step recorded until the next sign-in.
Added
- Phone questions on the booking form have a country picker beside the number. Numbers are stored in international format (
+447700900123) however they were typed, and shown grouped (+44 7700 900123) on the confirmation page and in the dashboard. A new Default phone country setting (Settings, Brand tab, under Booking form) sets where the picker starts. Numbers saved before upgrading are not converted. - Booking webhooks (
booking_createdandbooking_rescheduled) carry four more fields:serviceSlug,attendee.phone,answers(each custom question as alabelandvalue) anddescription, whether the customer reschedules or staff move the booking on the dashboard calendar. Existing fields are unchanged, so current Zapier zaps and subscribers keep working. - Booking questions can be set to Half width in the service editor, so short questions such as name and email share a row on wider screens. Existing questions stay full width.
- The Terms and Privacy texts support
## headings,- bulletsand**bold**, and turn email addresses and web addresses startinghttp://orhttps://into links. Text without those markers looks the same as before.
Fixed
-
Two-factor authentication could not be switched on. The secret is stored encrypted, which is 83 characters, in a column that held 64, so every enrolment failed. Migration
0006widens the column. -
Unpublishing a service more than a day old deleted it. Unpublish turned the service back into a draft, and the background job deletes drafts a day after they were created, taking the provider assignments with it and leaving its bookings without a service. Unpublish is gone: to take a service offline without deleting it, turn off Visible on company booking page in the service editor, and its public page returns not found until you turn it back on. Publishing is now one way, and the cleanup never deletes a service that has bookings. A service already deleted this way cannot be recovered except from a backup.
-
On Availability, switching to another person or another schedule kept showing the previous schedule's hours, and saving wrote them over the one now selected. The editor now loads the hours of whichever schedule you switch to.
-
Webhooks were never delivered on Node 20 or later. Every delivery failed with "Invalid IP address: undefined" and was retried until it was marked failed. The SSRF guard pins each request to the address it has already checked by passing its own
lookup, which answered in the single-address form. Node 20+ connects withautoSelectFamily, asks for{ all: true }and needs a list, so the connection failed before it was made.pinnedLookupnow answers both shapes, and a new test drives it through the realnode:httpstack, which the existing mocked test could not. Deliveries already marked failed are not retried. -
The dashboard was unusable on screens narrower than 768px. The mobile header was a sibling of the sidebar inside a row-direction flex container, so it took a column of its own and pushed the content area off-screen, so every dashboard page rendered as a blank screen below the header. The header now sits inside the content column.
-
Booking times like
10:00 AMwrapped onto a second line in the overview's agenda rows, giving rows in the same list different heights. -
The date label in the booking page's slot column wrapped after the weekday when the rail was narrow.
-
The calendar's month toolbar overflowed the viewport on phones.
-
The brand colour setting only painted a 4px bar across the top of public pages and a dot beside the company name. It now themes the public booking, confirmation and legal pages: buttons, selected dates and slots, links and focus rings, with button text switched between white and near-black for contrast. The dashboard keeps Tidetime's indigo, and the setting's hint now says so. If you set a colour on an earlier version, your public pages take it on after upgrading. On light pages a colour below 3:1 against the page, such as a pale yellow, is deepened just far enough to reach 3:1, and any other colour is used as entered. Dark mode lightens it to at least 62%, and further if it still falls short of 3:1.
-
The booking confirmation page and the dashboard booking detail showed the attendee's phone number twice when the service's form had its own phone question.
-
The confirmation page title read "Your booking · Tidetime · Tidetime".
-
A cancelled booking's confirmation page still linked to the meeting.
-
"New service" on the Overview was shown to schedulers and members, who cannot create services, and clicking it ended on an error. "Manage services" on an empty Bookings tab sent schedulers to a page that turned them away. Both now appear only for roles that can use them.
Changed
- Choosing a time on the public booking page goes straight to the details form. The separate Next button is gone.
- The public booking page shows the company once, in the service panel: the logo if one is set, otherwise the name. The page header no longer repeats it. The chosen date and time appear once, at the top of the details form.
- Filters on Bookings, Customers, Calendar and Availability now use the app's own select control instead of the unstyled browser dropdown.
- Service rows have a labelled
PreviewandEditaction plus an overflow menu (move, duplicate, hide, delete) in place of five unlabelled icon buttons with an unguarded delete first in the row. - Settings opens on the Brand tab; Domain, a one-time DNS step, moved last. The domain setup steps are a numbered list rather than one run-on line.
- Dark theme: cards and list rows sat too close to the page background to read as separate surfaces, and secondary text fell below AA contrast. Card, border and muted-foreground tokens were lifted.
- Native date and time fields keep their platform pickers but the indicator glyph now matches the surrounding controls.
- Month-grid event chips use compact times (
9am,9:45am) so the meeting title has room. - The calendar's timezone footnote names it as the viewer's own zone and links to the profile setting.
- The interface was rebuilt across every screen. Pages, navigation and behaviour are unchanged. Colours, surfaces, shadows and radii now come from one set of design tokens, and forms, tables, tabs and segmented controls share primitives (
Field,FormSection,Table,Segmented) instead of per-page styling. The dashboard content sits in an inset panel beside a grouped sidebar, with the account menu at its foot rather than in the top bar. Long forms put each section's help text beside its fields. Decorative motion (the page-enter animation, counting numbers, the confirmation burst) was removed.CONTRIBUTING.mddescribes the primitives. - The typeface is now Geist, under the SIL Open Font License (included as
public/fonts/Geist-OFL.txt). It replaces Satoshi and Clash Display, whose ITF Free Font License v2.0 does not allow redistributing the font files in a repository or an app served from public servers. One variable font file replaces seven static ones.
Full changelog: v0.1.2...v0.2.0
v0.1.2
Maintenance release; no functional changes. Image: ghcr.io/sulaiman-dauda/tidetime:0.1.2
v0.1.1
Changed
- The in-app update check now compares the app's semantic version against the latest published GitHub release, and the dashboard shows the version number (e.g.
v0.1.1) instead of a git commit. Updates are surfaced only for tagged releases, not every commit tomain.
Image: ghcr.io/sulaiman-dauda/tidetime:0.1.1
v0.1.0 — first public release
The first public release of Tidetime — self-hosted appointment scheduling for a single company with multiple services and multiple providers. It runs on your own server and PostgreSQL, with no hosted plan and no per-seat pricing.
Highlights
- Booking pages & scheduling — branded public pages, custom questions, durations, buffers, minimum notice; least-busy provider assignment with double-booking protection; group events, daily caps, and optional confirmation.
- Availability — multiple named schedules per provider, weekly rules with date overrides, and admin-managed availability.
- Calendars & meetings — Google Calendar conflict checks with event write-back and Google Meet links, Microsoft 365 read-only conflict checks, and built-in Jitsi links.
- Email — SMTP or Microsoft 365, covering confirmations, reschedules, cancellations, RSVP links, and calendar invites.
- Integrations — signed, retrying webhooks on booking events (Zapier-compatible); customer directory with CSV export.
- Security — optional TOTP two-factor auth, password reset, email verification and change, ownership transfer, rate limiting, and ALTCHA spam protection.
- Operations — custom domains with automatic HTTPS via a bundled Caddy proxy, a one-command installer that sets up Docker for you, and in-app update notifications with an optional one-click updater.
Install
```bash
curl -fsSL https://raw.githubusercontent.com/Sulaiman-Dauda/Tidetime/main/install.sh | bash
```
Prebuilt image: `ghcr.io/sulaiman-dauda/tidetime:0.1.0`. See the documentation and deployment guide.
Released under the MIT License.