# Tempo · Team calendar — Product Requirements (v2.8)

Tempo is a team calendar. It shows a person’s week in Day, Week, Month and Agenda views, lets them create, move and resize events on a 15-minute grid, reply to invitations, book rooms with capacity and double-booking rules, find a time that is free for everyone, and change recurring series for one date or for all dates. The signed-in user is Linh Pham; the primary time zone is GMT+7 and a second UTC column can be shown.

**Problem.** Teams need a calendar they can trust: events must not hide each other, rooms must never be double-booked, a change to one date of a series must not touch the others, and the guest list and time zones must always be correct.

## Users
- **Organizer** — Create and change events, invite people, book a room that fits and is free, and edit one date or a whole series.
- **Invitee** — See who is going, reply Yes, No or Maybe, and see declined events clearly without losing them.
- **Remote colleague** — Read the same hours in UTC next to local time.

## Goals
- Overlapping events are always readable side by side.
- A room can never hold more people than it has seats or be booked twice for the same time; back-to-back bookings are allowed.
- A change with scope "This event" affects exactly one date; a change with "All events" affects every date.
- Guest summaries always reflect the latest replies.

## Views and navigation (VIEW)

Week grid, overlaps, other views, calendars and keyboard.

- **FR-VIEW-01** (Must) The Week view opens on the current week with every marker of the day: today, the now line, off-hours shading, all-day events and the time-zone label.
  - Heading "19 – 25 Oct 2026"; Monday 19 is marked as today
  - Hours 07:00 to 21:00; gutter header "GMT+7" with tooltip "Ho Chi Minh City"
  - A red now-line on Monday at 10:40 with a "10:40" tag in the gutter
  - Rows before 09:00 and after 18:00 are shaded; events already finished today are faded
  - 17 timed events; all-day row shows "Team offsite" (Thu 22) and "Founders’ Day" (Fri 23, dashed chip)
- **FR-VIEW-02** (Should) Events that overlap in time share the day column in lanes side by side and never cover one another; an event that overlaps nothing uses the full width.
  - Monday: "Design review" (10:00-11:00) and "1:1 Linh / Quang" (10:30-11:00) each take half the column and both titles are readable
  - Wednesday 15:00-16:15: "Interview — Backend" on the left half, "Code freeze sync" and "Interview debrief" stacked on the right
  - "Vendor call" (Wed 09:30) uses the full width
- **FR-VIEW-03** (Could) Icon-only controls and event flags are identified by tooltips and accessible names.
  - "Today" tooltip "Today, Monday, 19 October"; chevrons "Previous week" / "Next week"; view switch shows keys D, W, M, A
  - Globe "Show second time zone (UTC)"; circular arrow "Reset demo data"; avatar "Signed in as Linh Pham"
  - Sidebar "1 invitation awaiting reply"; Holidays has a lock "Read-only calendar"; room Fuji "Fuji · 6 seats · Floor 3"
  - Repeat flag "Repeats: Weekly on Monday" / "Repeats: Every weekday (Mon–Fri)"; "Video call"; "Private"; "Awaiting your reply"
  - Accessible name "Code freeze sync, 15:15 to 15:45, declined"
- **FR-VIEW-04** (Could) The Month view shows at most three chips per day and folds the rest into "+N more", which opens that day.
  - October 2026 grid starts on Monday 28 Sep
  - Wed 21: two chips then "+3 more"; the tooltip lists hidden titles separated by " · "
  - Clicking "+3 more" opens Day view for Wednesday, 21 October with 5 events
- **FR-VIEW-05** (Could) The Agenda view lists the next 14 days, only days that have events, with the reply state of each row.
  - Heading "19 Oct – 1 Nov 2026"; first block "19", "Oct · Mon", "Today" pill
  - Tue 20: "Sprint planning" with an "Awaiting reply" pill and room "Halong"
  - "Code freeze sync" shows "Declined" with the title struck through; "Team offsite" shows "All day" and "Going"
  - Sun 25 has no block; clicking the date "20" opens Day view for Tuesday, 20 October
- **FR-VIEW-06** (Could) Keyboard keys switch views and move the range, and the mini month mirrors the visible range.
  - d, w, m, a switch view; t returns to today; left and right arrows move by one day or week
  - Day view of Monday 19 shows 4 timed events
  - Mini month day 19 is named "Monday, 19 October, has events"
  - Clicking Wed 4 Nov in the mini month jumps to the week of 2 Nov
- **FR-VIEW-07** (Could) Calendars can be hidden with the sidebar checkboxes, and Reset restores the seeded week.
  - Unchecking Holidays hides "Founders’ Day" only; checking it brings it back
  - Reset shows toast "Calendar restored": deleted events return, hidden calendars are checked again, 17 timed events

## Event popover, permissions and replies (POP)

Details, organizer-only editing and RSVP.

- **FR-POP-01** (Must) Clicking an event opens a popover with date and duration, calendar, room, guests and the reply summary; the organizer sees Edit, Delete and Close and no reply buttons.
  - "Design review": "Monday, 19 October · 10:00 – 11:00 (1 h)", room "Fuji · 6 seats · Floor 3"
  - Guests header "3 guests · 2 yes, 1 maybe"; rows Linh Pham (you) with an "Organizer" tag, Minh Ho, Thao Vu
  - Hovering a guest shows "Thao Vu · Maybe"; Escape closes the popover
- **FR-POP-02** (Should) Only the organizer can change an event; for everyone else the popover shows a lock, the event cannot be dragged, and the server refuses the change.
  - "Roadmap Q4" shows a lock with "Only Thao Vu can edit" and a "Going?" row with Yes pressed; dragging it does not move it
  - Dragging "Interview — Backend" leaves it at 15:00 - 16:00; lock "Only Khoa Ngo can edit"
  - Changing "Sprint planning" through the API returns 403 "Only Minh Ho can change Sprint planning"
  - Read-only calendar events (Founders’ Day, "Holidays · read-only calendar") have no Edit or Delete
- **FR-POP-03** (Must) Replying Yes, No or Maybe updates the guest summary immediately, the event styling and the sidebar count of unanswered invitations.
  - "Sprint planning" starts at "5 guests · 2 yes, 1 no, 1 maybe, 1 awaiting"
  - Yes: toast "You’re going", header "5 guests · 3 yes, 1 no, 1 maybe", solid outline, no "?" flag, and the sidebar line "1 invitation awaiting reply" disappears
  - Linh’s own guest row shows the new reply
  - An organizer replying to their own event is refused: 422 "You organize Design review"
- **FR-POP-04** (Should) A declined event stays visible, striped with a struck-through title, and changing the reply back removes the styling.
  - No: toast "Invitation declined", summary "2 yes, 2 no, 1 maybe"
  - Maybe: toast "Replied maybe", summary "2 yes, 1 no, 2 maybe"; the event is no longer striped

## Creating events (CRE)

Drag-create, quick-create and validation.

- **FR-CRE-01** (Must) Dragging on an empty part of the grid selects a time range that quick-create turns into an event on the Work, Team or Personal calendar.
  - Dragging Wed 11:00 to 12:00 shows "11:00 – 12:00" and opens a card "Wed 21 Oct · 11:00 – 12:00 (1 h)"
  - The calendar list offers Work, Team and Personal, not Holidays
  - Saving without a title shows "Title is required"
  - With the title "Pairing" the toast is "Created Pairing" and the event is in the Work colour
- **FR-CRE-02** (Could) A plain click proposes a one-hour slot starting on the half-hour at or before the click.
  - Click at about 16:20 gives 16:00 - 17:00; at about 16:40 gives 16:30 - 17:30
  - Escape creates nothing; Enter in the title saves with "Created Notes"
- **FR-CRE-03** (Should) The editor and the create service validate each field with its own message and nothing is created on error.
  - Empty title: "Title is required" (400)
  - End not after start: "End must be after start"
  - Times not on a quarter hour: 422 "Times snap to 15 minutes"
  - Creating on Holidays: 403 "Holidays is read-only"
  - "All day" can only be set on new events; for existing events the checkbox is disabled with the tooltip "All-day can only be set on new events"

## Guests, rooms and find-a-time (ROOM)

Free/busy, capacity and double-booking.

- **FR-ROOM-01** (Must) The editor shows free/busy rows for every guest and the chosen room and suggests start times that are free for all, never in the past part of today.
  - Panel "Find a time · 21 Oct, 09:00–18:00" with a row per guest and the room
  - Busy block tooltips: "Thao Vu · 09:30–10:30 · Vendor call" and "Booked 15:00–16:00 · Interview — Backend"
  - For Linh, Khoa and Thao in Sapa for 60 min the suggestions are 10:30, 11:00, 11:30, 12:00; clicking 12:00 sets 12:00 - 13:00
  - For Linh and Minh on Monday 19 Oct for 30 min: 11:00, 11:30, 13:00, 13:30 (nothing before 11:00 today)
  - Duration other than 15, 30, 45, 60, 90, 120 gives 400 "Duration must be 15, 30, 45, 60, 90 or 120 minutes"
- **FR-ROOM-02** (Must) A room cannot hold more invited people, organizer included, than it has seats.
  - Sapa (4 seats) with 5 people: "Room Sapa holds 4 people (5 invited)" under Room, nothing is created
  - Removing one guest lets Save succeed and the event shows "Sapa · 4 seats · Floor 3"
- **FR-ROOM-03** (Must) A room cannot be booked for a time that overlaps another booking, but a booking that starts exactly when another ends is allowed.
  - Fuji is booked 10:00 - 11:00 by Design review; 10:30 - 11:00 is refused with "Room Fuji is booked 10:00–11:00 by Design review" (409 by the service)
  - 11:00 - 11:30 in Fuji is created ("Created Clash" / "Sync") and sits directly under Design review

## Recurring events (REC)

Scope "This event" versus "All events".

- **FR-REC-01** (Must) Changing a recurring event asks "Change recurring event" with "This event" (default) or "All events"; "This event" changes only that date and leaves every other date unchanged.
  - Moving "1:1 Linh / Quang" to 11:30 with "This event": toast "1:1 Linh / Quang moved to Mon 19 Oct, 11:30–12:00"; the moved item shows "Weekly on Monday · changed for this date"
  - On Monday 26 Oct the event is still 10:30 - 11:00 with "Repeats: Weekly on Monday"
- **FR-REC-02** (Should) Choosing "All events" applies the change to every date of the series.
  - Renaming to "1:1 Linh / Quang (v2)" with All events: toast "Changes saved"; Monday 19 and 26 both show the new title
  - Moving to 11:30 with All events puts Monday 26 at 11:30 - 12:00
- **FR-REC-03** (Should) The repeat rule can only be changed for all events.
  - Repeat "Does not repeat" with "This event": "Repeat can only change for all events" under Repeat (422 by the service)
  - With "All events" the toast is "Changes saved" and the series becomes a single event (no Demo day on Fri 30)
- **FR-REC-04** (Should) Deleting a recurring event asks for a scope; deleting a one-off event asks for inline confirmation.
  - "Delete recurring event" with This event / All events and a "Delete" button; This event: toast "Deleted Demo day on 23 Oct" and Fri 30 still has it
  - One-off "Run club": inline "Delete Run club?" with Cancel / Delete; toast "Event deleted"
  - The service without a scope refuses with 400 "Choose \"This event\" or \"All events\""; with scope=this it returns {"ok":true,"deleted":"occurrence","date":"2026-10-26"}
  - Unknown event: 404 "Event e999 not found"

## Moving and resizing (MOV)

Drag move, undo and resize on the 15-minute grid.

- **FR-MOV-01** (Could) A one-off event can be dragged to a new time in 15-minute steps and the move can be undone.
  - Dragging "Vendor call" to Thu 10:00 shows a time tag and the toast "Vendor call moved to Thu 22 Oct, 10:00–11:00" with "Undo"
  - Undo: toast "Move undone" and the event is back on Wed 09:30 - 10:30
- **FR-MOV-02** (Could) Dragging the bottom edge changes the end time in 15-minute steps.
  - "Lunch with Ha" 12:00 - 13:00 dragged to 13:30 shows "12:00 – 13:30 · 1 h 30 min"; toast "Lunch with Ha moved to Mon 19 Oct, 12:00–13:30"
  - Its popover says "Private — others see only “Busy”"

## Second time zone (TZ)

The optional UTC column.

- **FR-TZ-01** (Should) The globe button shows a UTC label column that is seven hours behind the primary GMT+7 hours and is remembered after reload.
  - Tooltip "Coordinated Universal Time (-7 h)"
  - 08:00 shows 01:00 and 14:00 shows 07:00
  - The column is still shown after a reload and disappears when the button is pressed again

## Glossary and rules

- **Lane** — One of the side-by-side columns inside a day that overlapping events share.
- **Scope** — The choice between "This event" (one date) and "All events" (the whole series) when changing a recurring event.
- **Find a time** — The free/busy strip in the editor that suggests start times free for all guests and the room.

## Out of scope

- Time zone changes of the primary zone
- Email invitations
- Multiple users signed in at once
