Register
Entered
2026-09-29
First commit
2026-09-30

Séance

App · training · installable, offline

A training manual that works offline and remembers everything, on the device alone.

A bodyweight training programme drawn as a gymnastics manual, one plate per movement. It guides a thirty-minute session, keeps a journal and draws the progress curves — installable, offline, with reminders, and with every number kept on the device.

  • TypeScript
  • React
  • PWA
  • Workbox
  • IndexedDB
  • Vitest
Open live (opens in a new tab)Source (opens in a new tab)
Shipped29 · IX · 2026
Drawing: a phone showing one movement plate of a session, marked offline and saved on this devicePLATE · MOVEMENTPush-upSetRestNEXT MOVEMENTOFFLINEevery route answersON THIS DEVICEevery number, nowhere elseJOINTS, NOT PATHS37 figures, chainedfrom the hip
Drawn for this register, not a screenshot
Entered
2026-09-29Shipped
First commit
2026-09-30Séance, a bodyweight training PWA drawn as a gymnastics manual
Commits
4on main, read 2026-10-02
Coverage
14%lines run by the app’s unit tests, read 2026-10-02
House packages
10 of 11installed from npm

What shipped

  1. Installable and fully offline: a hand-written Workbox service worker precaches the whole shell, and every route is served with the network cut, mid-session included.
  2. Reminders without a server: an in-app clock, Notification Triggers where they exist, and Periodic Background Sync reading the schedule from IndexedDB in the worker — and the settings screen says plainly what this browser can do.
  3. Progress charts drawn by hand in SVG and HTML: regularity counted in weeks, volume per week, waist and weight never on a shared axis, each walkable with an invisible native range input and backed by a table.
  4. Every number stays on the device: a JSON backup carries it elsewhere, and a seeded specimen profile shows the charts full without ever touching the reader’s own data.
  5. Thirty-seven animated figures drawn from joints, not paths: interpolation is polar and chained from the hip, so no bone stretches mid-movement.

How it works

zero server, everything cached on the device

Diagram: inside the device, the app shell is precached by a service worker and every number lives in IndexedDB; the network is cut and there is no serverThis deviceApp shellprecachedService workerWorkbox · remindersIndexedDBevery numberNo servernetwork cutJSON backupthe only way out

As the compiler reads it

Set the way an editor shows it: a wavy line is a build that refuses, a violet line is what the compiler works out on its own.

  1. reminder-schedule.ts
    Compiles0 errors
    export const isReminderOwed = ({
      lastSessionDay, lastShownDay, now, schedule, today
    }) =>
      schedule.isEnabled &&
      schedule.days.some((day) => day === now.getDay()) &&
      now.getTime() >= reminderOn(now, schedule.time).getTime() &&
      lastShownDay !== today &&
      lastSessionDay !== today
    

History

Straight from the git log, abridged: 4 of 4 commits, newest first.

  1. Deploy the public demo from CI on every push to main
  2. Deploy the public demo to Cloudflare Pages from a clean clone
  3. Deploy the private build to Netlify on push to main, drop Cloudflare Pages
  4. Séance, a bodyweight training PWA drawn as a gymnastics manualFirst commit

Installs

10 of the 11 house packages, from npm, down to the compiler and linter settings.

Your team, 2026

I am looking for my next job. Write to me, or take the CV: a designed one, and a plain one for applicant tracking systems.