Ticket total is derived as available + sold — inflates on every organizer edit #143

Closed
opened 2026-09-06 13:35:45 +00:00 by padreug · 0 comments
Owner

Client-side half of aiolabs/events#34. Postponed for now; filing so the
webapp changes aren't lost when the backend fix lands.

Symptom

The "Transhumance of the Merens" event on aio-demo renders
"10 of 17 tickets left" for an event the organizer set to 10 tickets.
The number climbs by sold every time the organizer edits the event.

Cause

src/modules/events/types/event.ts:98 reconstructs the total by
addition, because the NIP-52 event never publishes one:

total: ticket.available !== undefined ? ticket.available + ticket.sold : undefined,

That identity only holds while tickets_available is strictly decremented
by sales. The backend also lets organizers overwrite it wholesale, which
re-baselines remaining and inflates the derived total by sold. Full
mechanism and drift history in aiolabs/events#34.

Rendered at:

  • src/modules/events/components/EventCard.vue:248
  • src/modules/events/views/EventDetailPage.vue:350

both via events.detail.ticketsRemainingOfTotal ({count} of {total} tickets left).

The form field feeds the same confusion

src/modules/events/components/CreateEventDialog.vue:619-625 labels the
input simply "Tickets" with helper text "0 = unlimited", and
:248 prefills it from the raw backend value:

amount_tickets: event.amount_tickets ?? 0,

That value is remaining, not capacity. An organizer with 7 sold sees
9, reads it as "capacity 9", corrects it to 10, and silently sets
capacity to 17. This is how the demo event drifted.

What to change once the backend lands

aiolabs/events#34 proposes publishing an explicit tickets_total tag
and keeping the organizer input as "tickets still available for sale"
(so capacity = sold + input makes capacity-below-sold structurally
impossible). On this side that means:

  • Parse tickets_total in parseTicketTags
    (src/modules/events/types/nip52.ts:132) and add it to TicketTags.
  • Read it directly in ticketTagsToInfo (event.ts:89), keeping
    available + sold only as a fallback for events published before the
    change — old relay copies will never gain the tag.
  • Relabel the form field to match whatever semantics the backend
    settles on, and revisit the "0 = unlimited" helper text: under the
    proposed model 0 means "stop selling", which is not the same thing.

No webapp change is worth making before the backend decision is final —
the parsing and the label both depend on it.

  • aiolabs/events#34 — capacity semantics (root cause)
  • aiolabs/events#35 — swallowed publish failures, which independently
    let stale counters sit on the relay
Client-side half of `aiolabs/events#34`. Postponed for now; filing so the webapp changes aren't lost when the backend fix lands. ## Symptom The "Transhumance of the Merens" event on aio-demo renders **"10 of 17 tickets left"** for an event the organizer set to 10 tickets. The number climbs by `sold` every time the organizer edits the event. ## Cause `src/modules/events/types/event.ts:98` reconstructs the total by addition, because the NIP-52 event never publishes one: ```ts total: ticket.available !== undefined ? ticket.available + ticket.sold : undefined, ``` That identity only holds while `tickets_available` is strictly decremented by sales. The backend also lets organizers overwrite it wholesale, which re-baselines *remaining* and inflates the derived total by `sold`. Full mechanism and drift history in `aiolabs/events#34`. Rendered at: - `src/modules/events/components/EventCard.vue:248` - `src/modules/events/views/EventDetailPage.vue:350` both via `events.detail.ticketsRemainingOfTotal` (`{count} of {total} tickets left`). ## The form field feeds the same confusion `src/modules/events/components/CreateEventDialog.vue:619-625` labels the input simply **"Tickets"** with helper text "0 = unlimited", and `:248` prefills it from the raw backend value: ```ts amount_tickets: event.amount_tickets ?? 0, ``` That value is *remaining*, not capacity. An organizer with 7 sold sees `9`, reads it as "capacity 9", corrects it to `10`, and silently sets capacity to 17. This is how the demo event drifted. ## What to change once the backend lands `aiolabs/events#34` proposes publishing an explicit `tickets_total` tag and keeping the organizer input as "tickets still available for sale" (so `capacity = sold + input` makes capacity-below-sold structurally impossible). On this side that means: - Parse `tickets_total` in `parseTicketTags` (`src/modules/events/types/nip52.ts:132`) and add it to `TicketTags`. - Read it directly in `ticketTagsToInfo` (`event.ts:89`), keeping `available + sold` only as a fallback for events published before the change — old relay copies will never gain the tag. - Relabel the form field to match whatever semantics the backend settles on, and revisit the "0 = unlimited" helper text: under the proposed model `0` means "stop selling", which is not the same thing. No webapp change is worth making before the backend decision is final — the parsing and the label both depend on it. ## Related - `aiolabs/events#34` — capacity semantics (root cause) - `aiolabs/events#35` — swallowed publish failures, which independently let stale counters sit on the relay
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
aiolabs/webapp#143
No description provided.