A simple family dashboard. Aggregates Today / This Week events from Google Calendar (and later Wilma + MyClub) into one Material Design 3 view that works on phone, tablet, and desktop.
- SvelteKit + Svelte 5
- Tailwind CSS v4 with hand-rolled MD3 design tokens
- TypeScript, Vitest
pnpm install
cp .env.example .env # fill in Google credentials (see below)
pnpm dev # http://localhost:5173Other scripts:
pnpm check # type + svelte check
pnpm test # run unit tests
pnpm build # production build
pnpm preview # preview the build- In Google Cloud Console, create an OAuth 2.0 Client (type: Desktop).
- Visit https://developers.google.com/oauthplayground:
- Click the gear icon, tick Use your own OAuth credentials, paste your client ID and secret.
- Select scope
https://www.googleapis.com/auth/calendar.readonly. - Authorize, then exchange the auth code for a refresh token.
- Put the values into
.env:GOOGLE_CLIENT_ID=... GOOGLE_CLIENT_SECRET=... GOOGLE_REFRESH_TOKEN=... GOOGLE_CALENDAR_IDS=primary,family@group.calendar.google.com
- ✅ Phase 0 — App skeleton, MD3 tokens, responsive layout
- ✅ Phase 1 — Google Calendar (Today + Week)
- ✅ Phase 2 — Wilma (schedule, exams, homework, messages)
- 🗒️ Phase 3 — MyClub (planning only; stub in place)
- ⏳ Phase 4 — Polish: dark-mode toggle, filter chips, PWA, a11y pass
Wilma has no official API — we reverse-engineer the JSON the web UI uses, and
endpoints vary per school. Add to .env:
WILMA_BASE_URL=https://helsinki.inschool.fi
WILMA_USERNAME=your.parent.login
WILMA_PASSWORD=...
Then probe what your school actually returns:
pnpm wilma:probe # dumps login, homepage role links, account info
WILMA_PROBE_SLUG=<slug> pnpm wilma:probe # then probe child-scoped endpoints
pnpm wilma:probe -- --write wilma-out/ # optional: save raw responsesUse the shapes it prints to adjust the mappers in
src/lib/server/sources/wilma.ts if fields differ from the defaults.
src/app.css— MD3 color/shape/type tokens exposed via Tailwind@themesrc/lib/components/md3/— small Svelte MD3 primitivessrc/lib/server/sources/— per-source fetchers, all returningFamilyEvent[]src/lib/server/aggregate.ts— combine sources, 60s in-memory cachesrc/routes/api/events/+server.ts— JSON APIsrc/routes/+page.svelte— Todaysrc/routes/week/+page.svelte— Week agenda