@go-labs-sg/rsvp 1.4.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -262,6 +262,14 @@ Replace illustrative IDs and revisions with current readback values. Deploy API
262
262
  policy enforcement before clients; an old API does not understand this field.
263
263
  No authentication, consent flags, command names, or credential protocol changes.
264
264
 
265
+ ### Registration-flow choice labels
266
+
267
+ `SINGLE_SELECT`, `VISUAL_SINGLE_SELECT` and `MULTI_SELECT` choices accept a
268
+ trimmed `label` of 1–500 characters, so a required single-choice `MULTI_SELECT`
269
+ can show a full-sentence declaration beside its tick box. Choice `value` stays a
270
+ stable machine value of 1–160 characters. Older APIs reject labels over 160
271
+ characters; deploy the API before saving longer labels.
272
+
265
273
  ### Registration-flow date-limit messages
266
274
 
267
275
  `DATE` and `DATETIME` custom fields accept optional
@@ -744,8 +752,9 @@ High-impact payment and retention commands remain protected by their matching fi
744
752
 
745
753
  ## Registration master list export
746
754
 
747
- `export request` queues the single **Registration master list** Excel workbook
748
- (one **Master List** sheet; question columns follow the event's form). The
755
+ `export request` queues the **Registration master list** Excel workbook
756
+ (**Master List** by default; optional registration/attendance tabs follow event
757
+ settings and permissions; question columns follow the event's form). The
749
758
  payload is `organizationId`, `eventId` and an audit `reason`; the old `dataset`,
750
759
  `format` and `filters` fields are ignored. See the RSVP app README for columns.
751
760
 
@@ -767,8 +776,40 @@ population. Non-operational statuses remain excluded, and permissions, payment
767
776
  gates, duplicate protection, and undo behavior are unchanged. The web panel's
768
777
  realtime connection and manual refresh controls do not add a CLI subscription.
769
778
 
779
+ Set `registrationMasterList.includeAttendanceSheets: true` with an approved
780
+ `event setup step update` (`section: "REGISTRATION_RULES"`) to add response-based
781
+ **Attending**, **Not attending** (explicit declines only), and **All** tabs,
782
+ plus the retained **Registered**, **Not Registered**, **Attended** and
783
+ **Not Attended** views. It defaults off. All matches every Master List person,
784
+ including guests and nonrespondents; attending/declined subsets use canonical
785
+ completed responses and never treat nonresponse as a decline.
786
+ Registered includes attending, legacy submitted and declined completed responses;
787
+ actual check-in tabs include only attending/legacy-submitted people and require
788
+ `check-in.view`. An undone check-in returns a person to Not Attended. Preserve
789
+ the other current master-list settings when editing this object.
790
+
770
791
  Deploy matching API and export-worker versions together; an old worker would build the retired
771
- registration-records report for master-list jobs.
792
+ registration-records report for master-list jobs. See the
793
+ [follow-up contract](../../../apps/rsvp/reporting-follow-up.md) for exact tab
794
+ populations, worker-order/rollback gates and the separate later post-draw merge.
795
+
796
+ ## Seat-change email presentation
797
+
798
+ In an approved `event setup step update` for `REGISTRATION_RULES`,
799
+ `seatChangeNotification.includeTablemates` controls saved same-table display
800
+ names (default off), and `seatChangeNotification.includeFloorPlanImage` controls
801
+ the inline highlighted floor-plan image (default on). Both accept explicit
802
+ `true` or `false` independently. Preserve the current notification subject/body,
803
+ enabled/automatic state, other registration rules and expected configuration
804
+ version when editing. Omitting either property preserves its stored value.
805
+
806
+ Explicit event presentation values apply to manual and automatic seat-change
807
+ notices, overriding saved-form presentation only; omissions inherit the form.
808
+ These switches do not enable notices, change seats or send/resend email. With
809
+ the image off, text assignments remain and the worker skips map/image work.
810
+ Deploy compatible workers before relying on the switches; old workers do not
811
+ honor the image off-switch. Tablemate-name disclosure still needs privacy
812
+ approval. The web exposes both switches in Registration rules.
772
813
 
773
814
  ## Seating updates
774
815
 
@@ -911,6 +952,49 @@ rsvp people registration update \
911
952
  --payload '{"organizationId":"org_123","eventId":"evt_123","registrationId":"reg_123","expectedVersion":4,"reason":"Moved to finance table","seats":[{"registrationAttendeeId":"att_1","seatId":"seat_9"},{"registrationAttendeeId":"att_2","seatId":"seat_2"}],"seatChangeNotice":{"idempotencyKey":"4d68a320-013d-43c7-bfcc-d1126827d8c5"}}'
912
953
  ```
913
954
 
955
+ ### Archive or cancel registrations
956
+
957
+ `people registration archive` and `people registration cancel` forward the same
958
+ permissioned, audited `event.bulkManageRegistrations` operation as the web app.
959
+ Both require server-side `rsvp.manage` permission and `--allow-state-change`;
960
+ `--target` must match the payload's **event ID**, not a registration ID.
961
+
962
+ Supply `organizationId`, `eventId`, an audit `reason` (3–500 characters after
963
+ trimming), and 1–100 unique `registrations`, each with `id` and its current
964
+ positive-integer `expectedVersion`. Get revisions from `people registration list`
965
+ or `people registration edit get`. The command sets `action` to `ARCHIVE` or
966
+ `CANCEL` when omitted; explicitly supplying any other action is rejected. Missing
967
+ or invalid fields, duplicate IDs and oversized batches fail before dispatch.
968
+ Stale revisions are checked by the server; refresh and review before retrying.
969
+
970
+ ```sh
971
+ rsvp people registration archive \
972
+ --allow-state-change --target evt_123 \
973
+ --payload '{"organizationId":"org_123","eventId":"evt_123","reason":"Remove refunded test registrations","registrations":[{"id":"reg_123","expectedVersion":6},{"id":"reg_456","expectedVersion":2}]}'
974
+
975
+ rsvp people registration cancel \
976
+ --allow-state-change --target evt_123 \
977
+ --payload '{"organizationId":"org_123","eventId":"evt_123","reason":"Participant withdrew","registrations":[{"id":"reg_789","expectedVersion":3}]}'
978
+ ```
979
+
980
+ - **Archive** sets `archivedAt` and removes the registrations from default lists
981
+ and exports. It also cancels active registrations through the normal release
982
+ path; this is not merely hiding an attending registration.
983
+ - **Cancel** cancels active registrations without changing `archivedAt`. Cancelled
984
+ registrations remain visible unless already archived.
985
+ - Both release consumed event capacity, active seat bookings and holds, cancel
986
+ session selections and waiting/offered waitlist entries, and run the existing
987
+ event/session waitlist maintenance. Released places may generate waitlist
988
+ offers and **emails to other participants**; neither command sends a
989
+ cancellation email to the affected registrants. No `--allow-email` flag is
990
+ required by these commands' existing `state-change` contract.
991
+ - Neither command automatically refunds a payment or deletes records. Payment,
992
+ refund and audit history are retained. Pending checkouts linked to a provider,
993
+ processing payments and payments requiring review must be reconciled first;
994
+ unlinked pending payment attempts are cancelled by the existing server logic.
995
+ - Use **Unarchive** below for archive visibility/fresh-response recovery, or
996
+ **Restore** for attending recovery. Unarchive does not undo cancellation.
997
+
914
998
  ### Unarchive for a fresh response versus attending Restore
915
999
 
916
1000
  `people registration unarchive` forwards the permissioned, audited
@@ -1011,6 +1095,25 @@ previous effects.
1011
1095
 
1012
1096
  Optional `registrationEmail` and `registrationMasterList` presentation settings
1013
1097
  are also written through `REGISTRATION_RULES`; omitted settings are preserved.
1098
+
1099
+ ### Registration email sender
1100
+
1101
+ Confirmation and seat-change emails send from the server default
1102
+ (`RSVP_EMAIL_FROM`) unless the event sets a sender in `registrationEmail`:
1103
+
1104
+ - `senderName` changes only the display name.
1105
+ - `senderLocalPart` and `senderDomain` (set both or neither) change the address,
1106
+ for example `"senderLocalPart": "nelbd", "senderDomain": "getout.sg"` sends
1107
+ from `"NELBD Quest" <nelbd@getout.sg>`. `senderDomain` must be a Resend domain
1108
+ that is verified with sending enabled when the update is saved; list them with
1109
+ `rsvp event email sender-domains`. Invalid local parts, a missing half of the
1110
+ pair or an unverified domain are rejected.
1111
+
1112
+ `registrationEmail` replaces the whole presentation object, so read the current
1113
+ event first and resend every key you want to keep. Reply-to and support email
1114
+ are unchanged. If a saved domain is later unverified in Resend, those emails
1115
+ fail and are recorded as failed deliveries; they never fall back silently to a
1116
+ different sender. Fix the domain or remove the address fields to resume.
1014
1117
  See `examples/amgen-rsvp-copy/registration-presentation-patch.json` and its README
1015
1118
  for a reviewed activation fragment, output-key semantics, receipt/header behavior
1016
1119
  and the separate deployment/configuration/live-send approval gates. No event
package/dist/index.js CHANGED
@@ -7726,11 +7726,13 @@ var flowBlockBaseShape = {
7726
7726
  title: string2().trim().min(1).max(240).optional(),
7727
7727
  visibleWhen: eventFlowConditionGroupSchema.optional()
7728
7728
  };
7729
+ var maxEventFlowChoiceLabelLength = 500;
7730
+ var maxEventFlowChoiceValueLength = 160;
7729
7731
  var customFieldChoiceSchema = object({
7730
7732
  assetId: eventFlowEntityIdSchema.optional(),
7731
7733
  description: string2().trim().max(500).optional(),
7732
- label: string2().trim().min(1).max(160),
7733
- value: string2().trim().min(1).max(160)
7734
+ label: string2().trim().min(1).max(maxEventFlowChoiceLabelLength),
7735
+ value: string2().trim().min(1).max(maxEventFlowChoiceValueLength)
7734
7736
  });
7735
7737
  var maxEventFlowDateBoundMessageLength = 500;
7736
7738
  var eventFlowDateBoundMessageSchema = string2().trim().min(1).max(maxEventFlowDateBoundMessageLength);
@@ -7955,6 +7957,8 @@ var defaultEventSeatChangeNotification = {
7955
7957
  var eventSeatChangeNotificationSchema = object({
7956
7958
  body: string2().trim().max(1e4).default(""),
7957
7959
  automatic: boolean2().optional(),
7960
+ includeTablemates: boolean2().optional(),
7961
+ includeFloorPlanImage: boolean2().optional(),
7958
7962
  enabled: boolean2().default(false),
7959
7963
  subject: string2().trim().max(240).default("")
7960
7964
  }).superRefine((notification, ctx) => {
@@ -10109,10 +10113,17 @@ var PUBLIC_EVENT_VALIDATED_FONT_FAMILIES = [
10109
10113
  "Zilla Slab Highlight"
10110
10114
  ];
10111
10115
 
10116
+ // ../validators/src/email-sender.ts
10117
+ var emailSenderLocalPartSchema = string2().trim().max(64, "Sender address must be 64 characters or fewer").regex(/^(?!.*\.\.)[a-z\d](?:[a-z\d._+-]{0,62}[a-z\d])?$/i, "Enter a valid sender address before @");
10118
+ var emailSenderDomainSchema = string2().trim().toLowerCase().regex(/^(?:[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?\.)+[a-z]{2,63}$/, "Select a valid sending domain");
10119
+ var EMAIL_SENDER_PAIR_MESSAGE = "Choose a sender address and domain together";
10120
+
10112
10121
  // ../validators/src/registration-email-presentation.ts
10113
10122
  var registrationEmailPresentationSchema = strictObject({
10114
10123
  layout: _enum(["DEFAULT", "GROUPED"]).default("DEFAULT"),
10115
10124
  senderName: string2().trim().min(1).max(120).regex(/^[^<>\r\n]+$/).optional(),
10125
+ senderLocalPart: emailSenderLocalPartSchema.optional(),
10126
+ senderDomain: emailSenderDomainSchema.optional(),
10116
10127
  introText: string2().trim().min(1).max(1e4).optional(),
10117
10128
  menuDetailOutputKeys: array(string2().trim().min(1).max(120)).max(100).default([]),
10118
10129
  submittedConsentOutputKeys: array(string2().trim().min(1).max(120)).max(100).default([]),
@@ -10120,6 +10131,13 @@ var registrationEmailPresentationSchema = strictObject({
10120
10131
  useResubmissionFooter: boolean2().default(false),
10121
10132
  supportEmail: email2().optional(),
10122
10133
  showLuckyDrawId: boolean2().optional()
10134
+ }).superRefine((value, ctx) => {
10135
+ if (Boolean(value.senderLocalPart) !== Boolean(value.senderDomain))
10136
+ ctx.addIssue({
10137
+ code: "custom",
10138
+ message: EMAIL_SENDER_PAIR_MESSAGE,
10139
+ path: [value.senderDomain ? "senderLocalPart" : "senderDomain"]
10140
+ });
10123
10141
  });
10124
10142
 
10125
10143
  // ../validators/src/registration-master-list-presentation.ts
@@ -10129,7 +10147,8 @@ var outputKeysSchema = array(string2().min(1).max(160).regex(/^[A-Za-z][A-Za-z0-
10129
10147
  var eventRegistrationMasterListSchema = object({
10130
10148
  omittedOutputKeys: outputKeysSchema.default([]),
10131
10149
  finalOutputKeys: outputKeysSchema.default([]),
10132
- showEligibilityCutoffInHeading: boolean2().default(false)
10150
+ showEligibilityCutoffInHeading: boolean2().default(false),
10151
+ includeAttendanceSheets: boolean2().default(false)
10133
10152
  }).strict().refine((config) => !config.finalOutputKeys.some((key) => config.omittedOutputKeys.includes(key)), { message: "Omitted output keys cannot also be final output keys" });
10134
10153
 
10135
10154
  // ../validators/src/event.ts
@@ -10734,8 +10753,8 @@ var emailCampaignDraftFieldsSchema = object({
10734
10753
  audienceMode: _enum(["SEGMENT", "SELECTED"]).optional(),
10735
10754
  selectedInviteeIds: array(string2().min(1)).max(1e4).optional(),
10736
10755
  fromName: string2().trim().max(120, "From name must be 120 characters or fewer").regex(/^[^\r\n<>]*$/, "From name contains unsupported characters").optional(),
10737
- fromSubDomain: string2().trim().max(64, "Sender address must be 64 characters or fewer").regex(/^(?!.*\.\.)[a-z\d](?:[a-z\d._+-]{0,62}[a-z\d])?$/i, "Enter a valid sender address before @").optional(),
10738
- fromDomain: string2().trim().toLowerCase().regex(/^(?:[a-z\d](?:[a-z\d-]{0,61}[a-z\d])?\.)+[a-z]{2,63}$/, "Select a valid sending domain").optional(),
10756
+ fromSubDomain: emailSenderLocalPartSchema.optional(),
10757
+ fromDomain: emailSenderDomainSchema.optional(),
10739
10758
  html: string2().trim().min(1, "Email body is required").max(1e5, "Email body is too long"),
10740
10759
  name: string2().trim().min(1, "Campaign name is required").max(160, "Campaign name must be 160 characters or fewer"),
10741
10760
  previewText: string2().trim().max(240, "Preview text must be 240 characters or fewer").optional(),
@@ -10753,7 +10772,7 @@ var validateSelectedCampaignAudience = (input, ctx) => {
10753
10772
  ctx.addIssue({
10754
10773
  code: "custom",
10755
10774
  path: [input.fromDomain ? "fromSubDomain" : "fromDomain"],
10756
- message: "Choose a sender address and domain together"
10775
+ message: EMAIL_SENDER_PAIR_MESSAGE
10757
10776
  });
10758
10777
  };
10759
10778
  var createEmailCampaignInputSchema = organizationEventByIdInputSchema.extend(emailCampaignDraftFieldsSchema.shape).superRefine(validateSelectedCampaignAudience);
@@ -12343,7 +12362,7 @@ var commands = {
12343
12362
  effects: ["sensitive-data", "state-change"],
12344
12363
  operation: (api, input) => api.export.requestExport.mutate(input),
12345
12364
  payloadOnly: true,
12346
- summary: "Request the registration master list XLSX (--payload JSON: eventId, organizationId, reason)."
12365
+ summary: "Request the event-configured master list XLSX with optional attendance tabs (--payload JSON: eventId, organizationId, reason)."
12347
12366
  },
12348
12367
  "export list": {
12349
12368
  effects: ["sensitive-data"],
@@ -12393,18 +12412,21 @@ var commands = {
12393
12412
  summary: "Update a user's global role (--payload JSON)."
12394
12413
  }
12395
12414
  };
12396
- var unarchiveRegistrationsInputSchema = bulkManageRegistrationsInputSchema.safeExtend({
12397
- action: bulkManageRegistrationsInputSchema.shape.action.extract([
12398
- "RESTORE"
12399
- ])
12400
- });
12401
- var parseUnarchiveRegistrationsInput = (input) => {
12415
+ var parseRegistrationManagementInput = (input, expectedAction) => {
12416
+ const inputSchema = bulkManageRegistrationsInputSchema.safeExtend({
12417
+ action: bulkManageRegistrationsInputSchema.shape.action.extract([
12418
+ expectedAction
12419
+ ])
12420
+ });
12402
12421
  const action = input ? Reflect.get(input, "action") : undefined;
12403
- const parsed = unarchiveRegistrationsInputSchema.safeParse(input ? { ...input, action: action === undefined ? "RESTORE" : action } : input);
12422
+ const parsed = inputSchema.safeParse(input ? { ...input, action: action === undefined ? expectedAction : action } : input);
12404
12423
  if (!parsed.success)
12405
12424
  throw new CliUsageError(parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; "));
12406
12425
  return parsed.data;
12407
12426
  };
12427
+ var parseArchiveRegistrationsInput = (input) => parseRegistrationManagementInput(input, "ARCHIVE");
12428
+ var parseCancelRegistrationsInput = (input) => parseRegistrationManagementInput(input, "CANCEL");
12429
+ var parseUnarchiveRegistrationsInput = (input) => parseRegistrationManagementInput(input, "RESTORE");
12408
12430
  var payloadCommand = (operation, summary, effects = [], targetKey) => ({
12409
12431
  effects,
12410
12432
  operation,
@@ -12491,6 +12513,7 @@ Object.assign(commands, {
12491
12513
  "event seating table delete": payloadCommand((api, input) => api.event.deleteSeatTable.mutate(input), "Delete a seating table (--payload JSON).", ["delete", "state-change"]),
12492
12514
  "event seating layout update": payloadCommand((api, input) => api.event.updateSeatTableLayout.mutate(input), "Update the seating layout and floor plan (--payload JSON).", ["state-change"], "eventId"),
12493
12515
  "event seating table update": payloadCommand((api, input) => api.event.updateSeatTable.mutate(input), "Update a seating table (--payload JSON).", ["delete", "state-change"], "tableId"),
12516
+ "event email sender-domains": eventScopeQueryCommand((api, input) => api.event.getEmailSenderDomains.query(input), "List verified Resend sending domains for campaign and registration email sender addresses."),
12494
12517
  "event audience-tier list": eventScopeQueryCommand((api, input) => api.event.listAudienceTiers.query(input), "List event audience tiers and assignment counts."),
12495
12518
  "event audience-tier create": payloadCommand((api, input) => api.event.createAudienceTier.mutate(input), "Create an event audience tier (--payload JSON).", ["state-change"], "eventId"),
12496
12519
  "event audience-tier update": payloadCommand((api, input) => api.event.updateAudienceTier.mutate(input), "Update an event audience tier (--payload JSON).", ["state-change"], "tierId"),
@@ -12523,6 +12546,14 @@ Object.assign(commands, {
12523
12546
  { effect: "email", whenPayloadField: "seatChangeNotice" }
12524
12547
  ]
12525
12548
  },
12549
+ "people registration archive": {
12550
+ ...payloadCommand((api, input) => api.event.bulkManageRegistrations.mutate(parseArchiveRegistrationsInput(input)), "Archive up to 100 unique registrations with expectedVersion and reason (3\u2013500 characters); action defaults to ARCHIVE and other actions are rejected. Requires rsvp.manage; target the eventId. Cancels active registrations, releases capacity, seats and holds, cancels session selections and waitlist entries/offers, and hides archived rows from default lists/exports. Unresolved checkouts must be reconciled first; no automatic refund. Not a delete: payment, refund and audit history remain. No cancellation email; released places may trigger waitlist-offer emails (--payload JSON).", ["state-change"], "eventId"),
12551
+ validateInput: parseArchiveRegistrationsInput
12552
+ },
12553
+ "people registration cancel": {
12554
+ ...payloadCommand((api, input) => api.event.bulkManageRegistrations.mutate(parseCancelRegistrationsInput(input)), "Cancel up to 100 unique registrations with expectedVersion and reason (3\u2013500 characters); action defaults to CANCEL and other actions are rejected. Requires rsvp.manage; target the eventId. Cancels active registrations, releases capacity, seats and holds, and cancels session selections and waitlist entries/offers without changing archive visibility. Unresolved checkouts must be reconciled first; no automatic refund. Not a delete: payment, refund and audit history remain. No cancellation email; released places may trigger waitlist-offer emails (--payload JSON).", ["state-change"], "eventId"),
12555
+ validateInput: parseCancelRegistrationsInput
12556
+ },
12526
12557
  "people registration unarchive": {
12527
12558
  ...payloadCommand((api, input) => api.event.bulkManageRegistrations.mutate(parseUnarchiveRegistrationsInput(input)), "Unarchive up to 100 unique registrations with expectedVersion and reason; action defaults to RESTORE and other actions are rejected. Target the eventId. Eligible cancelled nonattending invitees can submit afresh; historical registration, payment and QR are not reinstated. Fresh submissions use normal checks and new payment/credentials; attending recovery uses people registration restore (--payload JSON).", ["state-change"], "eventId"),
12528
12559
  validateInput: parseUnarchiveRegistrationsInput
@@ -12536,7 +12567,7 @@ Object.assign(commands, {
12536
12567
  "event mine list": payloadCommand((api, input) => api.event.listMyEvents.query(input), "List the caller's events (--payload JSON)."),
12537
12568
  "event reminder list": payloadCommand((api, input) => api.event.listReminderSegments.query(input), "List reminder segments (--payload JSON).", ["sensitive-data"]),
12538
12569
  "event rsvp submit": payloadCommand((api, input) => api.event.submitRsvp.mutate(input), "Submit the caller's registration (--payload JSON).", ["sensitive-data", "state-change"]),
12539
- "event setup step update": payloadCommand((api, input) => api.event.updateSetupStep.mutate(input), "Update one revision-checked event setup step (--payload JSON). REGISTRATION_RULES accepts registrationEmail presentation, registrationMasterList output-key ordering/omissions and seatChangeNotification automatic policy; omitted settings are preserved.", ["state-change"]),
12570
+ "event setup step update": payloadCommand((api, input) => api.event.updateSetupStep.mutate(input), "Update one revision-checked event setup step (--payload JSON). REGISTRATION_RULES accepts registrationEmail presentation (including senderName and a senderLocalPart+senderDomain address on a verified Resend sending domain), registrationMasterList output-key ordering/omissions and includeAttendanceSheets, and seatChangeNotification automatic/includeTablemates/includeFloorPlanImage policy (tablemate names default off; floor-plan image defaults on); omitted settings are preserved.", ["state-change"]),
12540
12571
  "event page update": payloadCommand((api, input) => api.event.updateSetupStep.mutate({
12541
12572
  ...input,
12542
12573
  section: "DETAILS_PAGE"
@@ -145,6 +145,33 @@ to events without automatic notices; the flag grants approval but does not
145
145
  itself enable sending. Non-seating profile/menu updates and saving or discarding
146
146
  a draft do not acquire an email effect.
147
147
 
148
+ ## Follow-up reporting and tablemates (#1657)
149
+
150
+ The optional workbook tabs (`registrationMasterList.includeAttendanceSheets`)
151
+ and tablemate display names (`seatChangeNotification.includeTablemates`) are
152
+ off by default. The existing example fragment intentionally does not activate
153
+ them. Preserve the full current configuration and obtain event privacy approval
154
+ before opting in. Clients omitting `includeTablemates` retain the stored value;
155
+ explicit `false` disables it. See the
156
+ [generic follow-up contract](../../../../../apps/rsvp/reporting-follow-up.md)
157
+ for exact tab populations and delivery privacy boundaries. The web also exposes
158
+ independent **Include tablemate names** and **Include floor-plan image** switches.
159
+ The latter is `seatChangeNotification.includeFloorPlanImage` (default on; false
160
+ omits map loading, image generation and attachment while retaining text seats).
161
+ Explicit current-event presentation values apply to manual and automatic notices
162
+ without changing notice enablement or copy. Deploy all email workers before
163
+ relying on the image off-switch. Post-draw results
164
+ will later merge the RSVP export with the Event Tech lucky-draw export; this
165
+ change does not infer winners from eligibility or create result placeholders.
166
+
167
+ **Worker-order warning:** upgrade/fence all email workers before enabling
168
+ automatic policy for historical forms. An old worker can skip a new notice
169
+ using old saved-form settings and mark it complete. Deploy matching export
170
+ workers before requesting optional tabs as well. For rollback, pause/fence
171
+ producers and workers, disable the new policies with a compatible API after
172
+ approval, and reconcile queued/in-flight messages before old workers consume
173
+ them. Never automatically resend skipped/completed jobs.
174
+
148
175
  Production deployment, event configuration writes, live submissions/reseating,
149
176
  export jobs, resends and any real provider send require explicit approval and
150
177
  readback. Local HTML/XLSX and synthetic browser checks are not live acceptance.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@go-labs-sg/rsvp",
3
- "version": "1.4.0",
3
+ "version": "1.6.0",
4
4
  "description": "RSVP CLI for authorized event operations.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",