# Writers camps
Source: https://docs.aden.space/docs/help/work-together/writers-camps

Org-run songwriting camps that group studio sessions, manage a roster of writers, collect a signature on the camp's own terms, and let people join either by personal invite (auto-join) or a public open-form signup link (with an optional price to join that can be invoiced per participant from the camp's People page, a signup wave with limited spots + waitlist, and a full public camp page. Schedule, hosts, FAQ, travel, partners). Logged-in applicants can review, edit, or opt out of their submission. Camps surface on the in-app calendar, ICS feeds, the MCP server, and mobile, and unfurl with a rich OG image when shared.
{/* Generated by `bun docs:publish` from docs/features/writers-camps.md: edit the spec, not this file. */}

## What it is [#what-it-is]

A writers camp is a songwriting event an organisation runs: a roster of writers
and producers, a set of studio sessions over a few days, and the tracks that come
out of it. The organiser builds the lineup, schedules people into sessions, and
afterwards reviews the songs and decides what to do with each one.

People get into a camp in one of two ways:

* **Personal invite**: the organiser adds someone by name/email and Aden sends
  them a private invite link. Opening it (and signing in or creating an account)
  joins them to the camp automatically, no extra "accept" step.
* **Open signup link**: the organiser turns on a public signup page and shares
  one link. Anyone with it can view the camp and apply without signing in (no
  account required to apply); the organiser then accepts or rejects each
  application. If you're already signed in to Aden the form shows your account
  and lets you pick which of your teams you're attending with.

**Partner organisations.** A camp can be co-run by more than one organisation.
The hosting organisation invites another organisation from the camp's
Organisations page; once that organisation's admins accept, the camp shows up in
their own camps list and sidebar with a live view of the sessions, the studios
they're rented in, the people, and the tracks produced. Managing the camp
(settings, roster changes, invites) stays with the host, and either side can end
the link at any time.

## Using it [#using-it]

**Run a camp (organiser).**

1. Create a camp under your organisation and set its dates, location, and
   branding (logo + banner). The new-camp form has a **Plan the sessions**
   helper: enter how many writers join and pick a group size to get a suggested
   group count, then set **Sessions at once** (parallel rooms), **Minutes per
   session**, and a **Daily hours** window and hit **Generate**: it lays out
   one pre-titled slot per group across the camp's start date, pairing parallel
   groups into the same time block and stepping blocks through the daily window,
   rolling onto the next day when the window fills. Generated slots (and any
   added by hand) can each be removed, including the last one. The form owns a
   single `useFieldArray` for the slots and passes it down to the shared
   `CampSessionSlotsField`, so generation and manual edits stay in sync. The
   form also collects the full **Where** block (location label, venue name,
   street address, city, country. Everything `createCamp` accepts), and each
   session slot has a collapsible **Location** section (label/venue/city/
   country, auto-expanded when values exist) via the shared
   `CampLocationFields` composable; slot payloads go through one
   `sessionSlotToPayload` mapper. The whole draft: camp fields plus planned
   sessions: autosaves to localStorage (`useLocalFormDraft`, keyed per
   org+kind), restores on revisit, and clears after a successful create; the
   Sessions dialog's one-off tab drafts the same way (keyed per camp). The
   new-camp page is a full-width three-rail workspace: a sticky `ScrollStepper`
   rail on the left (Details / Location / Plan / Sessions, driven by an
   IntersectionObserver scroll-spy like the release-plan workspace), the form in
   the middle, and a live read-only calendar `CampSchedulePreview` on the right
   that groups valid slots into per-day columns over a shared time axis and packs
   concurrent sessions side by side, updating as slots are generated/edited
   (below the `lg` breakpoint the rails collapse and the preview renders inline).
   Time controls honour the app-wide 12h/24h time-format preference
   (`useEffectiveTimeFormat`): the **Daily hours** pickers use the shared
   `InputTime`, which now formats via that preference, as does the preview; the
   per-slot start/end pickers still use the native time input (OS-locale display).
2. On the **People** page, add writers from your organisation/teams or invite new
   people by name and email. While the camp is a draft, invites are queued. Hit
   **Send invites** (which also publishes a draft camp) to email everyone at
   once. The page is split into
   server-side `?tab=lineup|roster|add|applications|responses` tabs (each tab's
   data streams in under its own Suspense boundary so the send-invite actions
   never wait on the slow lineup query). The tab strip renders in the shared
   underline-tab style with an icon per tab and no full-width divider. The
   **Roster** tab lists everyone on the camp and lets organisers remove a
   participant from the camp entirely (`removeCampParticipant`: also drops
   them from any sessions). Across Roster, Applications and Responses, each
   person renders with their avatar (resolved from `avatar_key` in one batched
   call) via the shared `CampPersonIdentity`: on-platform people link to their
   public passport page (`/user/[userId]`), the team they signed up with shows
   as a `CampTeamBadge` (team avatar, links to `/profile/[slug]`), and a
   quick-message button (`CampPersonMessageButton`) opens the org chat with a
   new conversation preselected for them
   (`/org/[orgId]/chat?createWithMember=<userId>`, handled by the chat
   workspace like `?createWithTrack=`) or falls back to `mailto:` for
   email-only participants. The **Add people** tab embeds the same
   `CampAddPeoplePanel` (shared collaborator invite panel) as the Sessions
   page's Add people dialog. One component
   (`_components/camp-add-people.tsx`) for both surfaces.
   The **new-camp form's** people pool runs the same panel with the artist-team
   tab enabled: the camp has no id yet, so a team joins as the rows picked from
   it and each row carries its `teamId` until `createCamp` returns and the
   drafts flush through `inviteCampPeople`.
3. Optionally open the camp's **public signup link** in **Settings → Public
   signup**, add an intro, and share the link anywhere.
4. Build the lineup by dragging people into sessions, or hit **Do it with AI**
   on the People page: pick an AI model (all free-on-plan, served via NVIDIA
   NIM like the in-app chat), optionally add instructions ("keep Anna
   and Bob together"), and it proposes who goes into which session with roles
   and a reason per pairing. Nothing is saved until you review the proposal,
   remove anything you disagree with, and apply it. Then send everyone their
   personal schedule. Drops are optimistic: the person shows up in the session
   column immediately (pending spinner) and rolls back with an error toast if
   the schedule action fails; removing someone from a session hides them
   immediately the same way. Each session card on the lineup links out to the
   full session editor on the team sessions surface
   (`/app/teams/[teamId]/sessions/[sessionId]`) for rich editing, and the
   quick-edit dialog can link a real studio room from the organisation's
   studios (`studio_id`) instead of only free-text venue fields.
5. On the **Sessions** page, one **New sessions** dialog covers both planning
   modes: **Plan a week** creates sessions in bulk (one title per line × every
   day in a date range, all at the same start/end times, live count preview
   capped at 61 days) via `createCampSessions`; **One-off sessions** keeps the
   repeatable custom slots. **Import sessions** takes the schedule the camp was
   planned in: an Excel file (`.xlsx`), a CSV, or rows pasted straight out of a
   sheet. Clean columns (`title, start, end, location`, header optional) map on
   their own; a messy sheet goes through **Map with AI**. Either way the parsed
   sessions are listed for review before anything is created. **Add people** opens the shared collaborator invite
   panel (same one as session participants): pick people from the org's
   writers/members/team members, invite someone new by email, or pick an
   **artist team** and choose who comes with them. Picking a team loads its
   people through `getInvitableTeamMembersAction` and pre-fills its **artists**
   as rows, so a camp can take an artist plus their two writers instead of the
   whole roster; each picked row is invited through `inviteCampParticipant`
   with `teamId` set, so the roster shows their team badge. Clearing every row
   falls back to `inviteCampTeam`, which fans the team's active `user_teams`
   members into `camp_participants` rows (dupes by user/email skipped, no
   emails sent until the camp publishes, `team_id` stamped on each row). The
   camp mixes freely: foreign artists with their own teams, unaffiliated
   songwriters invited by email, and org writers all land in the same pool. Pool and member
   avatars resolve from `avatar_key` via the batched optimized-URL helpers.
6. The camp **overview calendar** also supports click-and-drag to create a
   session on a specific day and time (week/day views), plus a right-click /
   hover-`+` create menu on day cells. The same interaction as the personal
   `/app/calendar`. Dragging a time range opens a quick "New session" dialog
   pre-filled with the picked start/end; confirming creates the session via
   `createCampSessions`. This rides the shared `EventCalendar`'s
   `createItemTypes` prop (passed `['session']`) so the drag-create picker only
   ever offers a session. The org breadcrumb resolves the camp id to the camp's
   name (via the layout's `x-pathname` + request-deduped `getCampDetail`).

**Join by personal invite.** Open the link from your email. If you already have
an Aden account you sign in; if not, you create one (no invite code needed). As
soon as you're signed in you're dropped straight into the camp. If the invite
includes a document to sign, you sign it first, then join.

**Join by open signup link.** Open the link (no login needed) and fill in your
name, what you play, an optional note, and any custom questions the organisers
added. The camp's location links straight out to maps. If you're signed in,
your account is shown and you can choose which team you're attending with;
otherwise you can still apply as yourself. Your application goes to the
organisers, who accept or reject it. You'll hear back by email.

**Price to join.** Organisers can optionally set a price to join a camp (shown
on the public signup page as a "… to join" badge, with an optional note after
it like "+ VAT"). Leave it empty to keep the camp free.

**Invoice the people who join.** **People → Invoices** lists everyone holding a
spot next to their invoice state (not invoiced / draft / unpaid / overdue /
paid). Tick the people you want to bill and hit **Create invoices**: the amount
per person is prefilled with the camp's join price, and you set the VAT rate and
due date once for the batch. Leave **Issue and email now** off to get drafts you
can still edit, or switch it on to number them and email each participant the
amount and payment reference straight away. Everything lands in the
organisation's normal invoice list, so PDFs, e-invoice exports, reminders and
mark-as-paid all work the same as any other invoice. An issued invoice also
gets its own public page. The participant's email links straight to it, and
the roster row has an open-invoice button, so people without an Aden account
can read the invoice and its payment reference. Nobody can be billed twice
someone with a live invoice can't be selected again until it's canceled.

**One contract everyone signs.** Under **Settings → Contract** organisers write
the camp's terms (splits, confidentiality, code of conduct, travel. Markdown)
and/or upload a contract PDF, and choose whether it **must be signed before
joining**. The title is optional. Left blank it is taken from the uploaded
file's name, or falls back to "\<camp> contract". Organisers can also attach
**extra documents** (house rules, travel brief, studio map): they ride along
with the contract and are readable/downloadable on the signing page, but are
never signed. From then on everyone who lands on the roster is asked
to sign it: invited people get it with their invite (and, when it's required,
sign it on the invite page before they're let into the camp), accepted
applicants and open-signup joiners get it the moment they're in. Signing runs
through the normal Aden flow. A private link, a saved signature, a signed PDF
with a certificate, and a verification page, so it behaves like every other
contract. **People → Roster** shows a Signed / To sign badge per person; opening it gives
the contract title, the signing date, a link to the signed copy (or the signing
page), a copy-the-link action for chasing, and the public verification page.
**Send to everyone on the roster** catches up anyone added before the contract
existed (nobody is asked twice).

**The public camp page.** The signup link is the camp's own page, not just a
form. Under **Settings → Public page** organisers fill in as much or as little
as they want, and each block only appears once it has content:

* **Schedule**: day by day ("Monday", "Tue – Thu"), each with timed items
  (welcome reception, breakfast, writing sessions, listening session,
  transfers). Free-text times, so "evening" works as well as "9:00".
* **What's included**: what the join price covers, plus a price note.
* **Getting there**: routes, transfers, nearest airport and station.
* **The team**: hosts with a photo, role and short bio.
* **Your rights**: where masters and publishing land, and any first-option
  arrangement.
* **FAQ**: grouped by category (Funding / General / Technical), rendered as
  one accordion per group.
* **Testimonials**, **partners** (label, sponsors), and a **contact** block
  with email, phone and social links.

**Signup waves, spots and the waitlist.** Camps run in rounds. Under
**Settings → Public signup** organisers set a wave name ("Wave 2"), when
signups open and close, and how many spots there are:

* Outside the window, or once the camp is full, the form is replaced by a
  &#x2A;*"notify me"** box: nobody hits a dead end. Organisers can write their own
  closed message ("we're reviewing round 1, wave 2 opens 1 August").
* With **waitlist** on, a full camp keeps taking applications; they arrive as
  waitlisted and never auto-join. Accepting one from the People page promotes
  it into the camp.
* When 10 or fewer spots remain, the page shows a real "3 spots left" badge.
* People who already applied keep seeing their own submission after the wave
  closes (locked, not editable).

**A custom public link.** By default the public page lives on the unguessable
token (`/camps/898o8-rx0RJ…`). Under **Settings → Public signup** organisers can
claim a readable alias instead, `/camps/durbuy-camp-2026`, validated as
lowercase letters, numbers and dashes and unique across all camps. The token
link keeps resolving to the same page, so anything already shared stays valid.
The camp name in the header doubles as a menu (open the public page, copy the
link, jump to applications), and the applications tab carries the same copy/open
link above the queue.

**Interest list.** Everyone who left their email while signups were shut lands
on **People → Interest**, where organisers can copy every address or open a
pre-filled draft to tell them the next wave is open.

**Benefits (what you get).** Organisers can list the perks participants get
a sponsor goodie bag, hardware, plugin licenses. Under **Settings →
Benefits**. They show on the public signup page as a "What you get" list, and
on **People → Roster** each participant gets a gift button with a delivered
counter so the team can tick off what has actually been handed out (goodie-bag
logistics).

**Edit or opt out after signing up.** If you're signed in and have already
applied or joined, the signup page shows your existing submission with its
status instead of a blank form. You can **edit** your name, roles, note, and
answers, or **opt out** to remove your submission (you can sign up again later
if you change your mind).

**See your camps everywhere.** Camps you're part of show up on your Aden
calendar as a multi-day event, in any calendar you've subscribed to via the
camp's ICS feed, and in the Aden mobile app under a dedicated **Camps** screen.

**End-of-camp listening session.** After a camp, the **Listening** tab plays
every final track and records what happens with each one. Release, pitch, hold,
or archive, with optional notes. Two things you can do with that session:

* **Share it.** Create a public link (with a QR code) that anyone can open
  without an account to hear the final tracks and see your decisions. Toggle the
  link off and on at any time; turning it off makes the link stop working
  without losing it.
* **Convert it to a playlist.** Turn every final camp track into a playlist in
  one click, then keep editing, reordering, or sharing it like any other
  playlist.