merge staging to production #58
No reviewers
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/Security
Kind/Testing
Priority
Critical
Priority
High
Priority
Low
Priority
Medium
Reviewed
Confirmed
Reviewed
Duplicate
Reviewed
Invalid
Reviewed
Won't Fix
Status
Abandoned
Status
Blocked
Status
Need More Info
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
cccb-website-team/www!58
Loading…
Reference in a new issue
No description provided.
Delete branch "staging"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
An occurrence of a recurring event that was changed on its own carries a RECURRENCE-ID and is stored as an additional VEVENT next to the event it belongs to. `getAllSubcomponents("vevent")` returns those components as well, and since they have no RRULE of their own they were handled as separate single events. The occurrence therefore ended up in the list twice: once from expanding the recurring event, which resolves the modified time through the exception, and once more from the extra VEVENT. To make it worse the two rows disagreed, because name and URL were taken from the recurring event while the time came from the modification, so the first row showed the new time under the old name. Skip components that are a recurrence exception, they are already covered by the event they modify, and take name and URL from the occurrence details, which point at the modification where there is one and at the event itself otherwise. Events whose RECURRENCE-ID refers to an event that is not in the file are dropped by this, which cannot happen in a full calendar export. With a recurring Plenum whose 25.08. occurrence is moved two hours earlier and renamed: before: 25.08. 18:00 CCCB Plenum 25.08. 18:00 CCCB Plenum (verschoben) after: 25.08. 18:00 CCCB Plenum (verschoben) Leaning on the resolution ical.js does here needs two more things to be right. The first is which modifications an event is asked to resolve. Unless it is told, `ICAL.Event` relates every VEVENT with a RECURRENCE-ID in the file to every recurring event and keys them by the recurrence id alone; the UID is only compared with `strictExceptions`, which then throws instead of skipping. A modification would therefore also override the occurrence another series holds at the same instant, so the wrong entry is shown and the modified one twice over. Group the modifications by the UID of the event they belong to and hand each event its own, which also turns the relating off for events that have none. Recognise a modification on the component instead of on the event, because relating exceptions to an exception throws. With two unrelated weekly series that both meet on Thursday at 19:00, of which only A has its occurrence of 03.09. moved to 17:00: before: 27.08. 19:00 Serie A after: 27.08. 19:00 Serie A 27.08. 19:00 Serie B 27.08. 19:00 Serie B 03.09. 17:00 Serie A (versch.) 03.09. 17:00 Serie A (versch.) 03.09. 17:00 Serie A (versch.) 03.09. 19:00 Serie B The second is how far the expansion has to run. It walks the unmodified recurrence times and stopped at the end of the window, but the occurrence handed to `getOccurrenceDetails()` may have been moved somewhere else entirely. Both directions were wrong: an occurrence pulled forward from beyond the window was never reached, because the walk had already stopped at its original time, and one pushed out of the window was still listed, because only its original time was ever compared against the window. Iterate far enough that the largest move towards the past can still reach the window, and decide by the time the occurrence really takes place at. The stop condition keeps using the raw recurrence time, which stays monotonic, so the iteration still terminates. With a weekly series whose occurrence of 05.10. is pulled forward to 25.08. and whose occurrence of 31.08. is pushed to 02.11., seen from 23.08. through the 20 day window: before: 24.08. Serie after: 24.08. Serie 31.08. Serie 25.08. Serie (vorgezogen) 07.09. Serie 07.09. Serie Reading the URL becomes a function of its own while name and URL move to the occurrence details, and it now looks at what it reads. The calendar is exported from a CalDAV server, so whoever may write to it decides what ends up in the href of the link, and `URL:javascript:alert(1)` on an event would run that script when a visitor clicks the entry. Pass on nothing but http and https; a value that is not a URL at all no longer reaches the href either. The published calendar contains no RECURRENCE-ID at all today, so nothing about the modifications changes for it. It is exported from a CalDAV server though, and moving a single Club Discordia or Plenum out of the way of a holiday is exactly what creates such a modification. Checked against https://berlin.ccc.de/calendars/all.ics: the list and the links of the 30 events that carry a URL are unchanged. Fixes:c28f04c6e8("switch to ics files; make calendars work; fix some minor issues") Assisted-by: Claude:claude-opus-5 Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>The response of the fetch went to the parser without ever looking at it. When the calendar could not be loaded the error page of the web server was parsed as a calendar, which threw inside a promise nobody was waiting on. The result was an empty table, an unhandled rejection in the console and an error message about broken calendar syntax that says nothing about the actual problem, a calendar that is not there. Refuse a response that is not ok, naming the status, and log failures in the chain, like the calendar page already does. before: Uncaught (in promise) Error: invalid line (no token ";" or ":") "<html>404 Not Found</html>" after: Fehler beim Laden der Termine: Error: /calendars/all.ics: 404 Not Found The table stays empty either way, there is nothing to show without a calendar. Fixes:c28f04c6e8("switch to ics files; make calendars work; fix some minor issues") Assisted-by: Claude:claude-opus-5 Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>The calendar page brought its own ICS parser and its own RRULE expansion. Both only covered the cases that happened to be needed when they were written, and the calendar has moved on since. Against the published calendar, for September 2026: Spieleabend FREQ=WEEKLY;INTERVAL=2;BYDAY=SA shown 05. 12. 19. 26., correct 05. 19. CCCB Plenum FREQ=MONTHLY;BYDAY=TU;BYSETPOS=2 shown 01., correct 08. CCCB Plenum FREQ=MONTHLY;BYDAY=TU;BYSETPOS=4 shown 01., correct 22. INTERVAL was only read for monthly rules, so the Spieleabend was shown twice as often as it takes place. BYSETPOS was not implemented at all, and since parseInt("TU") is NaN the fallback turned both Plenum rules into "first Tuesday", putting two Plenums on a day without one and none on the two days with one. UNTIL, COUNT, EXDATE, RECURRENCE-ID, BYMONTHDAY and a BYDAY listing more than one weekday were not handled either. The text was no better. Content lines longer than 75 characters are continued on the next line, of which there are 477 in the calendar, and the parser did not join them, so it cut values off in the middle of a word. It also split every line at the first colon, which lands inside the parameter of DESCRIPTION;ALTREP="data:text/html,...". And it never resolved the escaping, so "\n" was shown as those two characters. 30 of 31 descriptions were wrong: before: "Der Club Discordia ist ein öffentliches Treffen in den Clubr" after: "Der Club Discordia ist ein öffentliches Treffen in den Clubräumen des CCC Berlin" Hand the parsing and the expansion to ical.js, which is vendored for the start page anyway. The month view now asks the library for the occurrences that touch the month, which removes the reimplementation along with all of the above. While the events are being reduced to what the view needs: - An event is entered on every day it covers, so the Amateurfunk trip from 30.10. to 01.11. is no longer marked on 30.10. alone. The end of an event is not part of it, so one ending at midnight stays on the day before. - A time that names a zone is converted to Europe/Berlin instead of being read off the digits of the ICS string. Every event currently carries TZID=Europe/Berlin, so the wall clock time shown does not change, but a UTC timestamp would have been shown in UTC. A date is a different matter, see further down. - The URL of the event is used for the link in the detail panel. Events without one are shown without a link, as on the start page. This replaces createEventLink(), which guessed URLs from the title and was never called, and the panel no longer builds an <h> element, which is not an element. Descriptions may contain line breaks, so keep them in the panel. The times of an event are labelled in German like the rest of the page, "Beginn" and "Ende" instead of "Start" and "End". An all day event carries a date, and a date has neither a time nor a zone: its digits are the day itself. `toJSDate()` reads them as midnight in the zone of the browser, and deriving the Berlin day from that afterwards moves the event by the offset between the two, so the promise of the same days everywhere would have held for every event except the ones that consist of nothing but days. An event on 30. and 31.08. would have been marked on: Berlin 30. 31.08. Los Angeles 30. 31.08. 01.09. Tokio 29. 30. 31.08. Take the day from the ICAL time, which still knows whether it names a day or a point in time, and convert only the latter. Stepping to the end of the event moves by a day where it is made of days and by a second where it is not, which also expresses "the end is not part of the event" in the terms of the event itself. The occurrences of a day are sorted by their start, which for an all day event is that same midnight, so order them before the timed events instead. Resolving an occurrence that was modified on its own needs two more things to be right. Unless it is told which modifications belong to an event, `ICAL.Event` relates every VEVENT with a RECURRENCE-ID in the file to every recurring event and keys them by the recurrence id alone; the UID is only compared with `strictExceptions`, which then throws instead of skipping. A modification would therefore also override the occurrence another series holds at the same instant, so the detail panel of that day would show the wrong event and the modified one twice. Group the modifications by the UID of the event they belong to and hand each event its own. And the expansion walks the unmodified recurrence times, so an occurrence pulled forward into the month from a later one would never be reached: the walk stops at its original time, and the month it was moved out of drops it because it no longer falls into it, which loses it from the calendar altogether. Iterate far enough that the largest move towards the past can still reach the month. Moving a Plenum or a Club Discordia to the week before, out of the way of a holiday, is exactly what produces such a modification. With a weekly series whose occurrence of 07.09. is moved to 28.08.: before: August 03. 10. 17. 24. 31. September 14. 21. 28. after: August 03. 10. 17. 24. 28. 31. September 14. 21. 28. The URL ends up in the href of a link and the calendar is exported from a CalDAV server, so whoever may write to it decides what that is; `URL:javascript:alert(1)` on an event would run that script when a visitor clicks the name. Pass on nothing but http and https. The stylesheet of the page goes through `minify | fingerprint` while the script next to it is rewritten, so both are delivered the way the assets of the start page already are: smaller, under a name that carries their content hash, and with an integrity hash in the tag. The published calendar has neither all day events nor RECURRENCE-ID today, both can be created in the CalDAV calendar the export comes from. Checked in Berlin, Tokyo, Los Angeles and Kiritimati: an all day event over 30. and 31.08. is marked on those two days in all four, one from 31.08. to 02.09. is marked across the month boundary, and the month view of the published calendar is the same in all of them. Fixes:4068fab565("improved calendar and fixed url temporarily") Assisted-by: Claude:claude-opus-5 Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>The two columns of the table on the start page touched each other, so the entry read "Donnerstag, 27.08., 19:00 UhrClub Discordia". The table is created with the classes "table table-condensed", which no stylesheet of the site defines, and the table styling that the theme applies inside prose addresses "tbody td". The table is delivered empty and its rows are added through the DOM, where a tr appended to a table stays a direct child instead of being put into a tbody the way the HTML parser would. The rows are therefore outside of any tbody and the padding of the theme never applied. Give the column holding the date its own padding, and keep the date on one line, it is one piece of information and reads badly broken after the weekday. The padding alone does not fit, though. The table stands in a prose column that the theme limits to 65 characters so that running text stays readable, and a date and the name of an event next to each other are wider than that, so the names would be wrapped over several lines. Lift the limit off the column and put it back on everything in it except the table, which leaves the table room to grow while the heading and the paragraph around it keep their width. That much space then has to be filled sensibly. The theme lays a table out as a block, "table { display: block; overflow: auto }", so that a wide one can be scrolled sideways, and a block fills its parent instead of shrinking to its content the way a table does. Spanning the page the entries would all sit at its left edge. Ask for the width of the content with fit-content, which the automatic margins then centre. Addressing the table by its id keeps all of this to the start page and takes precedence over the theme, whose prose rules are written with :where() and carry no specificity. Measured in a browser at 1280, 768 and 500 pixels: date and name share one line at the first two, the table is 602 pixels wide with the same distance left and right, and at 500 the column is narrower than the table, so the table fills it and only the longest name wraps. The page never scrolls sideways. Fixes:c28f04c6e8("switch to ics files; make calendars work; fix some minor issues") Assisted-by: Claude:claude-opus-5 Signed-off-by: Hauke Mehrtens <hauke@hauke-m.de>