Workshop Kit · widget reference
Shippedrhythm-calendar
The last widget in the bootcamp. Month-view calendar with recurring task pins (hours / reviews / regenerate / SEO) and a real .ics export that drops four RRULE'd recurring events straight into Google Calendar, Apple Calendar, or Outlook. The .ics is generated entirely client-side from MuntinContext.rhythmCadence — Blob URL download, no fetch, no server.
Live demo
Change a cadence — the month grid below repopulates with pins on the days that cadence lands. Click Download calendar file to get the .ics. Open it in your calendar app; the recurring events go live immediately.
No config block — four tasks + the cadence dropdowns are baked in
Contract
| Behavior | Notes |
|---|---|
Reads rhythmCadence |
Pre-fills the four cadence dropdowns. Defaults to hours=monthly, reviews=biweekly, regen=monthly, seo=quarterly when not set. |
Reads restaurantProfile.name |
Appended to the SUMMARY of each .ics event so an operator with multiple calendars can tell which restaurant the event belongs to. |
Writes rhythmCadence |
{ hours, reviews, regen, seo } with each value one of: 'off', 'weekly', 'biweekly', 'monthly', 'quarterly'. |
| Legacy string handling | The L16 lesson previously stored rhythmCadence as a free-text string (operator-typed). On first mount, the widget ignores anything that's not an object and writes the structured object on the next change — operators don't lose progress, but the schema upgrades. |
| .ics export | Generated client-side using the VCALENDAR/VEVENT/RRULE specification. First event lands on the upcoming Monday at 9 AM local time; recurrences follow the FREQ rule. Filename: muntin-rhythm.ics. |
| No fetch | Blob URL + document.createElement('a').click(). No third-party service, no server endpoint. The file lives in the operator's Downloads folder; the calendar app opens it directly. |
Markup
No config block. The four tasks + their default cadences are baked into the widget module.
What the .ics file contains
One BEGIN:VEVENT block per task whose cadence isn't "off":
30-minute slots starting 9 AM local on the first Monday after the download. Google Calendar, Apple Calendar, and Outlook all import this format directly via "open with" or drag-and-drop.
Accessibility
- Each cadence picker is a labeled
<select>— keyboard-reachable, screen-reader-announced. - The month grid is a real
<table>with<th scope="col">day-of-week headers — screen readers navigate by column / row naturally. - Task pins inside each cell are visible text labels ("Hours", "Reviews") with a colored background — color reinforces the label, doesn't replace it.
- "Today" cell is outlined in addition to being colored, so high-contrast modes still flag it.
- Download button announces "Calendar file downloaded" via a polite live region after the download triggers.
Why .ics and not a paid scheduling API
Calendar integrations could route through Google Calendar's API or Cal.com's webhooks, both of which would require auth flows + server-side credentials. The .ics format is the open standard those services themselves build on — every calendar app on every platform has imported .ics for decades. Generating it client-side and downloading a file preserves the suite's "no fetch, no account" posture while still delivering a real recurring-event setup. The operator's rhythm goes live on their phone the moment they tap the file.
Where it ships
Source
/tools/_shared/workshop/rhythm-calendar.js — ~290 LOC. Exports { tag, contextKeys, mount, serialize }. The TASKS array is the four-task contract; RRULE is the cadence-id → FREQ-rule mapping; buildICS() generates the VCALENDAR file; nthCadenceDays() computes which day-of-month each cadence lands on for the pin overlay.