wheel-planner
Interaktiv årshjul-planlegger – en radial tidslinje der aktiviteter og milepæler dras og justeres direkte på hjulet. React, SVG og en ren geometrimodul, med et lite Express- og SQLite-API.
- Kontekst
- Privat
- Periode
- sep. 2026 – pågår
Bakgrunn
Et årshjul er en radial tidslinje over ett kalenderår: måneder ytterst og aktiviteter som buer på ringer innover. Jeg ville se hvor langt jeg kom med direkte manipulasjon som hovedgrep – du skal kunne ta tak i en aktivitet på selve hjulet og dra den, ikke bare fylle ut et skjema.
Designspesifikasjonen i repoet slår uttrykkelig fast direkte manipulasjon foran en tryggere, skjemabasert første versjon, med en bevisst enkel backend.
Hva jeg bygde
- Hjulet. SVG-rendering av en månedsring, en markør for dagens dato og én ring per kategori. Aktiviteter vises som buer og milepæler som punktmarkører.
- Dra for å redigere. Flytt en hel aktivitet, dra i håndtakene i hver ende for å endre start eller slutt, eller flytt en milepæl. Datoene oppdateres mens du drar og vises i en tooltip.
- Gjentakende aktiviteter. Daglig, ukentlig, månedlig eller årlig regel med intervall og valgfri sluttdato, utfoldet til forekomster for det valgte året.
- Banepakking. Aktiviteter som overlapper i samme ring legges automatisk i hver sin bane.
- Ringer. Legg til, fjern og endre rekkefølge på kategoriringer ved å dra dem i en liste i sidepanelet.
- Sidepanel med skjema og liste for å opprette, endre og slette aktiviteter og milepæler. Dessuten zoom og panorering på lerretet, tomtilstander og feilmeldinger når API-et svikter. Brukergrensesnittet er på norsk.
Status og begrensninger. Dette er en fungerende enbrukerprototype, og README-en sier det rett ut: det finnes ikke
noe brukergrensesnitt for å opprette eller velge hjul ennå – appen lager ett hjul per nettleser (for 2026) ved første
oppstart og husker id-en i localStorage. Det er ingen innlogging, ingen flerbruker, ingen visning av flere år, ingen
eksport og ingen angre/gjør om. Dette er uttrykkelig utenfor omfanget i designspesifikasjonen.
Arkitektur
Det viktigste valget er geometry/: rene funksjoner uten React eller DOM for dato↔vinkel, banepakking og utfolding av
gjentakelser. Komponentene inneholder bare pekerhåndtering, og all datomatte går gjennom de samme funksjonene. Det
gjør de subtile delene lette å teste isolert. Drag bruker lokal forhåndsvisningstilstand mens du drar, og lagres i storen først
når du slipper. Banene regnes ut på klienten, lagres i databasen og regnes på nytt når en aktivitet endres.
Teknologi
React 19, TypeScript og Vite, med Zustand for tilstand, Tailwind CSS 4 og shadcn/ui. Hjulet er håndskrevet SVG med
d3-shape og date-fns; @use-gesture/react håndterer drag, zoom og panorering, @dnd-kit ringrekkefølgen og
Floating UI tooltips. API-et er Express 5 med better-sqlite3 og rå SQL. Monorepo med pnpm workspaces og just-oppskrifter.
Det er 58 testtilfeller i 15 filer (Vitest, Testing Library og Supertest) som dekker geometri, store, API og
sentrale komponenter. Selve dra-følelsen testes ikke automatisk i nettleser; spesifikasjonen sier at den verifiseres manuelt.
Hva jeg lærte
- Skille geometri fra rendering. Å holde dato↔vinkel, banepakking og gjentakelse i rene funksjoner var det som gjorde resten håndterbart. Feil her er subtile og vanskelige å se visuelt.
- Datoer er vanskeligere enn de ser ut. Koden har en egen konverteringsfunksjon fordi
toISOString()kan flytte en dato en dag i tidssoner foran UTC og over sommertidsskifter. - Direkte manipulasjon koster. Lokal tilstand under drag, snapping til hele dager og en gjentakende serie som én rad (der drag flytter hele serien) er valg jeg måtte ta for at det skulle føles riktig.
- Avgrensning er en egenskap. README-en og spesifikasjonen sier tydelig hva prototypen ikke er.