A portfolio project by Chairul Akmal
A job tracker built on a finite state machine.
Thirteen states, an explicit transition table, and an immutable audit trail behind every change. Track an application from wishlist to offer without the pipeline ever lying to you about where it is.
Rails 8 · Next.js 16 · PostgreSQL 18
- Wishlist
- Draft
- Applied
- Phone screen
- Technical
- Final round
- Offer
- Accepted
- Rejected
- Ghosted
- Withdrawn
It is not a line. A job hunt goes quiet, or ends, and then starts again — so three closed states can return to Applied, and the audit trail keeps every step of the round trip.
Illustration. The transition table lives in api/app/lib/application_fsm.rb
01
An explicit transition table
Thirteen states and a hand-written list of the moves between them. ghosted may return to applied; applied may not jump to offer. It is a plain Ruby object, not a state-machine gem.
02
History that cannot be rewritten
Every transition appends a timeline entry — who moved it, from which state, to which, and when. Nothing in the app updates one in place.
03
Background jobs on Postgres
Follow-up reminders run on Solid Queue inside the Puma process. No Redis, no worker service. Each reminder is idempotent by key, so a retry cannot send it twice.
04
A board that reads the table
Drag a card across the Kanban board and it runs a real transition. The board fetches the transition table from the API instead of keeping a copy, so only the columns a card may legally reach light up — and the server checks again anyway.