book-free.openapps.xyz

Source

This app’s code and revision history are public. Download a copy or explore the files below.

Public
The app’s code and revision history are public.
Owned app · rev f8f1b3a80195
Run it yourself
Take the whole tree and run it on your own machine. The download needs no account. Cloning needs a sign-in.
Download .zip
bounded clone 7385a6c1c47153b1728160ff
A copy you download is not bounded. The guarantees this app runs under - declared egress, proven invariants, writes that fail closed - live in the runtime serving this page, not in the files. What you get is auditable source you can read and run, with no runtime guarantees attached.
← All files
AGENTS.md
# Book

Book is a free booking page for one-on-one meetings, and a free alternative to Calendly, Cal.com and SavvyCal. It is an OpenApp in the OpenApps free apps store (https://free-apps.openapps.xyz/), owned by the OpenApps team.

## What it promises

- Free, with no limit on pages or bookings, no ads and no paid tier. Never add a cap, an upsell or tracking.
- No sign-up. Owners and the people booking are guest accounts created in the browser.
- Booking pages (`/<handle>`) are public. The meeting link is not: only owners and people who booked can read it.
- Guests move (reschedule) or cancel their own booking from its manage link. A move books the new time and frees the old one in one batch, so the guest never ends up with no time.
- Only owners see bookings and change a page. The creator is an owner; anyone who opens the admin link (`/e/<handle>#k=<key>`) becomes one. Each booking has a cancel key; its manage link (`/b/<handle>/<bookingId>#c=<key>`) opens it on any device. Keys travel in the URL fragment, so servers never see them.
- Two bookings can never share or overlap a time. This is enforced in the policy, not just the UI (see below).
- Times show in each viewer's own time zone, with the zone named.
- Honest limits, said on the landing page: Book does not read calendars (owners block time with weekly hours and days off) and does not send emails or reminders. Owners get bookings into their calendar app by subscribing to the page's calendar feed.
- AI assistants can do everything a person can through the public API (`functions/api.ts`), documented in `public/llms.txt`. Keep that file true whenever the API changes.

## How it is built

- `index.html` is the static landing page that search engines and AI crawlers read. Keep its copy, comparison, FAQ and JSON-LD in sync with what the app does.
- `src/` is the React app. `/` mounts the page maker (`PageForm.tsx`) and "Your pages" (`Home.tsx`, from localStorage) into the landing. `/<handle>` is the booking page (`BookingPage.tsx`), `/e/<handle>` the owner's dashboard (`Dashboard.tsx`), `/b/<handle>/<bookingId>` one booking (`Manage.tsx`).
- The booking page reads `?date=YYYY-MM-DD`, `?tz=Area/City`, `?name=` and `?email=` (links people and assistants send), and `?move=<bookingId>` (move mode, opened by Change time on the manage page).
- The look is the shared kit in `public/kit.css` (dialogs, dropdowns, skeletons, empty and error states, motion). `src/styles.css` holds only Book's layout. App screens fill the screen so the footer never jumps while they load.
- `functions/lib/book.ts` holds the logic shared by the app and the API: weekly hours, days off, open times, time zones, meeting links, the .ics file, the calendar feed and its key, and the Google Calendar link.
- `functions/lib/config.ts` holds the app id, the site URL used in links, and the API base. SITE_URL is the app address, `https://book-free.openapps.xyz`.
- `policy.json` holds the data rules (Pattern B in the store's app guide):
  - `pages/<handle>` is public. Its optional `daysOff` is a JSON list of `[from, to]` dates in the page's time zone; it is left out when empty and cleared with null. `pagelinks/<handle>` holds the meeting link, readable by owners (`pages/<handle>/owners/<userId>`) and by accounts that booked (`pages/<handle>/bookers/<userId>`). The admin key lives in `pagekeys/<handle>`, readable only by the public API function.
  - `pages/<handle>/bookings/<bookingId>` is readable by the page's owners, by the account that made it, and by anyone who presented its cancel key (`bookingclaims`, checked against `bookingkeys`). Only a cancel can change a booking. A move is a cancel with `movedTo` (the new booking's id) in the batch that creates the new booking, which keeps the old cancel key, so the old manage link forwards to the new booking. Bookings live under the page because a list read can evaluate an owner check built from path variables (`get(/pages/$handle/owners/@user.id)`), but not one built from a document field (`get(/pages/@data.handle/...)`), which silently returns nothing.
  - No double booking: a booking holds its time, plus the break after it, as 15-minute cells `pages/<handle>/slots/<utcSeconds>`. A cell can be created only once (updates are refused), each cell but the last must name the next one in the same batch, and the booking must hold the cell at its start. So every held minute is a unique document, and an overlapping booking is refused whatever its length. Cancelling deletes the cells in the same batch. Meeting lengths and breaks are multiples of 15 minutes for this reason.
  - The policy also checks the length, break, minimum notice and booking window. The weekly hours and days off are checked by the app and the API (`openSlots`), not the policy.
  - `policy-tests/bookings.json` checks these rules: run `bounded tests run --app-id <an app you own>` after any policy change.
- Every API caller shares one public principal, so the API checks the owner or cancel key before acting, then writes a claim so the policy checks it too.
- The calendar feed (`GET /pages/<handle>/calendar.ics?f=<feedKey>`) is read-only: the feed key is the first 32 hex characters of SHA-256 of `book-feed:<ownerKey>`, computed by `feedKey` in both the app and the API, so it cannot be turned back into the owner key.
- `public/llms.txt`, `public/openapi.json` and `public/index.md` describe the API and the app for assistants. Keep all three true whenever the API or the app changes.

## Working on it

`npm install && npm run build` typechecks and builds `dist/`. Ship changes through previews and releases as your runtime instructions describe; the app is an OpenApp, so nobody deploys it directly.

Check a change by making a page, booking it from a second browser in another time zone (the time should disappear on the first browser right away), trying to book the same time twice, moving the booking with Change time, cancelling from the manage link, opening the admin link on a third browser, adding days off, subscribing to the calendar feed, downloading the .ics file, and calling the API with curl (including a wrong owner key, a wrong cancel key and a wrong feed key). Check phones at 390px wide.

## Voice

Plain words, short sentences, no jargon, no em dashes. Say what happens, not how clever it is.

Change history

Add favicon.ico and link tag
f8f1b3a801 · 2 hours ago
Quality pass: guest reschedule, days off, calendar feed, API docs
c827e5d073 · 5 hours ago
Bounded source sync
68e67b4092 · 1 day ago
Bounded source sync
ab2e006f9b · 1 day ago
Bounded source sync
bb10824b54 · 1 day ago
Bounded source sync
d22f765fb7 · 1 day ago
Bounded source sync
30b4b06513 · 1 day ago
Bounded source sync
c3d297477a · 1 day ago
Bounded source sync
7b18317417 · 1 day ago