Skip to content

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.

  1. 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.
  2. An item has two homes. The same item record lives either in the personal stash (plan_id null) or on a plan canvas (plan_id set). 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.
  3. 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.

  1. Only the host can promote, and only an active plan that has not already been promoted.
  2. 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.
  3. The promotion is claimed with a single conditional write, so concurrent promotes collapse to one winner rather than minting two events.
  4. The host becomes an approved participant; every accepted collaborator carries over as an approved participant and chat member.
  5. The items selected in the request are snapshotted onto the event's event-items board; items marked gated are hidden behind joining.
  6. The source plan is archived, and its promoted_to_event_id points 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