[portfolio](https://geonwoo.kim/en/portfolio) › Tools and open source

# offon · open-source Slack app for attendance and leave

https://geonwoo.kim/en/portfolio/offon-oss

Period: 2026.04 — 2026.08

When I joined Lukuku, attendance was not being tracked properly, so I built a system for it after work hours. That system later grew into the company-wide work management system, now with the company ontology attached. I split off only the attendance and leave features into offon, an open-source Slack app.

Split off from the company work management system, MIT-licensed and self-hosted

- [github](https://github.com/takealook97/offon)

Team: Solo

Role: Design · Full-stack development

Next.js 16 · React 19 · TypeScript · Tailwind CSS 4 · shadcn/ui · PostgreSQL · Prisma · Slack Web API · Vercel Cron

- Shifts past midnight, several sessions a day, and automatic work, break and overtime totals
- 60-minute meal breaks that set their end time at the start and send a return reminder
- Full-day and half-day leave excluding weekends and holidays, with remaining days tracked
- Attendance correction requests from the calendar, with times recalculated on approval
- One admin inbox for leave and correction approvals
- Slack one-time-code login, 5 slash commands and reminders for missed clock-ins and clock-outs

## Clock-ins arriving at the same moment

Problem: A Slack retry or a double click could send two clock-ins at almost the same time, and both passed the pre-check, leaving two open sessions.

Solution: A PostgreSQL partial unique index allows one open session per attendance record. The second request gets the same already-working response as the pre-check.

## Records made after a correction request

Problem: If someone clocked out or took another break after requesting a correction, approval could overwrite those records.

Solution: Approval compares the value at request time, the requested value and the current value. Fields the request did not touch keep later records, and a field changed differently on both sides is treated as a conflict.

## Time zones on the server and locally

Problem: Handling dates as-is on a UTC server and a Korean-time laptop could add the 9-hour offset twice or pick the wrong weekday.

Solution: Times are read in Korean time and stored in UTC. Displaying times in Korean time and finding the weekday from a date are separate functions. The same time and weekday appear regardless of where the code runs.

## Slack retries

Problem: Slack resends a command when the reply is slow. Processing it again could create the same record and notification twice.

Solution: The handler first checks the retry marker on the request and, for a retry, replies 200 at once without processing it. A slow reply no longer sends the same notification twice.


[vat · multi-repository workspace CLI](https://geonwoo.kim/en/portfolio/vat) · [sshift · SSH CLI with jump-server connections and password storage](https://geonwoo.kim/en/portfolio/sshift)
