T22: calendar week view + fix workerd fetch binding bug #21

Merged
dzenda merged 38 commits from t22-calendar into t21-mcp-setup 2026-11-14 17:49:50 +01:00
Owner

T22: Calendar screen + fix for a production bug in createFnContext

Stacked on #20 (t21-mcp-setup) → … → #1. Merge those first.

⚠️ Production bug found and fixed

createFnContext stored the global fetch on the context object, and handlers called it as ctx.fetch(…). In workerd that throws TypeError: Illegal invocation, so every registry call inside the Worker failed. That includes every MCP call tool invocation, and it surfaced as a generic internal_error. Node-based tests can't see it. I found it while smoke-testing the calendar with a real session.

The fix is a wrapper ((input, init) => fetch(input, init)), plus a regression test that emulates workerd's this check; it fails without the fix. Astra then audited the codebase: no other stored host-object calls.

Calendar (/calendar)

  • Week view Mon–Sun (?week=YYYY-MM-DD, any day maps to its Monday) with Previous / Today / Next. Days are columns on desktop and a stacked list on mobile, and today is highlighted (aria-current="date").
  • Classes: time (Europe/Prague), course code (or the event name when there's no course; the course name shows on hover), a type with a colour stripe (lecture, tutorial, lab, exam, assessment), the room, and cancelled (struck through) or moved markers.
  • Deadline overlay: Marast quizzes due that day ("Due 23:59 · …"). Exams and assessments already come from the timetable.
  • iCal subscription: "Get subscription link" calls a POST server function only on click, so the private calendar token never sits in the page HTML or SSR data. It warns that the link is private, and network errors are handled.
  • Warnings are localised from source + code (en/cs). Raw registry text is never shown. A Sirius truncation warning keeps days from wrongly reading "Nothing scheduled".
  • Data comes from the same registry functions as the MCP tools (fit.sirius.events, fit.deadlines), run as the signed-in user.
  • Nav "Calendar" and an overview card. src/lib/calendar.ts is pure and tested: mondayOf, weekDays, parseWeek, pragueTime, groupByDay.
  • Month view isn't included. The week view plus the iCal subscription cover SPEC §8's need, and a month grid can come later if wanted.

Verified

  • bun run lint && bun run typecheck && bun run test: 251 tests. The 5 new calendar-helper tests cover Mondays across years, ?week parsing including invalid input, Prague time across DST, and grouping by Prague day. There are 2 createFnContext tests, one of them the workerd regression test.
  • Live, signed in (a request cookie for the local dev session): /calendar?week=2026-11-16 renders 10 events (lecture, tutorial, assessment) and 3 Marast due items in en and cs, with no warnings. Before the fix it showed "Internal error". /, /connectors and /assistants render signed in.

Astra review (gpt-6-astra)

It audited host-object usage (no other issues) and found the Prague date maths, timezone formatting and on-demand iCal token sound.

Fixed:

  1. (P2) A transport failure on the iCal request left the button disabled → caught, with a localised error and retry.
  2. (P2) Events without a course showed no name → the name is shown.
  3. (P2) Warnings showed English registry text in Czech → localised source/code messages.
  4. (P2) The Sirius truncated flag was dropped → warning.

🤖 Generated with Claude Code

## T22: Calendar screen + fix for a production bug in `createFnContext` **Stacked on #20 (`t21-mcp-setup`) → … → #1.** Merge those first. ### ⚠️ Production bug found and fixed `createFnContext` stored the global `fetch` on the context object, and handlers called it as `ctx.fetch(…)`. **In workerd that throws `TypeError: Illegal invocation`**, so every registry call inside the Worker failed. That includes **every MCP `call` tool invocation**, and it surfaced as a generic `internal_error`. Node-based tests can't see it. I found it while smoke-testing the calendar with a real session. The fix is a wrapper (`(input, init) => fetch(input, init)`), plus a regression test that emulates workerd's `this` check; it fails without the fix. Astra then audited the codebase: **no other** stored host-object calls. ### Calendar (`/calendar`) - **Week view Mon–Sun** (`?week=YYYY-MM-DD`, any day maps to its Monday) with Previous / Today / Next. Days are columns on desktop and a stacked list on mobile, and today is highlighted (`aria-current="date"`). - **Classes:** time (Europe/Prague), course code (or the event name when there's no course; the course name shows on hover), a type with a colour stripe (lecture, tutorial, lab, exam, assessment), the room, and **cancelled** (struck through) or **moved** markers. - **Deadline overlay:** Marast quizzes due that day ("Due 23:59 · …"). Exams and assessments already come from the timetable. - **iCal subscription:** "Get subscription link" calls a POST server function only on click, so **the private calendar token never sits in the page HTML or SSR data**. It warns that the link is private, and network errors are handled. - **Warnings** are localised from source + code (en/cs). Raw registry text is never shown. A Sirius truncation warning keeps days from wrongly reading "Nothing scheduled". - Data comes from the same registry functions as the MCP tools (`fit.sirius.events`, `fit.deadlines`), run as the signed-in user. - Nav "Calendar" and an overview card. `src/lib/calendar.ts` is pure and tested: `mondayOf`, `weekDays`, `parseWeek`, `pragueTime`, `groupByDay`. - Month view isn't included. The week view plus the iCal subscription cover SPEC §8's need, and a month grid can come later if wanted. ### Verified - `bun run lint && bun run typecheck && bun run test`: 251 tests. The 5 new calendar-helper tests cover Mondays across years, `?week` parsing including invalid input, Prague time across DST, and grouping by Prague day. There are 2 `createFnContext` tests, one of them the workerd regression test. - **Live, signed in** (a request cookie for the local dev session): `/calendar?week=2026-11-16` renders **10 events (lecture, tutorial, assessment) and 3 Marast due items** in en and cs, with no warnings. Before the fix it showed "Internal error". `/`, `/connectors` and `/assistants` render signed in. ### Astra review (gpt-6-astra) It audited host-object usage (no other issues) and found the Prague date maths, timezone formatting and on-demand iCal token sound. **Fixed:** 1. *(P2)* A transport failure on the iCal request left the button disabled → caught, with a localised error and retry. 2. *(P2)* Events without a course showed no name → the name is shown. 3. *(P2)* Warnings showed English registry text in Czech → localised source/code messages. 4. *(P2)* The Sirius `truncated` flag was dropped → warning. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Also fixes a production bug found while smoke-testing: createFnContext stored the global
fetch on the context object, so ctx.fetch(...) threw "Illegal invocation" in workerd. That
broke every registry call in the Worker (including all MCP tool calls); Node tests could
not see it. Regression test emulates workerd's this-check.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ALLOWED_USERS enforced for MCP too (shared policy); excluded users' grants revoked on use
- telemetry: streaming 512-byte cap before parsing
- POST /oauth/register rate-limited per IP and instance-wide (Workers rate-limit bindings)
- FIT API bodies capped at 2 MB (streaming); Grades tree walk iterative and bounded
- readBounded moved to src/lib/bounded.ts

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Logos: FIT service marks vendored in public/services (see README there); assistant marks
from @lobehub/icons (MIT), simple-icons (CC0) and devicon (MIT).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- service_settings table (migration 0002); MCP calls to a switched-off service fail with
  service_disabled, aggregates skip it with a warning, fit.status lists it
- setServiceEnabledFn: same-origin + session only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- fit.sirius.courseEvents: every parallel of a course (room, teachers, capacity/occupied),
  shared paging helper with fit.sirius.events; respects the Sirius MCP switch
- getCourseWeek server fn for the calendar overlay; upcoming deadlines (60 days) beside the week

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- layoutLanes/pragueMinutes in src/lib/calendar.ts (tested)
- shared EventDetails popover; agenda list stays on mobile
- agentSetupPrompt in src/mcp/setup.ts (tested)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Referrer-Policy: same-origin (no-referrer made browsers send Origin: null on our form POSTs,
  so every management form was rejected with 403); shared isSameOriginRequest accepts
  Origin: null only with Sec-Fetch-Site: same-origin
- No COOP on OAuth/consent/login routes (popup-based web MCP clients keep window.opener)
- fit.sirius.icalUrl is websiteOnly: excluded from the MCP registry (URL embeds a credential)
- docs: telemetry wording (operator-enabled, consent advice), CSRF/COOP/credential notes

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reviewed-on: #25
Reviewed-on: #24
Reviewed-on: #23
Reviewed-on: #22
dzenda merged commit 96394d6675 into t21-mcp-setup 2026-11-14 17:49:50 +01:00
Sign in to join this conversation.
No reviewers
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
dzenda/os!21
No description provided.