Plans — Domain Spec¶
A plan is a date-free, collaborative intention to go somewhere; its canvas is a board of captured items grouped by place, and it is promoted once into an event. This page is the rules for how items are captured and triaged, how a plan is shared, and what promotion does.
Mental model¶
Three shapes carry the domain.
- A plan is a shared canvas, not a scheduled event. It has a title, a visibility, collaborators, and a set of items. It has no fixed time until it is promoted. Promotion is one-shot: a plan becomes exactly one event, then it archives.
- An item has two homes. The same item record lives either in the
personal stash (
plan_idnull) or on a plan canvas (plan_idset). Moving an item onto a plan is just setting its plan. The item's place (location_id) is the grouping key that collects items into place-cards. - An item is captured, then parsed, then triaged. A share or paste creates the item; a parse pipeline resolves its place and links; triage confirms what it is and where it belongs.
States¶
| Object | Field | Values |
|---|---|---|
| Plan | status | active, archived |
| Plan | visibility | only_me, close_friends, friends, public |
| Collaborator | role | owner, member |
| Collaborator | status | invited, accepted, declined, left |
| Collaborator / host | rsvp | going, maybe |
| Item | role | place, activity, note |
| Item | status | pending_parse, parsed, triaged, dismissed |
A public plan surfaces on the host's profile. close_friends and
friends scope visibility to those graphs; only_me is private to
the host.
Surface area¶
Two route groups, both authenticated (see
backend/internal/services/plan/routes.go).
| Group | Purpose |
|---|---|
/plans |
Create and edit plans, group items into place-cards, vote on the leading place, run the day/time availability poll, manage members and share links, and promote to an event |
/items |
Capture, list, triage, edit, and organize board items across the stash and plan canvases |
Load-bearing endpoints:
| Endpoint | Does |
|---|---|
POST /items |
Capture a new item (share / paste) |
POST /items/save-location |
Save a found place into the stash or a plan |
POST /items/:id/triage |
Resolve the "is this X?" prompt and file the item |
POST /items/:id/create-plan |
Spin a new plan off a single item |
PUT /plans/:id/place-cards/:plid/vote |
Vote for the leading place |
POST /plans/:id/poll/options |
Propose a day / time option |
POST /plans/:id/share-link + POST /plans/join/:token |
Mint and redeem a join link |
POST /plans/:id/promote |
Promote the plan to an event (one-shot) |
GET /users/:userId/plans |
A user's visible plans |
Promotion¶
Promotion turns a plan into an event and closes the plan.
- Only the host can promote, and only an
activeplan that has not already been promoted. - The start time and duration come from the promote request, falling back to the leading availability-poll result; the venue comes from the request, falling back to the leading place-card, then the plan's anchor location.
- The promotion is claimed with a single conditional write, so concurrent promotes collapse to one winner rather than minting two events.
- The host becomes an approved participant; every accepted collaborator carries over as an approved participant and chat member.
- The items selected in the request are snapshotted onto the event's event-items board; items marked gated are hidden behind joining.
- The source plan is archived, and its
promoted_to_event_idpoints at the new event.
Invariants¶
| Invariant | Why |
|---|---|
| A plan promotes to at most one event | promoted_to_event_id is set once; the conditional claim rejects a second promote |
| Promotion archives the source plan | A promoted plan is done; its live form is now the event |
| The event-items snapshot is a copy | The event board must survive later edits or deletion of the source items (see Events) |
An item's location_id is the grouping key |
Items with the same place collect into one place-card; a null place is the uncategorized bucket |
| The stash is personal | Stash items (plan_id null) belong to their owner until filed onto a plan |
| Only accepted collaborators carry into the event | Invited / declined / left members do not become participants |
Cross-domain interactions¶
| Other domain | Interaction |
|---|---|
| Events | Promotion creates the event, carries accepted collaborators over, and snapshots selected items onto the event-items board |
| Locations | Items and place-cards reference a shared Location; save-location files a found place onto the board |
| Friends | friends and close_friends plan visibility resolve against the friend graph |
| Chat | The promoted event's group chat receives the carried-over collaborators |
| Media | Item photos and captured links flow through the capture and asset pipeline |
See also¶
- Related spec: Events (the promoted form of a plan)
- Backend: Backend → Plan service
- UX:
../ux/plans.md(coming soon)