Skip to content

feat: add pause/resume functionality for clock events and implement clock monitoring for breaks and auto clock-out#196

Merged
Dharp02 merged 17 commits into
mainfrom
should-long-clocks-shows-a-banner-until-acknowledged-or-resolved
May 21, 2026
Merged

feat: add pause/resume functionality for clock events and implement clock monitoring for breaks and auto clock-out#196
Dharp02 merged 17 commits into
mainfrom
should-long-clocks-shows-a-banner-until-acknowledged-or-resolved

Conversation

@Dharp02
Copy link
Copy Markdown
Collaborator

@Dharp02 Dharp02 commented May 19, 2026

This pull request introduces major improvements to the backend database schema documentation and migration logic for clock event breaks. The changes include a comprehensive, up-to-date database reference, a migration to normalize and later extract clock event breaks into their own collection, and supporting migrations to backfill and clean up related fields. These updates clarify the data model for developers, improve query performance, and enable more flexible future development.

Database Documentation

  • Added a new, detailed DATABASE.md as the single source of truth for the backend data model, including a mermaid diagram, field-level schema summaries, index catalog, and migration history. This will help all developers quickly understand and reason about the database structure.

Clock Event Breaks Model Refactor

  • Added migration to normalize clock event break and pause fields, ensuring all documents have the expected shape and new fields required for break tracking.
  • Added migration to backfill break type and classificationSource fields for embedded breaks, auto-classifying as "rest" or "meal" based on a 30-minute threshold.
  • Added migration to remove deprecated pause and break fields from all clockevents documents, cleaning up legacy state.
  • Added migration to extract embedded breaks arrays from clockevents into a new clockbreaks collection, with full up/down support for moving between embedded and referenced models.
  • Updated index creation logic in ensure-indexes.ts to add indexes for the new clockbreaks collection, optimizing for open-break lookups and ordered retrieval per event.

These changes modernize the time tracking schema, improve maintainability, and ensure the database documentation is always up to date.

Dharp02 and others added 3 commits May 18, 2026 16:48
…lock monitoring for breaks and auto clock-out

Co-authored-by: Copilot <copilot@github.com>
…dels, services, and tests

Co-authored-by: Copilot <copilot@github.com>
- Updated clock test cases to reflect changes in break segments to breaks.
- Added new tests for shrinking overlapping break periods and manual break edits.
- Enhanced TimesheetPage to manage editable breaks, including validation and rendering.
- Refactored TimesheetRow to build a timeline of work and break segments.
- Updated API to support breaks in clock event updates.
- Added unit tests for TimesheetRow to ensure correct rendering and functionality.

Co-authored-by: Copilot <copilot@github.com>
@Dharp02 Dharp02 self-assigned this May 19, 2026
@Dharp02 Dharp02 linked an issue May 19, 2026 that may be closed by this pull request
2 tasks
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR upgrades the clock/break system to support pause/resume (break intervals), adds status reporting and break-aware timesheet calculations, and introduces a background monitor that sends break reminders and auto clocks users out at an 8-hour cap.

Changes:

  • Added pause/resume + status APIs and client helpers, and extended the ClockEvent shape with break/paused metadata.
  • Updated timesheet UI and calculations to display and edit break intervals, including summary “Break Hours”.
  • Added a backend clock monitor service with periodic enforcement (3h/4h reminders, 8h auto clock-out), plus indexes and a migration for new fields.

Reviewed changes

Copilot reviewed 17 out of 17 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
src/lib/useClockToggle.ts Adds pause/resume actions and loading state around new clock endpoints.
src/lib/timeUtils.ts Updates active-session duration calculation to support accumulated time, pause state, and server-provided workSeconds.
src/lib/api.ts Extends ClockEvent type and adds pause/resume/status endpoints and break-aware updateTimes payload.
src/features/timers/WorkPage.tsx Disables timer controls while the user is on a clock break and shows “On Break” badge.
src/features/clock/TimesheetRow.tsx Refactors timesheet rows into a work/break timeline rendering model.
src/features/clock/TimesheetRow.test.tsx Adds unit tests validating work/break row rendering and edit action behavior.
src/features/clock/TimesheetPage.tsx Adds break editing UI, break-aware duration math, and “Break Hours” summary.
src/features/clock/ClockPage.tsx Adds Break/Resume button and paused-state labeling for the active session.
backend/tests/clock.test.ts Adds integration tests for pause/resume/status, monitor reminders, auto clock-out, and break edits.
backend/src/services/timer.service.ts Exposes helpers to close the running timer and fetch a timer session by id for break resume.
backend/src/services/clock.service.ts Implements pause/resume/status, break normalization, and break-aware timesheet summaries.
backend/src/services/clock-monitor.service.ts Adds periodic enforcement service (reminders + 8h auto clock-out).
backend/src/server.ts Starts the clock monitor in non-test environments during bootstrap.
backend/src/routes/clock.ts Adds /clock/pause, /clock/resume, and /clock/status endpoints + break payload support for updateTimes.
backend/src/models/clock.model.ts Extends ClockEvent model with break, pause, notification, and auto clock-out fields.
backend/src/lib/ensure-indexes.ts Adds new clockevents indexes to support monitor/enforcement queries.
backend/migrations/20260518_090000_add-clock-break-and-cap-fields.cjs Backfills new clockevents fields and sets originalStartTime for existing records.

Comment thread backend/src/services/clock.service.ts Outdated
const breaks = toBreakIntervals(raw["breaks"]);

const now = Date.now();
const workSeconds = getActiveWorkSeconds(e, now);
Comment thread backend/src/services/clock.service.ts Outdated
Comment on lines 309 to 332
const prev = event.accumulatedTime ?? 0;
const elapsed =
typeof event.pausedAt === "number" ? 0 : getElapsedSeconds(event.startTime, now);
const finalSeconds = Math.min(MAX_WORK_SECONDS_PER_DAY, prev + elapsed);

// Close all open timer sessions for this user in a single updateMany
await timerService.closeAllForUser(userId, now);

const $set: Record<string, unknown> = {
endTime: now,
accumulatedTime: prev + elapsed,
accumulatedTime: finalSeconds,
pausedAt: null,
pauseStartedSessionId: null,
breaks: (() => {
const breaks = toBreakIntervals((event as unknown as Record<string, unknown>)["breaks"]);
if (typeof event.pausedAt !== "number") return breaks;
const openIdx = breaks.findIndex((b) => b.endTime === null);
if (openIdx === -1) return breaks;
const next = breaks.slice();
next[openIdx] = { ...next[openIdx], endTime: now };
return next;
})(),
...(reason === "auto-8h" ? { autoClockedOutAt: now } : {}),
};
Comment on lines +84 to +91
const locked = await coll.updateOne(
{ _id: event._id, endTime: null, autoClockedOutAt: null },
{ $set: { autoClockedOutAt: now } }
);
if (locked.modifiedCount === 1) {
autoClockedOut += 1;
await clockService.stopWithReason(event.userId, event.teamId, "auto-8h");
}
Comment on lines +27 to +33
const coll = clockEventsCollection();
const activeEvents = await coll
.find({
endTime: null,
})
.toArray();

@mfisher31
Copy link
Copy Markdown
Collaborator

mfisher31 commented May 20, 2026

@Dharp02 - I do not approve this P.R. It over complicates and bloats the simple ClockEvent in/out model. Break tracking is a good idea, but a proper data model is in order: e.g. ClockEvent stays the same as an in/out ledger. Break tracking exists as it's own ledger as an overlay. Mathematics in time reporting stays clean while augmented by breaks.

Clean Approach (possible):
The break tracking embedded in ClockEvent (the breaks[] array, pausedAt, totalPausedSeconds, pauseStartedSessionId, originalStartTime) adds significant complexity to a model that should stay a simple shift record. A cleaner approach — and one that's actually more FLSA-compliant — is a separate BreakEvent collection keyed to clockEventId. Pause = insert a doc with endTime: null, resume = close it. This makes break records independently queryable, lets you tag type: "meal" | "rest" (which matters legally — rest breaks < 20 min are compensable under FLSA and must not be deducted from pay), and keeps accumulatedTime a simple derivation at clock-out time rather than a value that has to be kept in sync across every pause/resume transition.

It also has legal implications when capping/automating clocks: We need review/approval from @horner :

MAX_WORK_SECONDS_PER_DAY = 8 * 60 * 60 capping stored accumulatedTime is a compliance red flag — FLSA requires recording all hours actually worked. If someone works 10 hours and the record shows 8, that's the employer's liability, not the software protecting them.

Ok, so this changeset adds pause/resume functions and alot of complexicity to clock in/out. questions 1) is this overkill for a time clock, 2) is this kind of tracking going to be compliant with federal/state regulations in the United states?

Here's a direct assessment of both questions:


1. Is this overkill?

For a pure in/out time clock — yes, significantly. The changeset adds:

  • Pause/resume with full break interval tracking and interval-merging logic
  • originalStartTime vs. startTime split (resume resets startTime)
  • totalPausedSeconds, pauseStartedSessionId, linked timer session resumption
  • A hard 8-hour daily work cap applied at multiple layers
  • Auto clock-out + notification at 8h
  • 3h/4h alert fields (notifiedAt3h, notifiedAt4h)
  • A clock monitor service polling for long sessions

If the goal is just capturing shift start/end, this is 4–5x the necessary complexity. Pause/resume makes sense if unpaid meal breaks need to be tracked separately from work time — which is a legitimate compliance need — but the current implementation bundles a lot more on top of that.


2. US Regulatory Compliance

There are real compliance concerns with the current approach:

🚨 The 8-Hour Cap Is the Biggest Problem

const MAX_WORK_SECONDS_PER_DAY = 8 * 60 * 60;

This caps accumulatedTime and workSeconds at 8 hours system-wide, including in toPublicClockEvent. If an employee works 10 hours, the record shows 8. Under FLSA, employers must record all hours actually worked — capping the record is effectively wage theft for the unrecorded overtime. Auto-clocking out at 8h with a notification doesn't fix this if the underlying stored time is also capped.

Break Classification Isn't Distinguished

FLSA has a hard rule:

  • Short rest breaks (< 20 min) → compensable, must count as work time
  • Bona fide meal breaks (≥ 30 min, employee fully relieved) → non-compensable

The pause/resume implementation treats all breaks the same way — subtracting all paused time from accumulatedTime. Pausing for a 10-minute break and subtracting it from pay would violate federal law.

State Law Adds More Complexity

  • California: mandatory 30-min meal break after 5 hours. If not taken, employer owes a 1-hour premium. The system would need to enforce the break, not just track it.
  • California/Nevada: mandatory 10-min rest break per 4 hours of work — those must NOT be deducted from pay.
  • Several states require break records to be kept for 2–3 years.

@mfisher31 mfisher31 requested a review from horner May 20, 2026 10:43
@Dharp02
Copy link
Copy Markdown
Collaborator Author

Dharp02 commented May 20, 2026

@mfisher31 I’m still thinking through the architecture for this and trying to determine the cleanest long-term implementation approach.

Thanks for calling out the compliance concerns as well — especially around FLSA and break classification. My intention with the 8-hour handling was more around the “user forgot to clock out” scenario rather than restricting actual worked hours. Even within the 8-hour cap flow, the user could still clock back in afterward to continue tracking additional hours if needed, so I wasn’t intending to block overtime or hide worked time.

That said, I understand the concern with capping stored accumulated time itself. I can rework that behavior so the notification simply prompts the user to clock out instead of auto clocking them out automatically. The auto clock-out logic was mainly carried over because that behavior existed in the older TimeHarbor implementation that everyone had been using.

Dharp02 and others added 6 commits May 20, 2026 13:01
- Introduced ClockBreak type to manage break entries with metadata.
- Implemented auto-classification for breaks based on duration (rest vs meal).
- Updated compute functions to accurately calculate work and break seconds.
- Refactored clock service methods to utilize new break handling logic.
- Removed deprecated fields from clock events and added migration scripts for data consistency.
- Enhanced tests to cover new break classification logic and ensure accurate time calculations.
- Added AdminTimesheetPanel to display timesheet information in the TeamsPage.

Co-authored-by: Copilot <copilot@github.com>
… models

- Added `clockbreaks` collection to store break intervals as separate documents.
- Updated `ClockEvent` and `ClockBreak` interfaces to reflect the new structure.
- Modified database indexes to support efficient querying of breaks.
- Refactored clock service methods to handle breaks from the new collection.
- Updated API responses to include breaks in the new format.
- Adjusted migration scripts to extract existing breaks from `clockevents` to `clockbreaks`.
- Revised tests to accommodate changes in break handling and notifications.

Co-authored-by: Copilot <copilot@github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 25 out of 25 changed files in this pull request and generated 13 comments.

Comments suppressed due to low confidence (1)

backend/src/services/clock.service.ts:421

  • This insert creates an open break without any DB-level uniqueness guarantee. If two pause requests race, you can end up with multiple endTime: null breaks for the same clockEventId, breaking isPaused/accounting. Enforce one-open-break-per-event at the DB layer (unique partial index) or make the write atomic.
    await clockBreaksCollection().insertOne({
      _id: new ObjectId(),
      clockEventId: event._id.toHexString(),
      startTime: now,
      endTime: null,

Comment thread backend/DATABASE.md
Comment thread backend/migrations/20260521_000000_extract-breaks-to-collection.cjs
Comment thread backend/src/models/clock.model.ts Outdated
Comment thread src/lib/api.ts Outdated
Comment thread src/features/clock/ClockPage.tsx Outdated
Comment thread backend/src/lib/ensure-indexes.ts
Comment thread backend/migrations/20260520_090000_backfill-break-type.cjs Outdated
Comment thread backend/src/routes/clock.ts
Comment thread backend/src/services/clock-monitor.service.ts
Comment thread src/lib/timeUtils.ts Outdated
Dharp02 and others added 4 commits May 20, 2026 18:10
… refactor routes for cleaner structure

Co-authored-by: Copilot <copilot@github.com>
…ows-a-banner-until-acknowledged-or-resolved

# Conflicts:
#	src/features/teams/TeamsPage.tsx
@Dharp02 Dharp02 requested a review from mfisher31 May 20, 2026 23:13
Copy link
Copy Markdown
Collaborator

@mfisher31 mfisher31 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes needed. This version reverts/breaks display of clock sessions over the midnight boundary.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change on TimesheetRow.tsx breaks cross-midnight display over multiple days.

Cross midnight clock spanning multiple days/rows. Clock in 7:49pm, clock out around 2:49am (main)
Image

Confusing single row now showing the cross over (same clock record; this branch)

Image

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

... but otherwise, thanks for keeping breaks as their own little entity. Good data design. 👍

@Dharp02
Copy link
Copy Markdown
Collaborator Author

Dharp02 commented May 21, 2026

Requested Change made and tested.
Screenshot 2026-05-21 at 11 45 02 AM

@Dharp02 Dharp02 merged commit bbce740 into main May 21, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Should Long Clocks Should A Banner Until Acknowledged or Resolved?

3 participants