One party backend, member-scoped.
One backend for the whole party: the party itself, the food, the todos, the photos — four tables, every one scoped to the party's own members. If you are not in the party, you do not see its plans. This is how a group gets a shared space with the access rules built into the schema, not bolted on per page.
The permissions are the app. This is the whole data model — schema.ts, the first of the app's three files (its import line, not shown, brings these names in from "create-dillo/schema"):
// The schema the live backend serves today (span2/live-schemas filing, re-verified at deploy).
export default schema({
Food: table({
bringing: relation.user().optional().nullable(),
name: string().min(1).max(120),
partyId: relation.team(),
quantity: integer().min(1).max(100),
}).permissions({ view: members, edit: recordOwner.or(roles('admin', 'owner')) }),
Party: table({
name: string().min(1).max(120),
partyId: relation.team(),
place: string().min(1).max(200),
startsAt: string().datetime(),
}).permissions({ view: members, edit: roles('admin', 'owner') }),
Photo: table({
caption: string().min(1).max(200),
image: file().accept('image/jpeg', 'image/png', 'image/webp').maxBytes(5242880),
partyId: relation.team(),
}).permissions({ view: members, edit: recordOwner.or(roles('admin', 'owner')) }),
Todo: table({
done: boolean(),
partyId: relation.team(),
task: string().min(1).max(160),
}).permissions({ view: members, edit: members }),
});