v1.14.1 - #103
Merged
Merged
Conversation
…#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>
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
Contributor
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This was
linked to
issues
Sep 10, 2026
Closed
Closed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.