Aryan Kumar

chunk: shyftweb workmy product, live2025 to now

Shyft

A UK shift tracker that shows your real take-home pay after PAYE, National Insurance, student loan and pension. I built it solo. It is live on the web.

The Shyft landing page: "Know what you'll actually take home", with a payslip card showing £474.48 take-home.
The landing page at shyft.live. The payslip card is an example week, not anyone's real pay.Source: Screenshot of https://shyft.live at 1440 px, 16 Oct 2026, non-essential cookies declined
Role
Solo: design, code, iOS build
Timeline
Aug 2025 to now, 592 commits
Status
Live on the web, iOS in review
Stack
React, TypeScript, Supabase, Capacitor
Sources for these facts
  • Role: git shortlog, Shift-Tracker
  • Timeline: git log, Shift-Tracker: 7 Aug 2025 to 11 Oct 2026
  • Status: client/docs/app-store-submission.md:134
  • Stack: client/package.json

tl;dr

Shyft is my own product: log your shifts, see what you'll actually take home. Underneath is a tax engine I wrote to HMRC's payroll spec, in whole pence, and it matches all 1,000 entries of HMRC's Table A. The iOS app is built and waiting on Apple.

Source: client/src/lib/__tests__/hmrcTableA.test.ts; client/docs/app-store-submission.md

Payday, without the guessing

I designed Shyft for UK hourly and shift workers, mostly on phones. The promise on its front page is the whole product: log your shifts, and see your pay after UK tax and National Insurance before payday, not after.

There is a second audience too: managers running small teams, who need a rota, shift approvals and a payroll export.

Source: client/src/components/landing/DESIGN.md; client/public/og-image.png; client/docs/teams/README.md
The Shyft landing page on a phone, with a This Week card showing £551.00 gross.
The same landing page at phone width.Source: Screenshot of https://shyft.live at 390 px, 16 Oct 2026

From clock-in to payroll export

  • Shift logging and clock-in, with an iOS Live Activity while a shift is running.

    Source: client/src/components/shift/ClockInButton.tsx; client/src/hooks/clock/useShiftLiveActivity.ts
  • Take-home pay after PAYE, National Insurance, student loan (plans 1, 2, 4 and 5, plus postgraduate) and pension, with Scottish bands and K, BR and D tax codes.

    Source: client/docs/payroll-engine.md; client/src/lib/taxCalculator.ts
  • Weekly, fortnightly and monthly pay periods, overtime rules and a payday countdown.

    Source: client/src/lib/payPeriods.ts; client/src/lib/paydayCountdown.ts
  • Teams: a rota planner that warns about overlaps, short rest and long weeks (it warns, it never blocks), shift approvals, and payroll export to Excel and PDF.

    Source: client/src/lib/rotaConflicts.ts:1-24; client/src/lib/enhancedExcel.ts; client/src/lib/export/
  • Billing with Stripe on the web and in-app purchase through RevenueCat on iOS.

    Source: client/docs/platform-branching.md
  • A Supabase backend: Postgres with row-level security, 139 migrations and 15 edge functions.

    Source: client/supabase/migrations; client/supabase/functions (excluding _retired and _shared)
  • A free public take-home pay calculator. No account needed.

    Source: client/src/pages/TakeHomePayCalculator.tsx
The Shyft take-home pay calculator: hourly rate, hours and a monthly breakdown of £1,437.94 take-home.
The public take-home pay calculator on shyft.live. Free, and no sign-up.Source: Screenshot of https://shyft.live/take-home-pay-calculator, 16 Oct 2026

The hard part: right to the penny

Payroll looks like arithmetic until you try to match HMRC exactly. Two problems sit under everything else in Shyft.

Tax, done HMRC's way

HMRC publishes the rules payroll software must follow, down to where you round. I wrote Shyft's engine to that spec, known as PAYErout. One function works out free pay and K-code adjustments the way HMRC's routine does; another reproduces Table A, the lookup that turns a tax code into tax-free pay for each period.

Every amount is a whole number of pence. Money in floating point drifts (in JavaScript, 0.1 + 0.2 is 0.30000000000000004), and a payslip that is a penny out is simply wrong.

One wrinkle: a single shift has no statutory pay period. So the per-shift figures come from a separate allocator that spreads a period's deductions across its shifts at an effective rate.

in the code: calculatePeriodDeductions, hmrcPeriodicAdjustmentPence

Source: client/src/lib/taxCalculator.ts:235, :547, :666; client/docs/payroll-engine.md
Shyft share card: "Know what you'll actually take home." with an example week: £551.00 gross, £474.48 take-home.
The share card. Its week of 40 hours across two jobs is illustrative, as the card says.Source: Shift-Tracker client/public/og-image.png (my own asset); example figures are illustrative, as on the card

Night shifts across the clock change

A 22:00 to 06:00 shift is 8 hours, except on two nights a year. When the clocks go forward it is 7. When they go back it is 9.

Subtract the start from the end and you get 8 every time. Trust the phone's time zone and a worker abroad gets a different answer again. Shyft counts every shift as elapsed time on the UK wall clock (Europe/London), in one function that the shift maths, the rota and the clash checks all call.

in the code: elapsedShiftMinutes

Source: client/docs/payroll-engine.md, section Time; client/src/lib/shiftCalculations.ts
  1. A normal night

    On a normal night the shift is 8 hours.

  2. Clocks go forward, March

    On the night the clocks go forward, 01:00 to 02:00 never happens, so the shift is 7 hours.

  3. Clocks go back, October

    On the night the clocks go back, 01:00 to 02:00 happens twice, so the shift is 9 hours.

One 22:00 to 06:00 shift, counted in UK clock time. Each box is an hour, labelled by the time it starts.Source: client/docs/payroll-engine.md, section Time

Every HMRC Table A entry, tested

I have no crowd of users to quote, so the evidence is tests. These are the ones that guard the money.

1,000 of 1,000HMRC Table A entries

HMRC publishes 1,000 Week 1 and Month 1 entries for Table A. A test checks every one of them against the engine.

Source: client/src/lib/__tests__/hmrcTableA.test.ts
2,000cases in a property test

A seeded, repeatable run of 2,000 generated payslips. Each must come out in whole pence, with nothing negative, take-home never above gross, tax never above half the pay, and tax that never falls when pay rises.

Source: client/docs/payroll-engine.md
100%coverage gate

The coverage run fails if a single line goes untested in the pure modules on its list: the calculation code with no screens or network attached.

Source: client/vitest.config.ts:59-81
6 Apr 2027tax-year tripwire

Tax rates change every April. From this date a test fails CI until I have checked the new rates and updated the constants, so last year's tax can never ship quietly.

Source: client/src/lib/taxCalculator.ts:104 (TAX_YEAR.endsOn); client/docs/payroll-engine.md
254unit and component test files

Plus 20 Playwright end-to-end specs, and SQL tests that run in rolled-back transactions on a local copy of the production schema, so the row-level security tests cannot lie.

Source: find client/src -name "*.test.ts*"; client/e2e; client/supabase/tests/README.md

Stripe on the web, Apple on iOS, one codebase

Apple's App Review guideline 3.1.1 says digital purchases inside an iOS app go through Apple's in-app purchase. The web has no such rule. So Shyft takes money two ways: Stripe Checkout on the web, in-app purchase through RevenueCat on iOS.

I did not fork the app to do it. Hooks, stores and the tax engine are shared, and only the rendering layer branches on the platform. I wrote the branching rules down, so they stay in one place.

in the code: Capacitor.getPlatform()

What branches, and what does not
WebiOS
PaymentsStripe CheckoutIn-app purchase via RevenueCat
Sheets and dialogsRadix dialogsvaul drawers
PasskeysYesNo: the origin is pinned
Native extrasNoLive Activity, home-screen widget, HealthKit sleep
SharedHooks, stores, the tax engine and the Supabase backend

The price is plumbing: two payment webhooks, scheduled reconcile jobs, and one file of test vectors that runs against both the Postgres functions and the app, so the server and the client agree on who has paid access.

One rule I cannot break: the iOS app loads from shyft.live, and that origin is frozen, because changing it would sign out every iOS user. It is also why passkeys are web only.

Source: client/docs/platform-branching.md; client/supabase/functions/stripe-webhook, revenuecat-webhook; client/src/lib/entitlement.vectors.json; client/capacitor.config.ts:5-17; client/docs/passkeys.md

Known issues

  • iOS is still in review. The build went to App Store Connect in October 2026 and was still in review when I last checked, on 11 Oct 2026. Until it is approved, the web is the only way in. There is no iOS CI either: I upload builds by hand from Xcode.

    Source: git log, 7 to 11 Oct 2026; client/docs/app-store-submission.md:134; client/docs/ios-release.md:3
  • Too early for numbers. Shyft is very new. There are no usage figures on this page because there are none worth showing yet.

  • An estimate, on purpose. The engine keeps no year-to-date state and skips the personal allowance taper. Those are written down as decisions, and the app says plainly that your payslip is the final figure.

    Source: client/docs/payroll-engine.md, decisions of record; client/src/lib/estimateDisclaimer.ts

Where Shyft sits in the index

Shyft's chunks are ringed and numbered, with their neighbours dimmed around them.

web work1 Shyft2 HMRC 1,000/1,0003 clock-change maths4 592 commits
  1. 1Shyft: a UK shift and take-home pay app, on the web and in iOS review.source: Shift-Tracker README.md; client/docs/app-store-submission.md
  2. 2Shyft's tax engine matches all 1,000 entries of HMRC's Table A.source: client/src/lib/__tests__/hmrcTableA.test.ts
  3. 3Shyft counts shifts in UK clock time, so a 22:00 to 06:00 shift is 7 hours on the night the clocks go forward and 9 on the night they go back.source: client/docs/payroll-engine.md, section Time
  4. 4Shyft: 592 commits since August 2025, built solo.source: git log, Shift-Tracker

Query the full index