Events
This runbook covers the core event management UX in Compass.
Scope
Use this guide to validate:
- creating timed events on the calendar grid
- creating all-day events
- editing events via the form
- deleting standalone events
- dragging events to a new time slot
- resizing events
- duplicating events (Cmd+D)
- undoing an event deletion (Cmd+Z / Ctrl+Z)
- failed-write optimistic rollback
- Week/Day navigation cache reuse
- Google revocation and SSE-driven query refresh
- picking a target calendar when creating/duplicating events, and read-only calendar/busy-event behavior
Do not use this guide to validate:
- recurring event create, edit, and delete (see
recurring-events.md) - Google Calendar sync behavior (see
google-sync.md)
Setup
- Start the app with
bun run dev:web. - Start the backend if you need events to persist across page reloads.
- Log in with any account that does not need Google connected (password-only is fine).
- Navigate to the Week view (
/week) or Day view (/day) depending on the scenario. - Scenarios 14 and 15 need a read-only Google calendar (a
readerorfreeBusyReadercalendar) in addition to a writable one — seegoogle-sync.mdto connect Google and import one. Skip those two scenarios if only writable calendars are available.
Helpful notes:
- All event interactions require a loaded calendar grid. If the grid is blank, reload and wait for events to fetch.
- The right-click context menu on an event opens a small overlay. It closes if you click elsewhere.
- For rollback checks, reject one repository write and verify the optimistic Event returns exactly to its prior state.
- Navigate away from and back to a recently viewed Week/Day range; cached Events should render immediately.
- Validate both anonymous local writes and authenticated remote writes so query and mutation sources remain aligned.
Scenario 1: Create A Timed Event By Clicking The Grid
UX
Clicking an empty hour slot on the calendar grid should open a new event form with the start time pre-filled to the slot that was clicked.
Steps
- Navigate to
/week. - Click an empty slot in the hourly grid (for example, the 2 PM row on Wednesday).
- Enter a title in the form.
- Submit the form.
Expected Results
- The event form opens with the start time set to the clicked slot.
- After submitting, the event block appears on the grid at the correct time.
- The event persists after a page reload.
Scenario 2: Create An All-Day Event
UX
Clicking the all-day row at the top of the week grid should open a new event form pre-configured as an all-day event.
Steps
- Navigate to
/week. - Click in the all-day row at the top of the grid for a specific day.
- Enter a title.
- Submit the form.
Expected Results
- The event form opens with the all-day toggle enabled.
- No start/end time fields are shown in the form.
- After submitting, the event appears in the all-day row for that day.
- The event persists after a page reload.
Scenario 3: Edit An Event Via The Form
UX
Right-clicking an event and selecting Edit (or clicking the event directly) opens the event form pre-filled with the event's current values. The user can change any field and save.
Steps
- Create or locate an existing timed event on the grid.
- Right-click the event and select Edit (or click the event).
- Change the title.
- Change the start time.
- Submit the form.
Expected Results
- The event block on the grid updates immediately to reflect the new title and time.
- Changes persist after a page reload.
Scenario 4: Delete A Standalone Event
UX
Deleting a standalone event (non-recurring) removes it immediately. No scope dialog appears.
Steps
- Right-click a standalone event on the grid.
- Select Delete from the context menu.
Expected Results
- The event disappears from the grid immediately.
- No "Apply Changes To" scope dialog appears.
- The event does not reappear after a page reload.
Scenario 5: Drag An Event To A New Time Slot
UX
Users can click-hold and drag an event block to a new time or date. The event snaps to 30-minute grid intervals. The event updates when dropped.
Steps
- Locate a timed event on the week grid.
- Click and hold on the event body (not the top or bottom resize handle).
- Drag the event to a different day and time slot.
- Release to drop.
Expected Results
- While dragging, the event block highlights and follows the cursor.
- A ghost or preview shows the target position.
- On release, the event moves to the new slot.
- The updated position persists after a page reload.
- If the event has a pending backend operation (cursor shows wait), drag is blocked.
Scenario 6: Resize An Event
UX
Hovering near the top or bottom edge of an event reveals a resize cursor. Dragging from the bottom edge changes the end time; dragging from the top edge changes the start time.
Steps
- Locate a timed event on the grid.
- Hover over the bottom edge of the event until the cursor changes to a row-resize cursor.
- Click and drag downward to extend the event by approximately 30 minutes.
- Release.
- Repeat from the top edge, dragging upward to move the start time earlier.
Expected Results
- The event block grows or shrinks in real time while dragging.
- On release, the event reflects the new start or end time.
- The start time cannot be dragged past the end time.
- Changes persist after a page reload.
Scenario 7: Duplicate An Event (Cmd+D)
UX
With an event form open, pressing Cmd+D (Mac) or Ctrl+D (Windows) creates a copy of the event with the same properties on the same date. The user can then move or edit the duplicate.
Steps
- Open an event form by clicking or right-clicking an existing event and selecting Edit.
- Press Cmd+D (Mac) or Ctrl+D (Windows).
- Close the original form.
Expected Results
- A new event appears on the grid with the same title, time, and description as the original.
- Both the original and the duplicate are present on the grid.
- The duplicate persists after a page reload.
Scenario 8: Undo An Event Deletion (Cmd+Z / Ctrl+Z)
UX
After deleting an event, a brief undo opportunity is available. Pressing Cmd+Z (Mac) or Ctrl+Z (Windows/Linux), or using the undo toast, restores the event.
Steps
- Delete a standalone event via the right-click context menu.
- Immediately press Cmd+Z (Mac) or Ctrl+Z (Windows/Linux).
Expected Results
- The deleted event reappears on the grid.
- The restored event retains all original properties.
Calendar-Aware Events
UX
Every event belongs to exactly one calendar. Creating and duplicating let you pick a target calendar; once an event exists, its calendar is fixed (A6) — there is no move-to-another-calendar control anywhere in the UI. Events on a calendar you can't write to (a Google reader or free/busy-only calendar) can still be opened and inspected, but every mutation surface is blocked.
Scenario 9: Create An Event On A Specific Calendar
UX
The new-event form includes a "Calendar" field. It lists only calendars you can write to, defaults to your primary calendar, and is fully keyboard operable.
Steps
- Navigate to
/week. - Click an empty slot in the hourly grid to open a new event form.
- Open the "Calendar" field.
- Confirm only writable calendars are listed (no reader or free/busy-only calendars appear) and the primary calendar is preselected.
- Choose a non-primary writable calendar, if you have one.
- Enter a title and submit.
- Reload the page.
Expected Results
- The "Calendar" field offers only writable calendars; the primary calendar is preselected and labeled "(primary)".
- The field is operable with arrow keys and Enter, not just the mouse.
- After submitting, the event is associated with the calendar you chose.
- If no writable calendar exists at all, the field shows "No writable calendar available" instead of a picker.
- The choice persists after a page reload.
Scenario 10: Duplicating An Event Defaults To Its Source Calendar
UX
Cmd+D (see Scenario 7) creates a copy on the same calendar as the original, as long as that calendar is still writable.
Steps
- Open the form of an existing event on a writable, non-primary calendar (if you have more than one writable calendar).
- Press Cmd+D (Mac) or Ctrl+D (Windows).
- Open the new duplicate's form and check its "Calendar" field.
Expected Results
- The duplicate is created on the same calendar as the source event, not the primary calendar, as long as the source calendar is writable.
- The duplicate's form still shows a "Calendar" picker (it's a new, independent event), with that calendar preselected.
Scenario 11: Editing Shows The Calendar As Read-Only Text
UX
Once an event exists, its calendar assignment cannot change from the event form — moving an event to a different calendar is out of scope for v1 (A6).
Steps
- Open an existing, previously-saved event's form (any calendar).
- Look for the calendar field.
Expected Results
- The form shows "Calendar:
<calendar name>" as plain text, not a picker or dropdown. - There is no control anywhere in the form to change which calendar the event belongs to.
Scenario 12: Read-Only Calendar Events Are Inspectable But Never Editable
UX
An event on a calendar you can't write to (or a private event showing busy content) can still be opened to view its details, but every mutating action is unavailable.
Steps
- Locate an event on a read-only calendar on the grid.
- Hover the event (or Tab to focus it, without clicking) and press
M. - Close the form, then right-click the same event.
- Try to drag the event to a new time slot.
- Hover the event's edges, looking for a resize cursor.
Expected Results
- Pressing
Mopens the event in a read-only form: fields are disabled, no Save button appears, and a note reads "Read-only — you don't have permission to edit this event." - The right-click context menu shows "View" (not "Edit"), "Duplicate", and no Delete option.
- The event cannot be picked up and dragged to a new time or day; no drag preview appears.
- No resize cursor or resize handle appears at the event's edges.
- A direct left-click on the event may not reliably open the form (a known
intermittent gap);
Mand the context menu's "View" are the reliable ways to inspect a read-only event.
Scenario 13: Busy Private Events Show No Details
UX
A private event on a calendar you only have reader access to is redacted: Compass shows that the time is busy without exposing Google's private title, description, or attendees.
Steps
- In Google Calendar, mark a test event as private on a calendar you've shared with your Compass account as a reader.
- Confirm the event syncs to Compass (see
google-sync.md). - Open the busy event in Compass, using
Mor the context menu's "View".
Expected Results
- The event displays with the title "Busy" on the grid and in its form, regardless of the event's real Google title.
- No description, location, or attendee details are shown anywhere.
- The event is read-only the same way Scenario 12 describes. (Busy content forces read-only even on a calendar you can otherwise write to — the redaction is per-event, not just per-calendar.)
Focused Regression Checks
If time is limited, run these checks before shipping event-related changes:
- Clicking an empty grid slot opens a form with the correct start time pre-filled.
- Submitting a new event places it on the grid and it survives a page reload.
- All-day events appear in the all-day row, not the hourly grid.
- Editing an event updates the grid block immediately.
- Deleting a standalone event shows no scope dialog.
- Dragging an event to a new slot moves it and persists after reload.
- Resizing an event updates the duration and persists after reload.
- Cmd+D duplicates an event with the same properties.
- Cmd+Z / Ctrl+Z after deletion restores the event.
- A new/duplicate event form offers only writable calendars, defaulting to primary; an existing event's form shows its calendar as read-only text.
- Read-only calendar events can be inspected (
M/ context-menu "View") but never dragged, resized, or deleted.