Skip to content

v1.14.1 - #103

Merged
pataniaeli merged 14 commits into
mainfrom
dev
Sep 10, 2026
Merged

v1.14.1#103
pataniaeli merged 14 commits into
mainfrom
dev

Conversation

@pataniaeli

Copy link
Copy Markdown
Collaborator

No description provided.

pataniaeli and others added 14 commits September 9, 2026 18:36
…#94)

Advance notice exists so that time is *claimed* a set number of hours
before it is used, so the thing to test is the time an edit newly claims
-- not whether the start moved, which is what the PATCH route tested.
Shortening a booking, pushing its start later, renaming it or cancelling
it release time or leave it alone, and none of them needs notice.

lib/spaces-advance-notice.ts works out the earliest instant an edit newly
claims and refuses only that. Extending the end of a booking whose end is
still outside the window is therefore allowed, as the issue asks, because
the block being claimed starts at the old end. Relocating a booking
wholesale to a date past the cutoff is allowed for the same reason, and
is not treated as an extension. Creation claims its whole interval, so the
POST route reduces to the test it already ran and now shares the rule.

`now` defaults to bostonWallClockNow() and not to Date.now(). A space
booking's start_time holds Boston wall-clock digits with a Z on the end,
so measuring it against a real instant makes every booking look an offset
earlier than it is -- issue #87, fixed in 1e3d894, which this would
otherwise have reintroduced. The default belongs in the rule rather than
at each call site because the browser is a caller too, and its clock is in
whatever zone the viewer happens to be sitting in.

The real blocker was in the calendar: the notice-zone guard in the click
handler returned before the "is this a booking I own?" branch, so a
booking sitting inside the window could not be opened at all, let alone
shortened or cancelled. Your own booking is now reached first, in both the
click and the hover handler, since opening a booking you already hold
claims nothing.

The edit modal runs the same rule as the form changes, so a change that
would be refused says so before it is submitted rather than after a round
trip.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Committees were distinguished from boards, teams and working groups by
their name and nothing else, so no query could ask for "the committees"
without pattern-matching a string. bodies.body_type is that distinction,
backfilled from the names that have been carrying it: 9 Committee,
9 Board, 3 Advisory Board, 5 Team, 3 Working Group, and Senate and SGA
General on Other, which is correct for them rather than a failed match.
Advisory Board is tested before Board, or the three advisory boards would
come out as plain boards.

Committees get the reminder: the day before each weekly meeting, the bot
posts the date, time, room, purpose and -- only when it is unusual -- the
status, to the channel named by bodies.slack_channel_id. An id and not a
name, because channels get renamed and a reminder that stops posting
because someone tidied up a channel name is worse than no reminder.

A week's overrides resolve against its series the same way My Rooms and
the update emails resolve them, and a hidden booking is never announced:
a channel is a wider audience than the people who can manage it. Anything
Cancelled, Repurposed or Missed is skipped, but Pending Cancellation is
posted with its status shown -- the people in the channel are exactly the
ones who need to know a meeting is in doubt.

Leadership turns the reminders off from inside their own channel, with
/chambers-reminders off. The channel identifies the committee, so there is
no body to pick and no way to reach another committee's settings; changing
it additionally requires Leadership of that committee, checked against
board_memberships rather than anything Slack asserts. With no argument the
command reports the current state, which anyone in the channel may ask.

Scheduled from a GitHub Action like the keep-warm ping, for the reason
that one is: Hobby plans cap Vercel crons at one a day and this needs to
retry, since GitHub schedules lag and are occasionally skipped. It fires
five times across the morning; a 9am-Eastern gate keeps it from posting
overnight when the date rolls over, and a recorded row per reminder keeps
the later firings from posting twice. That row is keyed on
(weekly_booking_id, occurrence_date) rather than an occurrence id, because
the weekly PATCH regenerates its occurrences on every save and a key on
their ids would forget every reminder the next time anyone made an edit.

Slack answers a post to a channel the bot was never invited to with
ok:false and HTTP 200, so lib/slack.ts checks the body rather than the
status -- a reminder that silently never arrives is the failure this is
most likely to have. A refused post deliberately writes no row, so the
next run of the morning tries again.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both pin America/New_York, and dev grew lib/boston-time.ts (#87) while
this branch was open, so the next reader meets two modules that look
interchangeable and are not. APP_TIME_ZONE is about DATE columns -- which
calendar day a booking falls on. bostonWallClockNow is about SGA Spaces
timestamps, which store wall-clock digits labelled Z and need a "now" in
that same shape to compare against.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The weekly PATCH picked the session to describe with "the first
occurrence carrying any override". That is a different question from
"which week did this edit move", and usually a different week: an
override set on week 1 months ago is still an override today, so every
later edit to a series was reported against week 1 -- with an empty
"What changed" table, because week 1 had not in fact moved.

Occurrences are deleted and reinserted on every save, so the answer has
to come from a comparison against the rows that were there before.
lib/weekly-occurrences.ts does that, normalising the shapes Postgres and
the editor disagree about (18:30:00 vs 18:30, null vs '') so a value that
only changed form is not read as a change. is_event is compared as a
plain boolean rather than an override, since an absent value there means
"not an event" and not "inherit".

The user_alerts rows now point at the earliest week that moved, and the
email describes every week that moved rather than one of them -- the
weekly editor saves all weeks at once, so a single save can move several,
and naming only the first meant the rest changed silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…93)

The preference existed only on the client, inside the My Rooms list
filter, so deselecting Office Hours hid those sessions from the page and
did nothing else. Update emails and dashboard alerts still went to every
member of the Senate, including the people who had said they did not want
them.

The rule -- and the list of session types, which was declared twice --
now live in lib/senate-types.ts, and resolveBookingRecipients applies it
to the audience it returns, so the page, the emails and the alerts cannot
drift apart. The weekly PATCH tells it which sessions the notification is
about: the weeks that moved, or all of them when the series itself moved.

One wanted session in a batch is enough to keep a member in the audience.
Someone who follows Full Body but not Office Hours still needs the email
about a save that moved both -- suppressing it would lose a session they
asked to know about, which is the worse failure of the two.

The filter applies to Leadership as well. The preference is about what
someone reads, not what they are responsible for, and Leadership can
still see every session on the booking itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…fications

fix: point weekly update notifications at the right week, and honour Senate type preferences (#91, #92, #93)
package.json, the lockfile's two root entries, and the sidebar label.

Only lines 3 and 9 of package-lock.json belong to the project; a find-and-replace
across the file would bump a dependency to a version that does not exist and
break npm ci.

A patch rather than a minor: this release only fixes things. It carries the
weekly update emails and alerts pointing at the week that actually moved (#91),
Senate session-type preferences applying to emails and alerts rather than only
to the My Rooms list (#92, #93), and SGA Space bookings inside the advance
notice window becoming editable again (#94) -- which relaxes a restriction
rather than adding a capability.

The FAQ entry is written for members: what changed for someone using Chambers.
None of it is administrator-only this time, so nothing is named as such. The
v1.15.0 placeholder stays where it is and keeps pointing forward.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-edits

feat: allow non-creative edits to SGA Space bookings past advance notice (#94), and bump to v1.14.1
Marking a request Done set status='Done' on the request row and touched
nothing else, so the booking stayed Pending Cancellation forever.

Applying the outcome is not enough on its own, because the request could
not find the reservation it was about. cancellation_requests.occurrence_id
names a dated row, and for a weekly booking that row does not survive an
edit: the PATCH handler deletes every occurrence and reinserts it on each
save, so the ids change while the values are carried across on the date.
Every cancellation_requests row in production is in that state -- all four,
including the one still Pending.

That had a second consequence nobody was looking for. Auto-Cancel finds
those reservations anyway, because it scans by status rather than by
request, but it could not attribute them back to a request -- so it lost
the cancellation_type and fell back to 'Cancelled'. A request that asked
to go Virtual would have cancelled the meeting outright.

So requests now record occurrence_date, resolved at creation while the id
still names a live row, and are matched on (booking_id, occurrence_date)
with the id tried first for the cases where it still resolves. The date is
the stable identifier the write model actually preserves, which is what
20260829001000 concluded for event_tracking and what issue #69 concluded
for calendar UIDs.

Verified against production, read-only: the id the one Pending request
stored resolves to zero rows, while (booking_id, date) resolves to exactly
one -- and it is the row marked Pending Cancellation.

The backfill recovers nothing in production, because there is no record
anywhere of which date those four requests named. They are left NULL
rather than guessed at; the one still Pending needs an administrator's eye,
since its booking has two weeks pending and only one request. The backfill
is written for environments where the rows are intact, and for a restore.

Marking Done includes reservations with no code on file, which Auto-Cancel
excludes. A missing code means CSC cannot be asked; it says nothing about
whether an administrator has dealt with it, and marking Done by hand is
that administrator saying they have. This is why SkippedReservation now
carries the identity to act on rather than only the four display fields.

Applying the statuses and building the audit rows are now shared with
Auto-Cancel, which has to reach the same result: the same request resolved
either way should leave the database in the same state.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ack-reminders

feat: body types, and Slack meeting reminders for committees (#95)
…done-applies-status

fix: marking a cancellation Done applies the outcome it asked for (#96)
This template did not move when the booking emails were rewritten in #79
and drifted from them.

It rendered its times straight out of the payload, so an alert read
"Time of Reservation: 18:30:00 to 20:00:00" where every other Chambers
email said "6:30 PM to 8:00 PM". Both shapes the codebase produces are
handled now -- Postgres returns HH:MM:SS and the booking forms submit
HH:MM.

It also took its date already formatted, via a second copy of the date
formatter living under a different name. That is probably why the raw
times survived so long: the one value the caller prepared looked right,
and the two it passed through did not. It takes raw values and formats
them here now, like every other template, and formatDateLong is gone --
it was character-for-character what changes.formatDate already did.

The subject follows the house style, an em dash rather than a hyphen.

Two smaller things while the file was open. The room or table is now
named, since the point of the alert is that a room went unused and
Operational Affairs would otherwise have to look it up. And a body with no
leadership on file reads "Contacts: None on file" rather than trailing off
after the colon.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two things were wrong with asking "is anything Missed".

It described the alert using the series' start date and time no matter
which week had actually been missed, so a series where week 7 was missed
told Operational Affairs about week 1. That is the same defect as #91, in
the one caller that rewrite did not reach.

And because a missed week stays Missed, the condition stayed true forever.
Every later edit to the series sent another alert about a week they had
been told about weeks earlier, with the wrong date on it.

movedOccurrences -- which #91 added to answer exactly this question -- is
what this save changed, so a week now alerts on the edit that missed it and
not again afterwards. A series-level Missed is compared against the stored
value for the same reason, so resubmitting an already-missed series does
not re-alert either.

One alert per missed reservation rather than one per save: each missed room
is its own incident to chase, and a single email naming one of several
would hide the rest. Occurrence values fall back to the series, so the
times and room are the ones that actually applied to the week missed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ation-email

fix: missed-reservation email — format its times, and alert on the week actually missed
@pataniaeli pataniaeli self-assigned this Sep 10, 2026
@pataniaeli pataniaeli added bug Something isn't working enhancement New feature or request labels Sep 10, 2026
@vercel

vercel Bot commented Sep 10, 2026

Copy link
Copy Markdown
Contributor

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
chambers Ready Ready Preview Sep 10, 2026 12:43am UTC

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment