BookYourPTO

Changelog

Stay up to date with the latest changes, improvements, and bug fixes in BookYourPTO.

v1.0.12

Onboarding & Offboarding Documents

A focused release on the document side of the onboarding/offboarding lifecycle. Templates can now be scoped to multiple departments at once; per-user copies of onboarding documents are generated from a single canonical template (closing a long-standing signature edge case where multiple new hires could overwrite each other's signature on the same file); read-only documents (employee handbook, code of conduct, exit-policy reads) automatically mark the linked task complete the moment they're read; and the global sidebar gains an amber "Action Required" badge so employees see at a glance how many docs they still owe.

New Features — Document Templates

  • Multi-department scope — A template can now be scoped to multiple departments at once instead of just one. Existing single-department templates were migrated automatically, so every previously scoped template kept its scoping intact. Department heads see a template if any linked department is theirs; administrators and executives see all.
  • Edit department scope from the template list/documents/templates rows now have a settings-cog menu next to the rename action that opens an Edit Template modal. Name and multi-select department scope live in one form. Department heads are locked to their own department in the picker, and the server re-validates on save so a tampered request can't escalate. Department updates are applied atomically — an interrupted save can't leave a template half-scoped.
  • Pill-segmented Active / Archived tabs — The Active and Archived rails on the templates page were redesigned as a pill-segmented control matching the audit-logs styling (constrained width, rounded selection background) instead of the old underline tabs.
  • Archive and restore — Templates can now be archived (hidden from the active picker without deleting the row) and restored. Archive preserves the template's history — the docs already generated from it are unaffected.
  • Multi-select department picker on create/documents/templates/create replaces the single department dropdown with a multi-select checkbox grid. Department heads see only their own department in the picker.
  • Templates redirect renders cleanly for employees — Employees who land on /documents/templates are now redirected to /documents cleanly, replacing the previous redirect that left a blank white screen until the user manually refreshed.

New Features — Per-User Document Generation

  • Per-employee copy of every onboarding document — When an onboarding or offboarding task with a doc attachment first surfaces for an employee, the system now reads the canonical template and creates a per-employee copy. This closes a quiet edge case where the same Document file was being signed in place by multiple new hires — subsequent signers could overwrite the first signature, and there was only ever one shared audit trail. Per-employee copies give every recipient their own audit trail, signature record, and signing certificate.
  • Read-only mode for informational templates — Templates with no required signers generate as a read-only document in the employee's drawer that doesn't require any signing flow to surface. The employee just sees the document in their files. Repeat generation is a no-op.
  • Backfill for existing organizations — Existing organizations have a one-time migration that walks every onboarding task with a legacy attached document, derives a template from it, and re-points the task at the new template. Safe to re-run on rows already migrated.

New Features — Onboarding/Offboarding Workflow

  • Instance delete returns immediately — Removing an onboarding or offboarding instance from a user's profile previously appeared to "come back for 30 seconds" — the row stayed visible until the email notification finished sending to all recipients. The delete now returns the moment the row is gone; the email is dispatched in the background. Refreshes during the email-send window now show the deletion correctly.
  • Standalone /onboarding page retired — Templates and per-user instances were both surfaced from /onboarding, duplicating UX that already lived on each employee's profile (Onboarding / Offboarding tabs). The standalone page is now a thin redirect to /users; the Guided Tours setting was updated to match. /settings/onboarding absorbs the template management UI for administrators.
  • Default assignee resolved per-instance — Onboarding template tasks with no explicit assignee previously resolved a default per-task at create time, which could bounce unassigned tasks across multiple administrators on the same instance. The default is now resolved once per instance creation, so all unassigned tasks for that import land on the same administrator — easier to track and matches what an administrator expects when they kick off a new hire.

New Features — Read-Only Document Acknowledgment

  • "I have read & acknowledge" button on read-only docs — Read-only onboarding/offboarding documents (employee handbook, code of conduct, exit policy, return-of-equipment acknowledgment) now have a footer button in the document preview that explicitly records "I have read this". Clicking it marks the linked onboarding task complete with a real timestamp + audit row, fires the standard task-completed notifications, and rolls the parent instance status forward (Not Started → In Progress → Completed). Only the document owner can click — administrators and department heads keep their existing path for marking tasks complete on someone else's behalf.
  • Implicit acknowledgment on view — Opening a read-only doc whose linked task is still pending also fires the same acknowledgment quietly in the background. Both paths are recorded distinctly in the audit log (ACKNOWLEDGE vs ACKNOWLEDGE_ON_VIEW) so explicit and implicit signals stay distinguishable. Document views are already audit-logged with IP and user-agent, so the implicit path retains enough trail to defend in a compliance dispute.
  • Identical behavior for offboarding — The same flow closes return-of-equipment and exit-policy reads when a leaving employee opens the doc, not just onboarding handbooks. Notifications correctly say "Offboarding" vs "Onboarding".
  • Self-heal on the documents page — When an employee opens /documents before visiting their onboarding tab, any per-employee copies of onboarding documents that haven't been generated yet are now generated lazily on the list request. Previously employees only saw their read-only onboarding doc after signing the sibling signature-required document, because the page reload happened to trigger the generation through a different code path.
  • Document list stays in sync after deletes and acknowledgments — Browser caching for the documents list was loosened so deletions and acknowledgments show up immediately instead of requiring a hard refresh. Server-side caching still handles the speedup.

New Features — Action Required Sidebar Badge

  • Amber count badge on Action Required — The global app sidebar's "Action Required" entry now shows an amber pill with the user's outstanding count: pending signature requests + overdue signatures + pending read-only acknowledgments. Hidden when zero. When the sidebar is collapsed, the badge becomes a small amber dot in the corner of the icon so the signal survives the narrow rail.
  • Refreshes without a full page reload — The badge count refreshes on first sign-in, on every navigation into /documents or /dashboard, and inline after any acknowledgment in the documents page itself — so the count never lags behind reality.

Bug Fixes

  • Document deletes didn't drop out of ?view=byEmployee until refresh — Deleting a document from the by-employee drawer view appeared to do nothing until the user manually refreshed. Fixed.
  • Onboarding instance "came back for 30 seconds" after delete — See "Instance delete returns immediately" above; root cause was the delete waiting on email send before responding.
  • Read-only doc only appeared after signing a sibling — Per-employee generation for onboarding read-only docs only fired when the employee opened the onboarding tab. If they hit /documents first, their read-only doc was missing until the page reload after signing happened to trigger generation through a different code path. Fixed via the self-heal described above.
  • Templates page redirected employees to a white screen — Logging in as an employee and visiting /documents/templates redirected to /documents but rendered a blank page until manual refresh. Fixed.
  • "New Template" button on /documents/templates was silently no-op — Clicking "New Template" did nothing for some users. Fixed.

v1.0.11

Documents Redesign

A top-to-bottom rework of the Documents experience. The /documents page now opens to a familiar filing-cabinet view of every employee's drawer, custom folders sit alongside the 13 built-in categories, the upload flow has separate Store and Sign modes, and the document detail page has a large preview and a quieter sidebar. Most of this release is in the documents area; the rest is small fixes around feature-flag propagation, exports, and dependency updates.

New Features — My Documents (filing cabinet)

  • Folder cabinet for every user — The /documents page opens on "My Documents" — a 2-3 column grid of folder tiles for every employee in the org. Each user always sees the full cabinet (13 built-in categories: Contracts, Offer Letters, Salary Increments, Performance Reviews, Policy Documents, ID Documents, Certificates, Training Materials, Medical Certificates, Visa Documents, Tax Documents, Insurance, Other) — even if a folder is empty — so you can drop a new doc straight into the right category instead of routing everything through a generic Upload entry point.
  • Folder visual — Each tile is rendered as an actual two-layer folder shape (tab + body) tinted by the category's brand color. Empty folders sit at low opacity; populated folders take a bolder tint plus a colored count badge in the corner. Hover lifts the folder graphic for a subtle "pluck-out" feel. The "New folder" tile mirrors the same shape with a dashed outline so it sits in the cabinet visually.
  • Custom folders — Per-user custom folders sit above the 13 built-in categories. Pick a name (max 50 chars), a Lucide icon from a curated set, and a hex color from a swatch grid. Folders are scoped to a single user's drawer.
  • Folder context menu — Edit / rename / re-icon / recolor / delete on any custom folder via the context menu that surfaces on hover. Soft-deleted folders release their name slot immediately, so you can recreate a folder with the same name without hitting a unique-constraint error.
  • Tag filter — Folder grid carries a tag filter dropdown sourced from the docs in the currently visible scope. Selecting a tag at the top level drops you into a flat result list with cross-folder matches; inside an open folder, the tag narrows that folder. Composes with search.
  • Folder URL routing — Open folders are encoded as ?folder=offer-letters etc., so refresh, browser-back, and shared links keep state. Custom folders use ?folder=custom:<id> for unambiguity.
  • Per-user folder pages/users/{id} now shows the same folder grid component on the Documents tab. Admins, Executives, and Department Heads (for in-scope employees) can browse a coworker's drawer; everyone else sees their own.
  • Role-gated folder creation — The "New folder" tile is hidden when an employee or department head is viewing their own drawer — you don't manage your own filing structure as a non-admin. It's still available to admins / executives anywhere, and to department heads viewing an in-scope employee.

New Features — Action Required

  • Unified attention dashboard — Action Required is now a single dashboard that aggregates pending signatures + expiring documents into one list, with chips to filter by All / Sign / Expiring / Overdue and stat tiles that double as filters. Replaces the old separate Inbox / Completed / Expiring rails.
  • Sent-style toolbar — Search, status filter, sort, Export, admin-only Templates / Bulk Send shortcuts, and a Start CTA on top of a desktop table + mobile card list. Pagination footer with rows-per-page selector renders whenever the list has any items.
  • Completed view folded in — The retired ?view=completed page is now a fifth chip + filter on Action Required. URL syncs via ?filter= so refresh and back-button preserve state.

New Features — Drive-style Upload Page

  • Two-mode upload/documents/upload ships with a Store / Sign segmented control rendered as a sliding-pill tab, each mode laid out as a 2-column drive view: large file dropzone + live PDF or image preview on the left, metadata aside on the right. Mode is URL-synced via ?mode=store|sign so deep-links and refresh land consistently.
  • Store mode — A dedicated form for filing a doc in a built-in category or a per-user custom folder. Posts to the existing personal-upload endpoint. Optionally targets another user when the caller can manage that user's documents.
  • Sign mode — Drag-and-drop a PDF, choose recipients (parallel or sequential), set a due date, place signing fields, and send. PDF preview now uses viewport-relative sizing (78vh, capped at 1100px) so the preview is actually usable on a 14"+ display.
  • Description & tags — Both modes have a "Description & tags" section: optional description (sanitized) and up to 10 tags (32 chars each, lowercase, deduped). Tags flow through to the folder grid's tag filter.
  • Categories-only upload picker with help tooltip — Store mode's category dropdown now offers only the 13 built-in DocumentCategory values, with a help tooltip next to the Category label explaining that administrators and executives can move the file into a custom folder afterwards. Removes confusion where uploaders saw their custom folders in the picker but couldn't always file into them.
  • Expiry date tooltip — A help tooltip next to the optional Expiry date field gives a concrete example (visa / insurance / certificate renewal) so it's clear what the field is for and when to leave it blank.

New Features — Document Detail Page

  • Slim breadcrumb header — Back link › title › status pill › action buttons (Place fields / Open / Download), all in one row, replacing the tall hero card.
  • Large embedded preview — The PDF takes the dominant left column at 78vh viewport-height with a click-to-open hint floating top-right on hover. No more 280×320 thumbnail card.
  • Unified sticky sidebar — One sticky panel on the right with internal dividers instead of four separately-bordered cards. Reads as a single neutral surface.
  • Sequential signing chips — In sequential mode the order chip (1, 2, 3…) is color-coded — green for signed steps, primary tint for the active step, gray for queued, red for declined / expired — so the timeline reads as a checklist at a glance.
  • Initials avatar chips — Each recipient row shows an initials avatar tinted by status, with a tiny colored dot + sentence-case status label inline ("● Declined · Apr 29, 7:46 PM") instead of a chunky pastel pill chip.
  • Truncated document ID — Doc IDs render as CMOKVT…0T0S1 in the Details panel with a one-click copy of the full ID.
  • Audit history collapsible — The audit timeline (admin-only) becomes a disclosure section at the bottom of the sidebar instead of a third tab. Tucked away because most visits aren't there to read it.

New Features — Sign Page Polish

  • Header always visible — The "Review and complete / Decline / Finish" header on /documents/sign/:id no longer hides behind the global app toolbar until you scroll.
  • Decline modal works on first click — The decline confirmation modal opens reliably the moment you press Decline.

New Features — Excel Export

  • AG Grid export preview for Documents — The Documents page's "Export in Excel" now opens /documents-export, an AG Grid preview matching /users-export. Theme picker (Quartz / Alpine / Balham / Material), quick filter, column show/hide, sort, reset, and CSV download. Status cells are color-tinted to match the chips on the list page.

New Features — Notifications

  • Document notifications deep-link to the right view — Notifications for signing requests, reminders, and expiring documents now link straight to /documents?view=actionRequired instead of the generic /documents landing page. The user lands one click from the doc that needs them.
  • Personal-upload recipients are notified — When an admin uploads a personal document for an employee (Store mode targeting another user), that employee now gets an in-app notification + email. Previously the upload was silent.
  • Document upload oversight notifications — Whenever a document is uploaded for an employee, executive, department head, or administrator, leadership is now looped in: every executive is notified, administrators are notified (except in the narrow case where an executive uploads to an administrator), and a department head whose own report receives a document is included for their own department. The recipient still gets their existing notification — this is an additional oversight feed for the people responsible for HR records.
  • Daily document-expiry reminders — A new daily reminder fires at the 60 / 30 / 7 / 0-day marks before a document's expiry date. The document owner is always notified (so they have time to renew their visa, insurance, certificate, etc.); the same oversight audience above is also notified, with mute switches per audience member. Internal de-duplication prevents repeat reminders inside the same window.
  • Document oversight settings/settings/notifications now has a Documents section (visible to administrators, executives, and department heads) with two toggles: "When a document is uploaded for someone in scope" and "When a document is approaching expiry". Both default on so leadership has visibility out of the box.
  • Default digest scope is now "Full organisation" — New users get a full-org digest by default rather than "Just me", since most teams want to see who's off across the whole company. Existing users keep whatever they had.
  • Public holidays on the digest by default — The "Include public holidays in digest" toggle is now on by default, so the daily / weekly digest shows holidays alongside leaves without having to opt in.
  • "Anyone you manage" is now manager-only — The "Anyone you manage" digest scope only appears for users who actually have direct reports. Non-managers no longer see (or get clamped onto) an empty scope, and the radio is hidden in their settings.
  • Privacy settings flow into the digest — When an organisation has "Other departments — Hidden" enabled in /settings/general, non-admin users can no longer pick the full-organisation digest scope, and any saved org-wide preference is automatically narrowed to their own department at dispatch time. Administrators and executives stay exempt — the privacy toggle has always carved them out.

New Features — Calendar Privacy

  • "Calendar view — Hidden" is now actually enforced — The privacy toggle in /settings/general for "Calendar view" was previously cosmetic — turning it on didn't change what users could see. It now gates the per-user year-view absence calendar at /calendar/<userId> end-to-end: when "Hidden", only the user themselves, their direct manager, the department head of their department, and administrators / executives can open the page. Peers are redirected back to the dashboard with a notice. The leaves and balance APIs that power the page apply the same rule, so direct API calls can't bypass it either.
  • Calendar view defaults to Hidden — New organisations now start with "Calendar view" set to Hidden out of the box, since a year-view absence calendar shows medical / bereavement / similar sensitive leaves and shouldn't be broadcast to peers without an explicit opt-in. Existing organisations are also flipped to Hidden, on the basis that the toggle wasn't enforced before so any prior "Visible" value reflected the legacy default rather than a deliberate choice. Admins who do want the calendar open across their org can flip it back from /settings/general at any time.
  • Dashboard name links respect the toggle — On /dashboard, the colleague name in each row is only rendered as a clickable link when the viewer is allowed into that person's calendar. Click-through never lands on a 403 or an empty grid — the link simply isn't a link for viewers who'd be blocked.
  • Private leave types are hidden on the per-user calendar too — The "Private" toggle on /settings/leavetypes ("Hide leave type name from other employees on the dashboard") already worked on /dashboard; it now applies on /calendar/<userId> as well. Non-self / non-admin / non-same-department-head viewers see the type as a generic "Private" label with the reason and notes hidden, matching the dashboard behaviour. The leave's owner and admin / dept-head viewers still see the real type name and details.

Improvements — Visibility & Billing

  • Declined / expired docs hidden from your own drawer — A document you declined to sign or one whose deadline lapsed no longer keeps appearing in your own folder. Department heads still see those rows for their reports, and admins / executives still see the whole organization. Restored automatically if the document is reopened.
  • Multi-recipient visibility fix — When a document is sent to multiple co-signers, every recipient now sees the doc in their My Documents folder — not just the first recipient. Previously only the primary signer saw it in their drawer; the others had to open it from the notification.
  • Document quota now gates assignment + bulk send — Adding new signers to an existing document, and bulk-sending from a template, both now check the org's monthly document quota before creating any rows. Previously these paths could push usage past your plan's limit silently. The block surfaces a clear message with current vs. allowed counts and a link to upgrade.
  • Per-user signer counts in the Sent list — The Sent view groups multi-recipient sends as one row with a "Signed by N of M" indicator, instead of one row per signer.
  • "Shared" and "Unassigned" status filters — The status filter on ?view=sent adds two new entries — "Shared" (for documents shared without a signature request) and "Unassigned" (for documents with no recipients yet) — so admins can split bulk-send activity from one-off shares without scanning every row.
  • Department-head Sent view scoped to their own uploads — When a department head opens ?view=sent, the list now shows only the documents they themselves uploaded. Previously the view could surface admin- or executive-initiated sends that happened to involve the department head, which was confusing.

Improvements — Navigation & Settings

  • Documents in the sidebar gets its own group — The global app sidebar now has a top-level "Documents" section above Time & Projects, with role-aware children (My Documents, Action Required, Sent, Templates, Bulk Send, By Employee).
  • By Employee moves under Management — On the global sidebar, By Employee sits under Management next to Approvals and Reports — they're all admin oversight surfaces with the same shape. The in-page documents rail keeps its own By Employee link so admins already inside /documents don't have to bounce back to the global nav.
  • Sidebar scrollbar auto-hides — The app sidebar's scrollbar is now hidden until hover / focus / active scroll. CSS keeps gutter stable so the rail width doesn't jump.
  • Tag filter at top level — Selecting a tag on /documents (with no folder open and no search) now drops you into a flat result list of matching docs across folders. Previously the tag dropdown updated state but didn't change the visible cabinet, so it appeared to do nothing.
  • /approvals stat row fills width — When time tracking is disabled, the /approvals stat row (Total Pending / Leave / Expense / Timesheet) drops to 3 columns instead of leaving an empty 4th slot.
  • Time-tracking toggle reflects immediately — Toggling "Enable Time Tracking & Projects" in /settings/timetracking now updates /reports and /approvals on the next visit instead of waiting on a 5-minute settings cache. The Time / Projects / Scheduling cards, charts, the Available Reports table rows, and the Timesheet Entries tile all appear or disappear in sync with the flag.
  • No more redirect flash — Visiting /reports/time, /reports/projects, /reports/scheduling, or /approvals/time while time tracking is disabled now redirects cleanly to the parent dashboard before the page renders. Previously you'd see a split-second flash of the disabled report before being bounced back.
  • All "when does the org work" config in one place/settings/general is now the single home for both business days (the day-of-week picker) and default working hours (start, end, break for shift auto-generation). The Default Organization Schedule card moved over from /settings/timetracking, so admins no longer have to bounce between two pages to set up the company calendar. The schedule card only appears when time tracking is enabled.
  • Settings search refreshed for the new sections — The "Search settings" bar in the /settings header now finds the Default Working Hours card, the new notification toggles ("Default digest scope", "Public holidays on digest", "Document upload activity", "Document expiry reminders"), the per-leave-type "Private" toggle, and gives the Calendar privacy and Other Departments privacy toggles their own dedicated entries. Searching for the old label "business hours" still resolves to the new Business Days section.

Bug Fixes

  • Folder rename collision with soft-deleted rows — Recreating a folder with the same name no longer fails with a 500. The unique index on (organizationId, userId, name) was treating soft-deleted rows as still occupying the slot. Delete now tombstone-renames the row so the slot frees up immediately.
  • Empty drawer now shows the full cabinet — A user with zero documents now sees every category folder marked "Empty" plus the "+ New folder" tile, instead of being routed through a generic upload prompt that hid all 13 categories.
  • Personal-upload notifications were missing — Employees received no notification when an admin uploaded a personal document for them. Fixed.
  • Multi-recipient docs vanished for co-signers — A document with multiple recipients only appeared in the first recipient's drawer. All recipients now see the doc in their My Documents folder.
  • Decline button on /documents/sign/:id did nothing — A z-index conflict caused the decline confirmation modal to render behind the signing pane, so clicks appeared to do nothing.
  • Personal upload would fail for some built-in categories — Uploading a document under categories such as Insurance, Tax, Visa, Medical Certificate, Training Material, ID Document, or Salary Increment could surface a generic "Server Error" on submit. Fixed — every built-in category now uploads cleanly.
  • "Requires signature" toggle removed in sign mode — Sign mode always requires signatures, so the redundant toggle on /documents/upload?mode=sign is gone. The Sign-by picker and signing-order editor are always rendered when the page is in sign mode.
  • Private leave types could leak on a colleague's calendar — Opening another user's /calendar/<userId> page surfaced the real leave-type name (e.g. Bereavement Leave) even when the type was marked Private — only the dashboard was applying the obfuscation. Fixed; the per-user calendar now mirrors the dashboard rule.
  • "Calendar view — Hidden" privacy toggle did nothing — The toggle in /settings/general for "Calendar view — Hidden" was persisted to the database but never read by the actual per-user calendar. Toggling it on / off didn't change what peers could see. Fully wired up now (see "Calendar Privacy" above).
  • "Business hours" was actually the business-days picker — The section in /settings/general labelled "Business hours" was a row of day-of-week checkboxes (Mon, Tue, ...), not a time-of-day setting. Renamed to "Business days" to match what it actually does.

v1.0.10

New Features

Geofencing

  • Allowed clock-in locations — New /settings/geofencing area lets Administrators and Executives define the sites where employees can clock in / out. Department heads can also manage geofences — scoped to their own department's users and projects. Every fence carries a name, type (Site / Project Site / Client Site / Home Office / Other), optional address, and a description field visible only to admins. Fences can be toggled Active / Inactive without losing history.
  • Circle and polygon shapes — A fence can be either a circle (centre + radius) or an arbitrary polygon (ordered list of vertices). Switch between the two with a toggle in the editor. The server stores both forms and runs the right inside/outside test per shape; polygon fences also store a centroid + bounding-sphere radius so list views, nearest-fence queries, and other circle-shaped code paths keep a sensible reference point.
  • Interactive map editor — A full-page, split-layout editor with the map on the left and a side rail for metadata. Click "Draw circle" or "Draw polygon" to place a shape by hand; drag the marker or the polygon vertices to refine; "Use current location" drops the shape at the admin's own GPS fix and zooms in. Coordinates + radius inputs stay in sync with the map in real time. When GOOGLE_MAPS_API_KEY is missing the editor gracefully falls back to coordinate inputs.
  • Google Maps + OpenStreetMap — Provider toggle in the editor toolbar. Google is the default when a key is configured; OpenStreetMap (Leaflet + leaflet-draw) is available as an alternative — same drawing tools, same shape edit handles, no API key required. Native map controls (zoom, map-type switcher) stay visible and out of the way of the custom toolbar on both providers.
  • Configuration + Assignments tabs — The editor splits fence geometry (Configuration) from access control (Assignments) into two top-level tabs, matching the pattern that enterprise IAM and HRIS tools use for resources + permissions. The tab state round-trips through the URL (?tab=assignments) for bookmarks and back-button navigation. Newly-created fences redirect straight to the Assignments tab so the admin never forgets the follow-up step.
  • Assignment scopes — Every fence must be assigned to at least one target before employees see it. Supported scopes are Organization (everyone), Department, User, and Project. Resolution order when an employee clocks in is Project → User → Department → Organization; the most specific match wins. Per-assignment flags control whether clock-in, clock-out, or both are enforced — useful for sites where workers must clock in at a specific location but can clock out anywhere. Assignments can be added, edited, and removed from a shared modal matching the rest of the product's dialog treatment.
  • Three enforcement modesREQUIRED blocks clock-ins that fall outside an applicable fence, OPTIONAL lets them through but flags the entry on the compliance dashboard, and LOG_ONLY silently records the out-of-bounds condition for admin review without surfacing anything to the employee. Mode + GPS accuracy tolerance + maximum acceptable GPS accuracy are all configured from /settings/timetracking.
  • Live status on the clock-in screen — A full-width status banner above the Clock In / Clock Out buttons on /time-tracking shows the current state: inside the allowed area ("At HQ"), outside ("1.4 km from HQ"), no GPS signal, or still acquiring. Colours are muted (emerald for good, amber for warning) so the banner reads as information, not an error. Updates in near-real-time as the employee's location changes. When the employee picks a project in the clock-in modal, the banner re-evaluates against that project's fences so they see immediately whether the chosen project is clockable from here.
  • Rejection modal with nearest-fence guidance — When an attempt is blocked, a modal shows the nearest allowed site by name, the distance to it, and a list of all allowed locations for the action — so employees know where to go without having to call their manager. GPS-accuracy rejections include a specific hint to move outdoors or near a window for a better fix.
  • Compliance dashboard + CSV export — Administrators / Executives (and dept heads for their own department) get a paginated list of out-of-bounds clock-ins with 7-day and 30-day summary counts. CSV export uses the same filters and encodes rows safely for spreadsheet consumers.
  • Audit trail on every change — Every create, update, delete, assignment change, and blocked clock-in attempt is recorded in the organization's audit log with actor, fence id, and the before/after payload, so compliance has a reviewable trail of every geofence policy decision.
  • Geofenced time entries record their fence context — When an entry is created inside a fence, the fence id, the GPS accuracy at the time, and a confidence classification (HIGH / MEDIUM / LOW / UNVERIFIED) are stored on the entry. Historical entries retain this information even if the fence is later renamed, deactivated, or deleted.
  • Mobile-ready clock API — Clock-in / clock-out endpoints emit an X-API-Version response header and return structured data.error codes so future native mobile clients can match on stable identifiers instead of parsing human messages. An offline-queue timestamp protocol (clockInAt / clockOutAt body fields with a ±15 min future / 24 h past tolerance) lets mobile apps replay clock events queued while the device was offline.
  • Shared row-action pattern — The /settings/geofencing list and the Assignments table both use the same ellipsis-menu row action (Edit / Manage assignments / Delete, teleported dropdown so it escapes table clipping) that the admin dashboard already uses — one interaction pattern, one mental model, regardless of which table the user is on.

Time Tracking & Project Management

  • Pay-period lockdown — Admins can set a lockdown date after which time entries with a clock-in before that date can no longer be edited or deleted by employees. A configurable grace window (in days) lets managers close out late-submitted hours after the lock. Only Administrators and Executives can override the lock for corrections; every override is written to the audit log with the entry's date and the lockdown cutoff so compliance has a reviewable trail of every exception. Department heads cannot override.
  • Configurable clock time rounding — New time-tracking setting to snap clock-in/out to a configured interval (1–60 minutes) with UP / DOWN / NEAREST direction. Disabled by default. Applied uniformly to live clock-in/out, manual entry, and edits so billable hours line up with the org's rounding policy regardless of how precisely the user clicks.
  • Free-form tags on time entries — Each entry can carry up to 10 lowercase tags (32 characters each) for categorisation and filtering. Tags appear in the entry modal as a comma-separated input — paste anything, the system normalises (lowercase, strips punctuation, dedupes). Filterable through the entries API.
  • Project money budgets — New budgetAmount field on projects complements the existing hours budget with a cost ceiling. Tracked against (hours × hourly rate) of billable entries only — non-billable time never draws down the cost budget. Project detail page renders a second progress bar alongside the hours budget with matching 80/100% colour thresholds. Currency inherits from the organization's default.
  • Bulk delete time entries — Multi-select in the timesheet list view with a floating action bar to soft-delete up to 200 entries in one request. Fails closed: if any row in the batch is blocked, nothing is deleted. Selection clears when the week changes.
  • Restart-timer (duplicate) — One-click duplicate on any past entry clones the project / task / description / notes and starts it as a fresh active timer. Stops any currently-running timer first so the "one active timer per user" invariant holds.
  • Branded PDF export for timesheetsGET /api/reports/timesheets.pdf returns a per-employee branded breakdown with summary totals, billable split, and an optional money column. Same filters as the JSON report plus projectId. Admins/Executives get org-wide; department heads are scoped to their own department; employees see their own. New "Export PDF" button on the timesheet top bar.
  • Expanded timesheet filters and report drill-down — Project, billable, and approval-status filters on the timesheet list, all bound to URL query params so filtered views are shareable. Time-report tables now drill down: clicking a row navigates to the timesheet view pre-filtered to that user / project, so a manager can go from "this person logged 40h" straight to the underlying entries.
  • Bulk edit on timesheets — Multi-select in the list view now supports field-level bulk edits alongside bulk delete. Pick any combination of project, billable flag, and hourly-rate override, apply to up to 200 entries in one request. PATCH semantics: only the fields the caller explicitly ticks get written, so one column can be restamped without touching the others. Same fail-closed lockdown and ownership gates as bulk delete.
  • Cost aggregation in time reports — The time report now surfaces fully-loaded cost alongside hours for administrators and executives. Rate is drawn from each employee's effective Compensation record at the time each entry was logged, so mid-period raises are picked up automatically. SALARY pay types are normalised to hourly at 2080 hours/year; HOURLY is used as-is. A new Total Cost summary card appears on the report, and the Employee breakdown picks up a Cost column. Entries for employees with no Compensation on file are flagged ("(N unpriced)") so admins can close data gaps rather than silently under-report. Department heads continue to see hours but never cost.

Team Matrix View for Schedules and Timesheets

The calendar view overlays every visible employee's shifts or time entries onto one week × 24-hour grid. With a handful of users it works fine; past 20-30 it becomes unreadable — impossible to spot who's missing entries, who's scheduled for overtime, or what still needs approval. Enterprise timesheet and scheduling tools (Workday, SAP SuccessFactors, UKG/Kronos, ADP, Oracle HCM, Ceridian Dayforce, BambooHR) all solve this the same way: one row per employee, one column per day. This release ships that view alongside the existing Calendar and List views on both /schedules and /time-tracking/timesheets, and makes it the default.

  • Employee × day matrix — Each visible employee is a row; the seven days of the current week are columns. Each cell shows the relevant aggregate (hours + status dots for timesheets; shift time range + type for schedules), color-coded by dominant state (green approved / amber pending / red rejected for timesheets; shift-type colors for schedules). A Week total column at the right gives the per-employee totals at a glance. The employee column and header row are sticky, so scrolling horizontally keeps the name and date context in view.
  • Click-to-drill — Clicking a cell with one entry or shift opens it directly in the edit modal. Clicking a cell with multiple entries expands the row inline, grouped per day, so you can see every entry side-by-side without leaving the page. Clicking an empty cell opens an Add Entry / Add Shift modal pre-seeded to that employee and date (and a 9 AM default start time for schedules), so admins don't have to re-pick the user they just clicked on. A chevron on the employee name toggles the inline expansion for the whole week.
  • Drag-resizable columns — Every column (Employee, each day, Week, Status) has a drag handle on its right edge. Widths clamp between 60 and 600 pixels and persist per-browser in localStorage so the layout you tuned last week is still there when you reload. Double-click any handle to reset that column to its default; a toolbar button clears every custom width at once.
  • Density toggle — Compact (36–40px rows) / Normal (52–60px) / Spacious (72–84px). Remembered across sessions. Compact hides secondary lines (department name under employee, shift-type label) so a 30-person team fits on a single screen; Spacious adds breathing room around the data when you're presenting or reviewing one team closely.
  • Sort and filter — Sort by name, week hours ↑/↓, pending count, or missing days (timesheets) / unscheduled days (schedules). Filter chips at the top toggle between All / Missing / Pending / Approved / Rejected (timesheets) or All / Unscheduled / Scheduled / 40h+ (schedules), each with a live count so you can see at a glance how many employees fall into each bucket. A text search narrows by name, email, or department. All three controls persist.
  • Holidays and leave aware — Holiday cells are tinted green with the holiday name in the header; cells where the employee is on leave show the leave type label instead of a blank, so "no shift scheduled" is visibly distinct from "scheduled off". Open shifts (schedules without an assigned user) render with a muted "Open Shift" label so they're easy to spot in the matrix.
  • Calendar and List views still available — The top bar's toggle is now three buttons instead of two: Team (default) / Calendar / List. The URL ?view= query parameter accepts all three values, so existing deep-links to the calendar or list view continue to work unchanged, and a team-view link can be shared with colleagues.

Approvals & Reports UX Redesign

  • Approvals dashboard overview/approvals is now a dashboard-style overview. Four edge-joined KPI stat cards (Total Pending, Leave Requests, Expense Reports, Timesheet Entries — hidden when time tracking is off) each link to their detail queue. Below, a unified "Pending Workload" card shows the big-number total + a horizontal bar breakdown per queue, followed by a Review Queues table (Queue badge, Status, Pending count, Review link). Status dot in the header pulses amber when items are waiting and turns green when the queue is clear. A per-queue skeleton covers the pre-fetch window so the cards never flash "0" before counts arrive.
  • Per-type approval pages — Three new dedicated routes: /approvals/leave, /approvals/expenses, /approvals/time. Each follows a consistent layout: breadcrumb + page header, search + filter dropdowns + export toolbar, a data table with <colgroup> column widths, employee avatar cell, colored status / type badges, row-click detail slideover, action dropdown menu per row, reject modal with a dedicated header / body / footer + live character counter + keyboard shortcut hint, and a bottom-pinned pagination footer with rows-per-page selector and prev/next navigation.
  • Reports dashboard overview/reports is now a dashboard overview. Five edge-joined KPI stat cards (Leave requests, Expenses total, Time hours, Active projects, Scheduled shifts — the last three hidden when time tracking is off) link to their detail pages and surface live numbers fetched in parallel. A two-column chart row shows the Expense Trend (3 or 6 months, area chart with fill) and Leave by Employee (top 5/10/15/20, bar chart rendering compact initials on the x-axis with full names in the hover tooltip — 10-user cap enforced on the API fan-out to prevent chart breakage on large orgs). A three-column chart row below (gated on time tracking) shows Hours Tracked over the last 6 months, Project Hours as a stacked area chart where each project is rendered in its own color (configurable top 3/5/7/10), and Scheduled Shifts by month. Each chart has a settings gear with contextual options (period, metric, max items) and a primary-colored "Done" button. An "Available Reports" table (responsive: desktop table, mobile card list) links to each sub-page.
  • Per-type report pages — Five new dedicated routes: /reports/leave, /reports/expenses, /reports/time, /reports/projects, /reports/scheduling. Each wraps the existing tab component inside a consistent shell (breadcrumb + page header). Permission gates and time-tracking redirects preserved.
  • Tab → query-param navigation — Hash-based tab navigation on /approvals and /reports (e.g. #leave) replaced with query params (?tab=leave). URLs are shareable, round-trip through Vue Router, and cooperate with existing drill-down query params (e.g. ?tab=time&userId=…). A shared useTabParam composable handles the state, URL sync, and the async-validation edge case where validTabs depends on settings (e.g. isTimeTrackingEnabled) that load after the initial render.
  • Shared UI primitives for approvals and reports — New components/common/ directory: PageHeader, PageTabs, PageToolbar, StatCard, StatusBadge, SearchInput, FilterSelect, FilterBar, SlideOver, ActionMenu. The stat card uses an edge-joined first:rounded-l-lg last:rounded-r-lg strip pattern so a row of cards reads as a single unit. The action menu teleports its dropdown to <body> with fixed positioning so it escapes any clipping ancestor (table wrappers, slideovers). The slideover is a right-aligned 400px drawer with backdrop click / Esc to close. All built in raw Tailwind against the existing CSS variables so they match the rest of the product's design system.
  • Cost flow fix in time reports — The Reports Time tab was dropping cost, totalCost, and entriesMissingRate between the API response and the chart/card render paths on the previous by_employee merge. Refactored the merge logic out into utils/time-report-shape.ts with proper types. The "(N unpriced)" hint now renders correctly on the Total Cost summary card when entries lack effective compensation, and per-employee cost flows through to the table column.
  • Full-width page treatment/approvals, /reports, and all 8 sub-pages drop the max-w-7xl cap and stretch to the full content-area width, matching the /schedules layout so wide tables don't leave empty side margins on large monitors.
  • Pass-through of avatar role colour + badges — Every new approval/report surface uses the existing <UserAvatar> component, so an employee's initials circle, role-tinted colour, and Administrator/Executive crown/star badge render identically across /users, /calendar, /approvals, and /reports. No new palette, no drift.
  • Global settings search bar in the top header — On every /settings/* route, the top toolbar renders a compact Stripe-style search input (left-aligned with the page H1). Press / anywhere on a settings page to focus it. Hidden on mobile to keep the toolbar usable.
  • Deep-linkable individual settings — Search results resolve to path#anchor (e.g. "time zone" → /settings/general#time-zone, "auto clock out" → /settings/timetracking#auto-clock-out). Anchor IDs added across ~30 settings components covering General, Time Tracking, Expenses, Carry Forward, Leave Types, Departments, Holidays, Email, Policies, Documents, Training, Performance, Onboarding/Offboarding, Company Directory, SSO, Integrations, QuickBooks, Branding, Security, Support, Danger Zone, Password, 2FA, Notifications, Connected Apps, Guided Tours, and Trusted Devices. Landing on a deep link smooth-scrolls to the target element and paints a soft primary-colored focus ring that fades after ~2.6s.
  • Hand-curated search index with keyword aliasescomposables/useSettingsSearch.ts defines a static index of ~75 entries (one per settings page + one per prominent sub-setting) with label, description, parent-page, icon, hash, and keyword aliases. Scoring prioritizes exact label match (1000) > label prefix (500) > keyword exact (400) > label substring (300) > keyword prefix (200) > keyword substring (150) > description match (75) > multi-term all-match (100). Case-insensitive; multi-word queries require every term to appear somewhere in label/description/keywords.
  • Billing-aware lock badges in results — Each result carries locked, planLabel, isAddOn. Locked entries render a 🔒 Pro / 🔒 Business / 🔒 Enterprise / 🔒 Add-on pill matching the badge style on /settings overview cards — so users can see a gated feature exists and know which plan unlocks it before they click. Locked rows stay in the list (for discovery) but rank lower via a -25 score penalty. Uses the same isFeatureLocked + getLockedPlanLabel helpers as /billing so the labels stay consistent across the product. The bar calls loadSubscription() on mount to ensure badges reflect the org's actual plan.
  • Role-aware visibility — The search only indexes visibleItems from useSettingsNavigation, so an employee searching for "audit logs" gets no result (the page is admin/exec only), while an administrator does. Department heads see onboarding/offboarding entries.
  • Tabbed-page auto-switch — On pages with tabs (Security, Connected Apps), navigating to #audit-logs / #personal-webhooks / etc. now auto-selects the matching tab via a watch(route.hash) wired to a static anchor-to-tab map, so a result click lands on actual content rather than the default tab.
  • Keyboard navigation — Arrow keys move the active row, Enter commits, Esc closes, / global shortcut focuses the input (ignored when the user is already typing in another field).
  • First-paint layout stability — The authenticated layout's outer wrapper animated lg:pl-[68px]lg:pl-[260px] when the sidebar collapsed state hydrated from localStorage, dragging the header — including the search bar — left/right for a split second on every refresh. Suppressed the transition on the very first paint via a layoutReady flag so the layout snaps instantly; sidebar-toggle animation still works for later user interactions.

Guided Tours Overhaul

  • Redesigned popover — Each tour step now leads with an icon chip, a clearer title, and a short body. Optional "Tip" strips surface pro moves and shortcuts; optional keyboard-shortcut chips (e.g. /, A, R) appear where relevant. Subtle entrance animation, a gentle pulse on the highlighted element, full dark-mode parity, and a viewport-aware width so the popover never overflows on small screens (respects prefers-reduced-motion).
  • Rewritten tour copy across the app — Every tour — Welcome, Calendar, Time Tracking, Timesheets, Schedules, Expenses, Approvals, Reports, Users, Documents, Billing — replaces the old label-style descriptions ("Click here", "View data") with outcome-led coaching that names the actual job ("One tap to clock in", "Find anyone, fast", "Select many, change once"). Welcome ends with a concrete next action.
  • Five new tours — Geofencing, Projects, Profile, Onboarding, Training. Each 3–4 steps, auto-opens once per user on first visit to the matching page, replayable any time from Settings → Guided Tours. The Geofencing tour walks through the map editor, the Google / OSM provider switch, and the assignments step that most admins miss.
  • "Don't show tours" opt-out inside the popover — A subtle text link on step 1 of every tour lets a user dismiss every tour they haven't seen yet, without navigating to Settings. Marks them all complete server-side and locally; individual tours can still be replayed later.
  • Smarter placement — Tours now scroll the highlighted element into view only when it's actually off-screen (with a 64px top margin for sticky headers), anchor the popover directly next to the target instead of drifting to the bottom of the page, and size themselves to the viewport so narrow phones don't get a clipped card. Fixed a positioning regression where the popover could drift hundreds of pixels from its target on tour start.

Page Redesigns & Data Export

  • Clean flat list view for timesheets — New admin-audit-log-style table replaces the day-grouped list view. Shows all entries across the selected range in a single flat table with columns: Date, Employee (UserAvatar + department), Project (color dot + task), Description, Time (in→out), Duration, Status (CommonStatusBadge), and Actions (CommonActionMenu). Server-side pagination (50 rows/page default) keeps the API efficient. Checkbox multi-select supports bulk edit and bulk delete. The entries API endpoint now returns summary aggregates (approved/pending/rejected minutes via groupBy) alongside the page so header stats stay accurate across pagination. View mode (calendar/list) persists in the URL query param (?view=calendar / ?view=list) — no flash on refresh.
  • AG Grid export page for timesheets — Clicking the export button navigates to /time-tracking/timesheets/export with the active date range and filters as query params. The page uses AG Grid Community (ag-grid-vue3) with: four theme variants (Quartz / Alpine / Balham / Material) auto-switching to dark mode, custom Excel-style checkbox set filter per column (Select All / Deselect All with search — replaces the Enterprise-only Set Filter), Columns dropdown to show/hide columns with live column re-fit, quick filter search across all columns, pinned bottom totals row that recalculates on every filter change, ResizeObserver for responsive column sizing on window resize, and CSV export that respects current filters and visible columns. All data loads in a single infinite-scroll grid — no pagination.
  • PDF preview in new tab — PDF export now opens in a new browser tab instead of auto-downloading, so users can review before saving. The PDF also now respects the date range filter (was previously always the current week). Logo alignment in the PDF header is fixed — vertically centered with the brand name text using proper pdf-lib baseline math.
  • List view + export for schedules — All timesheet list-view features ported to /schedules: flat table (Date, Employee, Department, Shift Type badge, Time, Break, Duration, Notes, Actions), calendar/list toggle persisted in URL, AG Grid export page at /schedules/export with the same tooling, Export CSV and PDF buttons in the top bar, sidebar and date range hidden based on view mode, stat chips updated to uniform neutral style, scrollbar auto-hide in list view. Client-side pagination since the schedules API returns all shifts at once.
  • Users page redesign/users redesigned to match the admin dashboard's users table pattern. Modal-based filter replaced with inline filter dropdowns (Department, Role, Status) always visible in the toolbar. Table upgraded from a 12-column grid layout to a proper <table> with 8 columns: Name+Email (UserAvatar cell), Department, Role (StatusBadge), Job Title, Status (StatusBadge with Offboarded variant), Last Login, Joined, Actions (CommonActionMenu preserving all existing actions). Client-side pagination with rows-per-page selector (10/20/50). CSV export of filtered users. Full-width layout matching /approvals. AG Grid export at /users-export.
  • Uniform stat chip styling — Timesheet and schedule top bars use consistent neutral-tone stat chips (bg-muted/60 ring-1 ring-border) instead of rainbow-colored pills, matching the admin dashboard pattern across all pages.
  • Responsive top bar wrapping — Both timesheet and schedule top bars use flex-wrap gap-y-2 so stat chips, view toggle, and action buttons wrap gracefully on narrow viewports instead of overflowing.
  • Sidebar visibility gating — Employee sidebar toggle button hidden in list view on both timesheets and schedules (sidebar is only relevant for the calendar grid).
  • Date range visibility gating — Date range filter row hidden in calendar view on both timesheets and schedules (calendar is fixed at 7-day columns).
  • Unified Export dropdown — New CommonExportDropdown component replaces separate CSV/PDF/Excel buttons with a single "Export" button + dropdown menu showing "Export in Excel", "Export in PDF", and "Export as CSV". Applied across all pages: timesheets, schedules, all 3 approval pages, users, expenses.
  • AG Grid export pages for approvals — Three new export pages: /approvals/leave-export, /approvals/expense-export, /approvals/time-export — each with the full AG Grid tooling (theme picker, column visibility, custom set filters, quick filter, pinned totals).
  • Client-side branded PDF export — New shared utility utils/export-pdf.ts generates branded PDFs client-side using pdf-lib. Fetches org logo + company name from /api/branding (which now falls back to SMTP_LOGO_URL env var). Logo + brand name header on page 1, proper page breaks, footers with page numbers. Used by all approval pages, users, and expenses for PDF export.
  • Manage Departments modal redesign — Compact header/body/footer sections matching the approvals reject modal pattern. Contextual icon badges per department (primary when active, muted when inactive). Clickable status badges. Inline add form with 2-column grid.
  • Expenses page redesign — Full-width layout, admin-style table, inline filter dropdowns (status, sort). Nav tabs removed — Reports and Approvals moved to toolbar action buttons with pending badge. ExpenseTableRow actions replaced with CommonActionMenu (3-dot ellipsis). AG Grid export at /expenses-export.
  • Expense approvals redesign — Admin-style table replacing grid-cols-12 layout. CommonActionMenu per row. Reject modal with header/body/footer sections, contextual chip, character counter. Pagination footer.
  • Expense reports redesign — Admin-style table, CommonPageToolbar with inline filters, edge-joined CommonStatCard strip (Total Claims, Purchases, Per Diem, Travel, Grand Total), pagination, branded PDF via shared utility.
  • Reports overview fixes — All 5 charts use settings gear overlay with period options (3 months / 6 months / YTD). Period badge next to gear button. Fixed expense chart month bucketing (departureDate not createdAt). Fixed schedule chart date range. Fixed leave count (org-wide via leaves-data endpoint). Stat card labels updated to "total" instead of "this month".
  • Leave report tab redesign — Transformed from "report generator" (download cards) into a data dashboard. Admin-style table showing actual leave requests with UserAvatar, leave type badges, status badges, CommonActionMenu. Server-side pagination with aggregate stats. Leave distribution section preserved.
  • Expense report tab redesign — CommonPageToolbar with search and inline filters. Edge-joined CommonStatCard strip. Admin-style table with CommonActionMenu and CommonStatusBadge. Client-side search. Pagination. PDF export via shared utility.
  • Time report tab redesign — Removed all charts (Area, Bar, Line, Donut). Admin-style data table with individual time entries, server-side pagination, CommonStatusBadge, CommonActionMenu. Stats use server aggregates.
  • Projects report tab redesign — Stat card strip, CommonPageToolbar with search and inline filters, admin-style table with project stats and progress bars, CommonActionMenu, pagination, export.
  • Scheduling report tab redesign — Stat card strip, CommonPageToolbar, per-shift detail table with shift type badges (REGULAR=primary, FLEXIBLE=info, ON_CALL=warning, HOLIDAY=success, WEEKEND=neutral, OVERNIGHT=error), pagination, export.
  • Projects list page redesign — Full-width, admin-style table with project color dots, status badges, progress bars, CommonActionMenu, pagination footer, export dropdown.
  • Documents page redesign — Full-width, admin-style table with CommonActionMenu and CommonStatusBadge. New CommonSegmentedTabs component replaces underline tab bar — pill-shaped toggle group with sliding primary-color indicator, ResizeObserver for responsive positioning. URL sync changed from #hash to ?view= query params.

Improvements & Fixes

UX Refinements

  • Org chart list view matches the /users table styling — The /users/orgchart?view=list People page has been realigned with the admin-dashboard <table> treatment used on /users: a single shared CommonPageToolbar with search + Department / Employment Type / Status inline filters, a proper <table> with <colgroup> column widths and rounded header cells, inline UserAvatar cells, CommonStatusBadge pills for the employment-type status (Full-time / Part-time / Contract / Intern / Temporary / Seasonal), and the standard rows-per-page pagination footer (10 / 20 / 50). Employment-type tones are mapped through the shared StatusBadge palette so colors stay consistent with the rest of the product. Column visibility (Employee # / Department / Location) is preserved as a trailing toolbar action. Mobile switches to a card layout.
  • Current-session indicator on /settings/trusted-devices — The trusted devices page now shows a dedicated "Current session" card at the top with your parsed browser + IP (e.g. "Chrome on macOS · 192.168.1.10") so you always know which device you're looking at — even when none of the stored trusted-device rows match. Per-row matching uses two signals: a cookie-based hash match flags "This device" with high confidence, and a heuristic fallback (single trusted row whose stored user-agent and IP both equal the current request) flags "Likely this device". Ambiguous matches are intentionally left unlabeled rather than guessed. The matched row is sorted to the top of the list so the device you're on is always the first thing you see.
  • Persisted day-row heights on the schedule grid — When a user drags the row-resize handle on /schedules to make a day taller or shorter, the new height now survives page reloads instead of snapping back to the 80px default. Heights are keyed by day index (Mon → Sun), clamped to the same 60–400px range the drag handle enforces, and written only when the drag ends (not during every mouse-move tick). Double-clicking a handle still resets that row, and when every row has been reset the storage entry is cleared entirely.
  • Leave history no longer shows phantom -1 day deductions for non-deducting types — The History table on /users/:id?tab=timeoff was rendering "-1.0" for every leave regardless of whether the leave type actually drew from a balance bucket. Work-from-home, Meeting, Training, and other tracking-only types were reporting balance hits that contradicted the Leave Balance card on /calendar/:id. A new shared utils/leaveBucket.ts helper mirrors the same ANNUAL / SICK / NONE classification used server-side (including the legacy fallback for rows that predate the deductionBucket column), and the History cell now shows "—" with an explanatory tooltip for leaves that don't deduct.
  • Mark all tours completed in one click/settings/tours gains a "Mark All as Completed" button next to "Reset All Tours". Admins and returning users who aren't interested in replaying the onboarding walkthroughs can now dismiss every visible tour at once instead of triggering each one to collect its completion flag. The button disables itself when every visible tour is already marked complete, so it can't be accidentally re-run. useGuidedTour.completeTours(ids) is the underlying helper for any future flow that needs to mark tours complete in bulk.

Bug Fixes

  • Notification settings (/settings/notifications) crashed for orgs with legacy defaults — When an organization's saved notification defaults were stored in an older shape, the page would throw Cannot read properties of undefined (reading 'enabled') instead of rendering. The server endpoint now normalises partial payloads through the shared resolveNotificationPreferences helper, and the client merges fetched values over the form's defaults so a missing nested key can no longer blow up the view.
  • Password manager hints on settings — Added the right autocomplete attributes to every password / client-secret input across settings (change password, SMTP password, SSO client secret). Browsers no longer warn in the console, and password managers can now offer the correct suggestion for each field.
  • Due-date calculation ignored BEFORE/AFTER direction — Template tasks configured as "7 days before hire date" were being created with due dates after the hire date (e.g. hire May 1 → laptop task due May 8 instead of Apr 24). The frontend import path in pages/users/[id]/index.vue was always adding the offset; it now defers to the server which honors dueDaysDirection: 'BEFORE' | 'AFTER' with UTC date arithmetic.
  • "Start Onboarding" silently picked the first template — Clicking Start Onboarding auto-selected whichever template was alphabetically first with no way to choose. Added a picker modal that lists every eligible task list (or "Start with no tasks (add later)") before the instance is created.
  • "Import Task List" merged into an existing list — Selecting IT Setup when HR was already attached appended IT tasks into the HR card, erasing the grouping. Import now calls POST /api/onboarding/instances with the template ID, creating a separate OnboardingInstance with its own templateName/templateIcon snapshots so each imported list renders as its own card.
  • Already-imported templates were re-selectable — The Import and Start modals showed every template every time, allowing duplicate imports of the same list onto one user. Both modals now use availableTemplatesForType / availableTemplatesForStart computeds that filter out templates already present in the user's instances.
  • Employees could mark their own onboarding tasks complete — The subject user (the employee being onboarded) was able to tick off their own checklist items, including completing compliance tasks that require admin/HR review. Completion is now restricted to administrators, executives, the department head of the employee's department, or the explicit task assignee. Employees keep read access to see what's coming up.
  • Signed documents did not auto-complete their linked tasks — When an employee signed a document attached to an onboarding task, the task stayed in PENDING status until someone manually ticked it. Added autoCompleteTasksForSignedDocument which fires from the document sign handler, matches tasks by (documentId, instance.userId), flips status to COMPLETED, rolls up instance status, and dispatches the task-completed notification. Works for both the direct per-user clone and the source-template document cases.
  • Task list modal was too small and cut off the icon grid — The "New Task List" modal was max-width="sm" which made picking an icon require scrolling a 47-item grid inside a 256px container. Bumped to max-width="2xl" with a two-column layout (Name + Department side-by-side) and the icon picker given full breathing room below.
  • Shrine icon rendered as a blank squarelucide:shrine is not a valid Lucide icon name and showed empty in the picker. Removed it. Added ~85 additional icons covering HR & onboarding (user-plus, id-card, handshake, badge-check), IT & equipment (laptop, headphones, server, wifi, printer), access & security (key, lock, fingerprint, shield-check), finance & payroll (banknote, calculator, receipt), communication (mail, phone, video, megaphone), documents (file-signature, file-check, folder), facilities (building-2, factory, truck), sales & analytics (target, trophy, rocket, pie-chart), and common actions (check-circle, bell, settings).
  • Department heads could not manage their own team's task lists — Only ADMINISTRATOR and EXECUTIVE could CRUD onboarding templates, forcing every HR/IT/Sales lead to route changes through an admin. Added OnboardingTemplate.departmentId (nullable). Department heads can now create, edit, delete, and manage task lists scoped to their own department. Templates with departmentId: null stay admin/exec only. Enforced via the new server/utils/onboarding-access.ts helper across all template, taskdef, instance, and per-user-task endpoints.
  • Department heads could not see Onboarding / Offboarding in /settings — The settings sidebar and page wrappers had hard adminOnly gates. Introduced a deptHeadAllowed flag on SettingsNavItem and SettingsPageWrapper. Onboarding and Offboarding now opt in; department heads see both entries in their settings sidebar and can open either page.
  • DH saw all templates on the settings page — After unlocking the page for DH, they were seeing org-wide (admin-only) task lists they couldn't edit. Added ?manageableOnly=true to GET /api/onboarding/templates. The settings page passes this flag, so a DH only sees their own department's templates. The user-page Import/Start flow still fetches the full visible set (org-wide + dept) because DHs should be able to import admin-built lists for their team.
  • 403 on /api/onboarding/packets for department heads — Opening /settings/onboarding-templates as a DH crashed the data load because the packets endpoint is admin-only. loadData() now skips the packets fetch entirely for non-admins and hides the "New Hire Packet Templates" tab. If a DH lands on ?tab=packets directly, it coerces to tasks.
  • Misleading "All departments (admin-only)" dropdown label — The department selector in the New/Edit Task List modal didn't make clear which roles could manage the list. Replaced with "Who can manage this list" — "Administrators & Executives only" for no-department, and "{Dept name} department — Administrators, Executives & {Dept name} Head" for each option. Plus explanatory helper text below: "Administrators and Executives can always manage any list. Picking a department additionally grants that department's head manage access. Employees and department heads of other departments cannot edit the list." For DH callers, the dropdown is auto-pinned to their own department and disabled.
  • Task list cards had no spacing between them — Multiple onboarding/offboarding instances rendered flush against each other with no gap. Added mb-4 last:mb-0 to each instance card on both the Onboarding and Offboarding tabs.
  • Add Task button disappeared from the user's onboarding tab — When the flow was restructured to support multiple task lists per user, the header-level "Add Task" button (which ambiguously targeted the first instance) was replaced with a tiny + icon that was easy to miss. Each task list card now shows a clearly labeled "+ Add Task" button next to Remove, targeting that specific list unambiguously.
  • Per-user Add Task modal missed fields that existed in the template modal — The modal was missing Task List selector (required now that multiple lists exist per user), attached-document picker, and "Require signature before complete" option. Rebuilt to match the settings "Edit Task" layout: Task Name, Task List, Assign to, Category, Due Date, Description, Attach Document + signature gate. Intentionally omits the template-only "Import this task when onboarding (All/Some)" and "Update existing employee tasks" fields because per-user tasks only ever apply to one user.
  • Assigned Role dropdown in the per-user Add Task modal was redundant — The modal showed both an "Assign to" employee picker AND an "Assigned Role" fallback dropdown, which was confusing for per-user tasks. Removed — the server still defaults to 'HR' when not provided, and the template flow keeps the full role picker since template tasks may be authored without a specific user in mind.
  • Task completion emails only went to the assignee — When a task was marked complete (or auto-completed by a document signing), no one in HR/management learned about it. Added sendOnboardingTaskCompletedNotification which emails administrators, executives, AND the task assignee (deduped). Includes the employee name in the subject line and body.
  • Due-date reminders only went to one person — The 1-day-before / 3-day / 7-day reminder cron sent to the assignee if one existed, otherwise to the employee. Now fans out to all administrators + executives + the task assignee (deduped). Subject line includes the employee name so admins know whose onboarding it belongs to. The employee being onboarded is intentionally not reminded because they can't mark their own tasks complete.
  • Task removed → no notification — Deleting a task from an active instance silently disappeared the row. Admins/execs/DH now receive an email + in-app notification with the employee name, task title, assignee, and who performed the removal. Sent synchronously before the delete so details can still be read.
  • Instance removed → no notification — Same for removing an entire task list from a user's profile. Admins/execs/DH are now notified with the list name, task count deleted, and who removed it.
  • No audit trail for onboarding/offboarding settings changes — Template and task mutations were not appearing in /settings/security → Audit Logs. Added logOnboardingAudit helper that writes AuditLog entries with IP + user-agent for every CREATE/UPDATE/DELETE on ONBOARDING_TEMPLATE, ONBOARDING_TASK_DEF, ONBOARDING_INSTANCE, and ONBOARDING_TASK. UPDATEs include before/after snapshots. Task PATCHes distinguish reason: STATUS (marking complete/incomplete) from reason: EDIT (field change) so the audit feed is actionable.
  • No way to reorder tasks within a task list — Once a task was added, its sort order was locked unless you deleted and recreated it. Each task row in /settings/onboarding-templates and /settings/offboarding-templates now has a grip handle and is draggable="true". Drag to reorder; sortOrder is renumbered locally and each changed row is PATCHed. Optimistic UI — reverts on error.
  • Email CTA buttons used a hardcoded purple — All onboarding/offboarding email action buttons ("View My Tasks", "View Task", "View Onboarding", "View Profile") rendered #4f46e5 regardless of the organization's white-label branding. Added primaryColor to EmailConfig which reads CustomDomain.primaryColor (falling back to #3B82F6). All eight CTA buttons across the onboarding email templates now render the organization's brand color.
  • OnboardingInstance lost its identity if the template was edited or deleted — When an admin renamed or deleted a template, every instance using it suddenly showed "Onboarding" as its card title or crashed on dereference. Added templateName + templateIcon snapshots on OnboardingInstance. Each imported/started list keeps its own identity even after the source template changes. The UI prefers the snapshot (inst.templateName) over the live relation.
  • canCompleteTask in the frontend allowed the instance owner to complete their own tasks — Mirrored the backend fix in the taskDetail completion gate. The modal's "Mark Complete" button now requires admin/exec, the explicit assignee, or a matching department head — not just "is this your own instance".
  • Saving a time-tracking setting could fail with a 400 after the page was reloaded — Toggling any switch on /settings/timetracking (for example "Require Location") sometimes responded with "autoApproveAfterDays must be a number" and rolled the UI back to the previous state. The page hydrates its form by merging the full GET response into local state, so nullable numeric columns were being echoed back as null on save — which a strict type check was then rejecting. The save handler now treats null the same as an omitted field (skip), so the toggle-and-save flow works regardless of which optional settings happen to be unset on the row. Real type errors (strings in numeric fields, out-of-range numbers, invalid dates, unknown enum values) still fail with 400 as before.

Security & Compliance

  • Department-head scope enforcement — A department head cannot use a template scoped to another department (enforced on POST /api/onboarding/instances when they try to pass a templateId belonging to a different department). They also cannot re-scope a template between departments via PATCH.
  • Template-move authorization — Moving a task definition between templates (via PATCH /api/onboarding/templates/[id]/tasks/[taskDefId] with a new templateId) now re-runs assertCanManageTemplate against the target template's department. A DH cannot move a task into a template they don't own.
  • Document auto-complete cross-user safetyautoCompleteTasksForSignedDocument filters by instance.userId === signerUserId, so signing a document only completes the signer's own tasks — never another user's task that happens to reference the same source document.
  • DH departmentId self-pin on create — When a department head creates a new task list, the backend always pins departmentId to their own department regardless of what the request sent, closing the gap if the UI-level dropdown is manipulated.
  • Tenant isolation on time-entry and project mutations — Foreign-key fields written on time-entry create/edit (projectId, taskId) and project create/edit (managerId) are validated against the caller's organization at every write path.
  • Department-head scope on timesheet PDF export — When a department head runs the timesheet PDF, results are constrained to their department's users on every code path; an out-of-department userId filter returns 403 instead of an empty report so the caller knows the request was denied.
  • Project financials visibility — Hourly rates, budget amounts, and billed-amount stats on projects are restricted to Administrators, Executives, and Department Heads. Regular employees can still see hours and the hours budget on projects they're working on; the monetary values are stripped from the API response entirely for non-privileged callers.
  • Lockdown-override audit trail — Every admin/exec edit, create, or delete that bypasses the pay-period lockdown is recorded in the audit log with the entry's clock-in and the lockdown cutoff. Audit writes are fire-and-forget so a failed log never rolls back the user-visible action — the override itself still happens, but compliance always sees it.
  • Hardened external fetch in PDF generator — The optional org-logo embed in PDF reports validates the URL through the same SSRF guard used by webhook delivery (HTTPS only, blocks private and link-local addresses), with a fetch timeout and response-size cap. A broken or missing logo never fails the report.
  • Stricter input validation on time-entry edits — Break duration must be a non-negative finite number and cannot exceed the entry's total span. Invalid clock-in / clock-out ranges are rejected up front so downstream billing math never sees a negative duration.
  • Strengthened input validation across all time-entry endpoints — All mutation endpoints (create, edit, bulk update, duplicate, clock) now enforce stricter type checks, length limits on free-text fields, and range constraints on numeric settings. Invalid or out-of-range values are rejected early with clear error messages.
  • Improved multi-tenant isolation on write paths — Foreign-key references passed during create and update operations are now validated against the caller's organization before persistence, closing potential data-integrity gaps in multi-tenant environments.
  • Tighter scoping for department-level roles — Department heads are now consistently constrained to their own department's data across reports, PDF exports, and list endpoints. Org-scoped lookups ensure role-based filtering cannot be bypassed via direct API calls.
  • Hardened settings update endpoint — Boolean, numeric, and enum fields on the time-tracking settings endpoint are validated by type and range before persistence. Numeric settings enforce documented min/max bounds.
  • Soft-deleted entries excluded from aggregations — Report endpoints and project statistics queries now consistently exclude soft-deleted records, ensuring accurate totals and preventing stale data from surfacing in dashboards.
  • Sanitized custom CSS in branding — The branding endpoint strips potentially dangerous CSS constructs (dynamic URLs, imports, expressions, script bindings) from custom CSS before serving it to clients.
  • Bumped vulnerable transitive dependencies — Updated follow-redirects (1.15.11 → 1.16.0), protocol-buffers-schema (3.6.0 → 3.6.1), dompurify (3.3.3 → 3.4.0), and hono (4.12.12 → 4.12.14) via npm overrides to resolve Dependabot security advisories.

Performance

  • Search debounce (300ms)CommonSearchInput now debounces emit by 300ms, preventing re-filtering on every keystroke across all pages. Single fix, global impact.
  • Leaves endpoint server-side paginationGET /api/reports/leaves-data now supports page/limit params. Returns pagination metadata + summary aggregates (approved/pending/rejected counts + totalDays via groupBy). Runs count, groupBy, and findMany in Promise.all for zero extra latency.
  • LeaveTab server-side pagination — Stats show real totals from server aggregates (accurate across ALL data, not just current page). Page changes trigger re-fetch. Filters reset to page 1.
  • Projects N+1 query eliminated — Replaced per-project Promise.all(projects.map(aggregate)) (N individual queries) with a single timeEntry.groupBy({ by: ['projectId'] }) query mapped back by project ID. O(N) → O(1).
  • TimeTab server-side pagination — Uses serverTotal for real entry count and serverSummary for accurate hours across all pages (not just current page of 50).
  • Branding endpoint SMTP fallback/api/branding now falls back to SMTP_LOGO_URL env var when no white-label logo is configured, ensuring PDF exports always have a logo.
  • Reports overview now loads in one pass/reports was firing roughly seventy requests on mount to paint five stat cards and six charts. The summary strip now calls a single new GET /api/reports/summary-stats endpoint that returns all five KPI numbers via DB aggregates; the leave-by-employee chart reads the existing /api/reports/leaves-data endpoint once and groups clients-side instead of fanning out per user; the hours-tracked chart fetches its six monthly buckets in parallel instead of awaiting each in turn. Typical admin loads drop from double-digit seconds to sub-2s on a 50-user org, and the fan-out no longer scales with team size.
  • Approvals overview uses one counts endpoint — The /approvals page used to mount three hidden tab components purely to compute the "N pending" numbers on its stat cards — three full queue fetches for three integers. A new GET /api/approvals/counts endpoint returns { leave, expense, time } via DB counts in one hop; the hidden tabs are gone. Drill-down pages still own their own detailed fetches when the user actually clicks through.
  • Faster time approvals — Approving or rejecting a timesheet entry on /approvals/time used to re-fetch the whole queue just to see the row disappear. It now removes the row locally on success and only reverts on error, so the click feels instant regardless of how many pending entries there are. /approvals/leave and /approvals/expenses already worked this way; this brings timesheets in line.
  • Dashboard reads in parallel — The home dashboard's user-row query used to walk through five independent reads in sequence (leaves for the visible range, the year-to-date balance, previous-year leaves for carry-forward, holiday overrides, org holidays). They now run in a single Promise.all. No query shape changes — the cold-render latency on large orgs drops proportionally to the slowest query instead of the sum.
  • Schedule auto-generation cut from hundreds of queries to one — When a team has the default-schedule feature turned on, loading /schedules was doing a per-employee-per-day findFirst to decide whether to create a shift. A typical 50-person, 5-day week ran 250+ individual queries. A single findMany now covers the whole range and the existence check happens against an in-memory Map. Unrelated but in the same file: three other sequential reads (leaves, public holidays, schedule publications) now run alongside the main shifts query, and a duplicate org lookup further down the handler has been merged with the one at the top.
  • Leave balance handler parallelizes lookups — The three independent reads at the top of GET /api/leaves/balance (current user, organization settings, target user) now fire together. Permission checks still gate access to the target user's data for non-self callers.
  • People grid on /time-tracking/people uses DB aggregates — The endpoint was pulling every time entry for the week and for today and then looping in memory to compute per-user totals. It now uses prisma.timeEntry.groupBy for the week and day sums plus one narrow fetch for the handful of live active timers, which is all the UI needs for the running-clock badge. Wire payload drops from "all closed entries for the week" to "one sum per user".
  • Department filter on time reports pushed to the DB — The /time-tracking/reports endpoint had the same department filter expressed both in the where clause and again as an in-memory Array.filter after the fetch. The redundant in-memory pass is gone; admin-supplied ?departmentId=… now goes to the DB too so it doesn't scan rows just to throw them away.
  • Composable fetches in parallel — Department-head paths in useTimesheetCalendar and useScheduleCalendar used to await /api/users and then await /api/users/{id} in sequence when both can start immediately. Same on the cold-boot useDashboard call for departments + leave types. All three now fire in parallel via Promise.all. The tour/timesheet calendar also gets a microtask-level debounce around fetchEntries() so the cascade of watchers that wake up together when you toggle a range filter coalesces into one request instead of 2-3 races. Preferences load once per session instead of refetching /api/users/{id} on every calendar navigation.
  • Cross-tab notification dedup — The unread-count poller in useNotifications runs in every open tab. Each tab still polls (so a backgrounded tab can't go stale), but the result is now broadcast over a BroadcastChannel so sibling tabs update their local unread ref from the broadcast instead of all hitting the server independently. User with three tabs open → same load on the server as one.
  • Domain middleware merged into a single queryserver/middleware/domain.ts used to find the CustomDomain row and then, on a hit, do a second findUnique on the owning Organization to check plan/features. The org is now loaded via a relation include on the first call, so the cache-miss path is a single DB round-trip per unique host.
  • Request-scoped plan cache in feature-gate — Endpoints that run two or more feature checks in a single request (e.g. an expense-create that checks checkReceiptLimit and checkFeatureAccess) used to re-query the organization row for each check. A new internal getOrgBasicCached(event, organizationId) helper stashes the plan/features/limits shape on event.context for the duration of the request, so subsequent checks in the same handler are free. Callers that don't pass event keep the old behaviour — nothing breaks.
  • Reports charts consolidated — The /reports page's four chart fetchers (expenses, time, project, schedule) now all hit a single /api/reports/chart-data endpoint that runs everything as Postgres date_trunc GROUP BYs. The wire carries ~6 rows per chart no matter how much underlying data exists. On mount the four charts share one call; each chart's settings gear still refetches independently when its period changes.
  • Reports stats explainer — A single "?" icon in the /reports page header reveals a short card explaining what each stat card measures (Leave/Expense/Time are all-time, Projects is ACTIVE-only, Scheduling is current calendar year) and that charts default to six months. No per-card clutter.
  • Admin home dashboard — A new /api/admin/summary endpoint returns the platform-wide KPI strip (organizations, users, MRR, etc.) via Postgres aggregates in one call instead of a handful of per-metric reads. Caches in Redis for 60s; charts paint in tens of milliseconds.
  • Admin list endpoints/api/admin/organizations, /api/admin/refund-requests, and /api/admin/audit-logs now Redis-cache their list responses for 30s. The status filter on refunds is allow-list validated (unknown values used to silently return the full list); the org search string is length-capped at 100 characters.
  • Users list/api/users now accepts page, pageSize, search, departmentId, role, status, and sortBy query params. All filtering, sorting, and pagination is pushed to Postgres. The response includes a total count alongside the page of users. DEPARTMENT_HEAD callers are forced to their own department server-side even if they pass a different departmentId (gate preserved). Role-aware select — employees never receive HR-sensitive fields. Two new composite indexes ((organizationId, isActive, firstName) and (organizationId, departmentId, isActive)) back the common sort/filter paths.
  • Documents list/api/documents now uses Prisma _count relation aggregates for per-document signer counts instead of looping. Server-side pagination, filter, and sort. Response is Redis-cached for 30s keyed by (organizationId, userId, role, params) so an admin's cached view never serves an employee's scoped view. Two new composite indexes back the common filter combinations.
  • Calendar list endpoint/api/time-tracking/entries (used by the timesheets calendar + flat list) now has a short-lived Redis cache scoped by organization + role + department/user and the date window. The avatar field was dropped from the user select since the calendar never renders it — one less join, one less field to serialize. Date-range validation added to reject malformed inputs early.
  • Approvals detail pages/api/leaves/pending, /api/expenses/approvals, and /api/time-tracking/approvals/index all got: Redis-cached list responses, slim selects, server-side pagination bounds, and count + list + summary aggregated in one Promise.all (expenses queue). The status filter on time-tracking approvals is now allow-list validated.
  • QuickBooks dashboards/api/integrations/quickbooks/status, /api/integrations/quickbooks/employees/mappings, and /api/integrations/quickbooks/sync/history all got: Redis-cached responses with conservative TTL (10-60s), groupBy-based status summaries instead of looped counts, and explicit exclusion of OAuth credentials + raw QBO request/response payload blobs from the cached response. Admins see fresh status without every poll hitting the DB.
  • Cache-Control headers everywhere — Every new read endpoint sets Cache-Control: private, max-age=10-30 and an X-Cache: HIT|MISS telemetry header so ops can see cache effectiveness in DevTools.
  • Home dashboard cache/api/dashboard/users (the endpoint every user hits on login) now Redis-caches its full response for 30 seconds. The cache key scopes by organization, role, and — for dept heads and employees — the caller's department / user ID, so an admin's cached org-wide view can never be served to someone who should see less. The external Nager.Date public-holiday lookup that ran on every dashboard load is now memoized per (country, year) in Redis for 7 days — same holidays for every tenant with users in that country, fetched once per week.
  • Expenses list pagination/expenses used to request a thousand expense reports with full line-item / per-diem / mileage / receipt relations on page mount, even though the table only renders a page of ~50 rows. The endpoint now takes page / pageSize / search / status / reportType / userId / sort / startDate / endDate query params and does all pagination / filtering / sorting in Postgres. Heavy relations are opt-in via includeItems=true. Optional includeStats=true adds a count + total aggregate in two parallel queries. Response is Redis-cached for 30s with role + viewer-scoped keys.
  • Reverse-geocode cache/api/maps/geocode is hit on every location-aware clock-in / clock-out. External geocoding calls (~900ms each) are now cached in Redis for 7 days, keyed by coordinates bucketed to 3 decimal places (~111m cells). Clock-ins from the same office share a cache entry; clock-ins from different neighborhoods don't. No tenant prefix on the key — reverse-geocode responses contain only public address strings with no per-user data. Rate limiting and auth checks still run before every lookup; null results are intentionally not cached to avoid pinning a transient provider outage.
  • Billing subscription dedupe — Every component that called useBilling() used to get its own local state and fire its own /api/billing/subscription request, so the /billing page was making three or more parallel identical requests on mount. useBilling() now shares module-level state + an in-flight promise (same pattern as useBootstrap), so concurrent callers all await the same single request. One network round-trip per page load instead of three.

v1.0.9

Major HR Modules

This release rounds out the core employee experience with new modules for Performance, Benefits, Training, Onboarding/Offboarding, Assets, Certifications, and more.

New Features — Profile & Personal Data

  • Profile page redesign — Two-column layout with sidebar vitals (phone, email, LinkedIn, location, status, department, employee #, hire date, tenure, manager, direct reports), tabbed main content, and inline section editing. Each section has its own Edit button that swaps the read view for an inline form with sticky save/cancel footer.
  • Inline editing — Replaces the old modal-based edit flow on the profile page. Per-section edits keep changes scoped (basic info, social links, address, contact, emergency contact, employment, leave allowance, holidays).
  • Profile completeness indicator — Auto-calculated progress bar in the sidebar based on 13 key fields. Color-coded (red/amber/green) and hidden once complete. Recalculated on every read so it stays accurate.
  • Employees can view their own profile — Employees now have access to /users/{theirOwnId} to see and edit personal sections. View Files and Documents tabs are hidden for employees.
  • My Profile navigation — Replaced the old modal with a direct link from the user menu to the profile page.
  • Education — Add multiple education entries with institution, degree (10 types), major, GPA, and date range.
  • Languages — Track spoken languages with separate reading/writing and speaking proficiency levels.
  • Certifications — Add certificates with name, issuing body, credential ID, issued and expiry dates. Auto-marks as expired when past the expiry date. Daily reminder cron sends 30/14/7/0-day notifications + email to the employee and their manager.
  • Visa / Work Authorization — Track visa type, country, dates, status, and notes. Auto-expires past-due visas. Daily cron sends 30/14/7/0-day expiry reminders to employee + manager.
  • Assets — Track assigned company assets (laptop, badge, phone, etc.) with category, serial number, assigned date, return date, and condition. Searchable category dropdown with custom-category support.
  • Compensation history — Salary changes over time with effective date, type, amount, and reason.
  • Bonuses — One-off bonus records with date, amount, type, and notes.
  • Job history — Position changes (title, department, manager) tracked over time.
  • Employment status history — Status changes (Full-time, Part-time, Contract, etc.) over time.
  • Notes (HR) — Per-employee notes with three visibility levels: HR Only, Managers, Shared with Employee. Color-coded badges and full audit logging.
  • Termination tracking — Termination code, notice given date, projected termination date, ROE/SAT completed date. Inline editable on the Job tab (admin-only).
  • Marital status, nationality, shirt size — New fields on the personal tab.
  • Citizenship Certificate # and Benefit ID — Encrypted-at-rest fields, masked display, admin-only.
  • Home email, phone extension, home phone — Separate from work email and landline.
  • Social links — LinkedIn, GitHub, Twitter/X, Facebook with their own profile section.
  • Emergency contact enhancements — Full address, email, dropdown for relationship type.

New Features — Org Chart & People Directory

  • Org Chart page at /users/orgchart with three views: list (sortable table), directory (grouped by letter/department/location), and an interactive org chart tree.
  • People navigation link — Top-level nav for admin, executive, and department head roles.
  • Directory search — Search by name, title, email, or employee ID across all three views.
  • Division and Work Location fields — Added to user profile, API, and org chart.

New Features — Performance Management

  • Performance Settings at /settings/performance — Configure goal users (none / all / specific), cascading goals toggle, and 1:1 meeting enablement.
  • Goals — Per-employee goal cards with progress slider, status, due date, and category. Expandable cards show a comments section for collaboration.
  • Goal Comments — Threaded comments on goals with timestamps and author avatars.
  • 1:1 Meetings — Recurring 1:1 series with frequency (Weekly/Biweekly/Monthly/One-time) and first meeting date. Inline view on the profile page with agenda items, side-by-side notes, and private note filtering. Creation/cancellation emails to participants. Daily reminder cron sends meeting reminders with current agenda.

New Features — Training

  • Training Settings at /settings/training — Define training requirements with frequency (One-time, Annual, Every 2/3/5 years), categories, due-from-hire days, required-for filter (All / Department / Role / Specific Employees), URL field, and "allow self-complete" toggle.
  • Training auto-assign — Required training is automatically assigned to targeted users when created or updated, with calculated due dates from their hire date.
  • Profile Training tab — Category-grouped view with inline edit, mark complete, and dismiss actions.
  • Training reminders cron — 7/3/1 day reminder emails before due. Notifies employee and manager.
  • Training overdue cron — Detects past-due training, updates status, and notifies the employee + manager.

New Features — Benefits Administration

  • Benefits CRUD — Track benefit plans (medical, dental, vision, life, disability, retirement, etc.) with provider, policy number, coverage, premium, and effective dates.
  • Profile Benefits tab — View enrolled benefits with dependents, premiums, and effective dates.

New Features — Onboarding & Offboarding

  • Onboarding Settings at /settings/onboarding-templates — Two tabs: task lists (with icons, drag-to-reorder, per-task assignee, due date type, notification timing, target scope) and New Hire Packet Templates.
  • Offboarding Settings at /settings/offboarding-templates — Parallel page for offboarding task lists. Type-aware copy throughout ("On Last Day" vs "On Hire Date", "before/after last day" vs "before/after hire date").
  • New Hire Packet Templates — Configure arrival time, location, contact person, instructions, "Get to Know You" questions, and linked task lists.
  • Onboarding profile tab — Per-employee onboarding/offboarding instances with progress bar, task list, assignee names, color-coded due dates, and category badges. Add Task and Import Task List buttons for admins.
  • Auto-trigger on hire — When a new user is created, the default onboarding template auto-creates an instance with all tasks.
  • Auto-trigger on termination — When isActive flips to false, the default offboarding template auto-creates an instance.
  • Standalone /onboarding page — Active/Completed/Templates tabs with progress tracking, task completion, and a stats bar.
  • Template categories — Tasks can be tagged Documents, Equipment, Access, Training, or Other with color-coded badges.
  • Update existing employee tasks — When admin adds a new task to a template, optionally apply it retroactively to all employees with active onboarding for that template.
  • Document attachment to tasks — Admins can attach a document (employee handbook, NDA, policy) to any onboarding task and require it be signed before the task can be completed.
  • Per-user document copies — When a sign-required document is attached to an onboarding task, each new hire gets their own personal copy of the document. Each employee signs their own copy independently — no shared multi-signer PDF, no privacy leak between employees. Each employee can re-view their signed copy at any time.
  • Signature gating on task completion — Tasks with attached signing-required documents cannot be marked complete until the recipient has actually signed their copy. Admins can override.
  • Auto due-date calculation — Task due dates are computed from the employee's hire date (or end date for offboarding) using the task definition's "X days before/after hire date" rule. Falls back to "today in the org's timezone" for users without an explicit hire date so dates always match the operator's local calendar.
  • Per-task notifications — In-app and email notifications fire when:
    • An onboarding instance is created (employee gets the full task list)
    • A task is assigned to a specific user (assignee gets a notification)
    • A task is reassigned (new assignee gets notified)
    • An instance is completed (employee + admins get a completion email)
  • Daily task reminder cron — Sends 7/3/1/0 day reminders + 1 day overdue for every pending onboarding/offboarding task. Recipient is the assignee or, if none, the employee being onboarded. Skips offboarded users.
  • Task detail modal — Click any task to open a detail modal showing description, assignee, due date, category, attached document, and Mark as Complete / Mark as Incomplete buttons. The modal also surfaces signature status with a clear "Signature required" or "Signed" badge.

New Features — User Offboarding Lifecycle

  • Deliberate offboarding flow — Replaces silent isActive toggling with POST /api/users/:id/offboard. Required: end date, termination code. Optional: notice date, ROE completed date, notes, replacement department heads. Runs a transactional cleanup across leaves, time entries, expenses, document assignments, direct reports, department heads, 1:1 series, onboarding instances, projects, tasks, benefit enrollments, and future shifts.
  • Offboard previewGET /api/users/:id/offboard-preview returns counts of everything that will change so the operator sees a "what will happen" summary before clicking.
  • Reactivate offboarded userPOST /api/users/:id/reactivate restores login access (subject to seat limits) without undoing the cleanup.
  • Audit log entry — Every offboarding writes an OFFBOARD audit entry with the full input + cleanup summary.
  • Inactive user enforcement — Approval, time-tracking, and expense endpoints now consistently reject actions from deactivated users.
  • Cron filtering — Reminder crons (training, certification, visa, document, 1:1, onboarding) skip offboarded users so they don't receive emails after leaving.
  • Hard-delete disabledDELETE /api/users/:id now returns 410 Gone with a pointer to the offboard endpoint. Hard-deletion would orphan audit logs, leaves, time entries, and reporting chains. GDPR Right-to-Erasure remains available via the privacy data-deletion endpoint.

New Features — Auth & Sign-In

  • Stripe-inspired auth pages — Redesigned login, register, forgot-password, and reset-password pages with a modern card layout, brand-aware branding, and consistent footer.
  • Google sign-in / sign-up — Server endpoints, frontend callback, and route configuration for Google OAuth.
  • Microsoft OAuth fixes — Removed the consent prompt that was blocking non-admin users.
  • Forced logout toast — Users see a clear toast notification when their session expires instead of being silently kicked to the login page.

New Features — Dashboard & Calendar

  • Hire date and work anniversary on dashboard and calendar — Hire dates show on the calendar grid and in the dashboard's "Today" view. Work anniversaries (every year after hire) display as a separate badge with the year count.
  • Daily anniversary digest — Cron sends a daily anniversary email to admins celebrating today's work anniversaries alongside birthdays.

Improvements

  • Profile tab navigation — Tab bar is now horizontally scrollable on all screen sizes with edge fade gradients and (desktop) chevron scroll buttons. Active tab smoothly scrolls into view on click. Works on mobile without the previous "More" dropdown.
  • Settings navigation — Added Training, Performance, Onboarding, Offboarding, and Company Directory entries.
  • Documents Completed tab — Now shows a "Signed By" column instead of "From", so you can see who actually signed each document.
  • Documents list filtering — Per-user document copies are hidden from the admin's list view by default to avoid clutter. Each employee still sees their own copies, and admins can opt in to see all copies via a query parameter.
  • Email layout — Onboarding/offboarding emails include the full task list with assignees and due dates, plus a "View My Tasks" CTA button. All emails use the org's timezone for date display.
  • Notification deep links — Onboarding notifications now link to /onboarding (which works for everyone — admins, employees, and assignees) instead of profile-page tabs that were admin-only.
  • Inline document badges on tasks — Tasks with attached documents show a green "Signed" or amber "Signature required" badge directly in the task row, with a click-to-open link.

Billing & Usage

  • Document signing requests count toward billing — Each document file (1) plus each signing request (1 per recipient) counts toward the monthly document quota. A handbook shared with 10 new hires = 1 file + 10 signing requests = 11 toward the quota. The billing page shows the breakdown ("X files + Y signing requests") with a tooltip explaining the math.
  • Per-user usage attribution — The Usage Breakdown page now credits documents to the file owner and signing requests to the recipient (the employee who has to sign), not the admin who triggered the assignment. So compliance work appears against the right person's row.

Security & Compliance

  • PII encryption — New profile fields (citizenshipCertificateNo, benefitIdNumber, phoneHome, homeEmail, offboardedReason) are encrypted at rest using AES-256-GCM.
  • Sensitive ID display — Tax ID, citizenship certificate, and benefit ID display masked (e.g. XXX-XXXX-1234) and are visible only to admins.
  • Rate-limited Google OAuth — Server endpoints harden against brute-force with rate limiting and input validation.
  • Permissions-Policy — Allowed geolocation for own origin so the in-app location features work in modern browsers.
  • GDPR Right-to-Erasure expansion — Data anonymizer now clears termination, ROE, and offboarding fields when a user requests deletion.
  • Department head consistency — A user can only head one department at a time, enforced via a shared helper across all department endpoints.

Bug Fixes

  • Mobile sidebar scrolling — Fixed scrolling failure that left the bottom user menu unreachable on production and staging.
  • Guided tour leakage — Guided tour tooltips no longer appear on the wrong page after navigation.
  • Setup page scrollbar — Removed the scrollbar from the setup page on laptop screens.
  • Reset-password redirect — Visiting /reset-password without a token now redirects to /forgot-password instead of showing an error.
  • iCal feed URL undefined — Fixed the iCal feed URL returning undefined after a page refresh.
  • Empty-string date crashes — All API endpoints that accept date strings now safely handle empty strings instead of crashing.
  • Profile data load on tab navigation — Profile sub-tabs (training, performance, benefits, onboarding) now correctly fetch their data on initial URL visit.
  • Birthday and hire date timezone shift — Date-only fields use UTC date components throughout, so e.g. "Feb 25" no longer displays as "Feb 24" for users west of UTC.
  • Onboarding due date off-by-one — Due dates now calculate from "today in the organization's timezone" with UTC date arithmetic, so "1 day after hire date" correctly resolves to the right calendar day for users in any timezone.
  • Receipt limit reset test flakiness — Stabilized a flaky test that occasionally failed near month boundaries.
  • Sectional save isolation on the user profile — Editing one section of the user profile (Job, Personal, Contact, etc.) now reliably leaves the other sections untouched. Comprehensive regression coverage added.
  • Tenure calculation rewritten — The "Years of service" line on the profile job tab is now calculated through a dedicated utility (utils/tenure.ts) covering future hire dates ("Starts in N days/weeks/months"), the first day ("First day today"), short tenures (Nd), and longer tenures (years/months). 39 unit tests cover every boundary including new-year rollovers.
  • Training assignment targeting — The "required for" filter on training courses (All / Department / Role / Specific Employees) is now applied consistently on both create and update paths, so courses only assign to the intended audience. Auto-assignment logic centralized into a shared helper. 39 tests cover the targeting algorithm.
  • Profile training tab now respects course targeting — The training tab on a user's profile only displays courses that actually apply to that user.
  • Profile training tab cleanup — Removed an incomplete inline editor in favor of the canonical training editor under /settings/training. Added a read-only details modal so users can review training info directly from their profile.
  • Recording completed training is now instant — The "Record Completed Training" flow no longer requires a manual page refresh to see the new entry. Optimistic UI update + single-request creation.
  • Reports To picker respects role hierarchy — The "Reports To" dropdown on the user edit form now only suggests users at or above the employee's level in the hierarchy, excludes inactive users, and reacts live to in-form role changes. The currently saved manager is preserved even if outside the filtered list.
  • Document links from profile docs tab — Clicking a document on a user's profile documents tab now opens the right assignment for the current viewer in all cases, while preserving the existing per-user privacy boundary (non-admins still only see their own assignment).

Performance & Loading Experience

This release dramatically reduces the "split-second flash" of empty/fallback content that used to appear on first navigation, and replaces every page-level loading spinner with a layout-matching skeleton.

  • Single-request page bootstrap — Authenticated pages used to need several sequential round trips to load the small set of "always needed" data (org settings, timezone, leave types, departments, etc). These have been folded into a single bootstrap request that uses the existing JWT middleware and is org-scoped on every database query. The response is intentionally minimal — no employee data, no leave or balance information, no per-user-per-year content. Comprehensive regression tests lock the response shape and verify no sensitive data is included.
  • Session-scoped cache — A reactive cache holds the bootstrap response for the rest of the session, shared across every page. It is dedicated to a single in-flight fetch at a time, refreshed on a short TTL, cleared on logout (so nothing persists across user sessions), and automatically invalidated whenever the user saves a setting that would affect it.
  • Pre-warming on app start — A client plugin fires the bootstrap fetch right after auth is ready, so the first page the user lands on already has its layout chrome populated.
  • Auto-invalidation on settings mutation — When an admin saves a new timezone or other org-level setting and clicks another page in the sidebar, the new value is reflected immediately — no manual page refresh required. Implemented as a network-layer hook so individual pages don't have to remember to invalidate.
  • Calendar, dashboard, users, and approvals refactor — Each of these pages used to make multiple sequential network round trips in onMounted before the layout could render. They now run all initial fetches in parallel. Lookups that aren't immediately needed (like the department list for the Group Booking modal) are lazy-loaded only when the user actually needs them.
  • Dropped redundant auth-readiness waits — Several composables were waiting for auth-ready state inside individual fetchers, which added per-fetch latency on the hot path. The auth middleware already guarantees ready state by the time onMounted fires, so these calls were dead weight and have been removed.
  • Parallelized leave types load — Calendar pages now fetch leave types in the same parallel batch as leaves, balances, holidays, and locked dates instead of waiting for the parallel block to finish first.
  • Skeleton component library — New components/skeletons/ folder with a Skeleton primitive (rect/circle/pill/text shapes) plus 11 layout-matching composites covering list pages, detail pages, form pages, card grids, the calendar header and grid, the dashboard grid, the user profile, the schedule/timesheet board, and the org chart. All fully responsive (mobile-first Tailwind breakpoints). Reusable across the app via props.
  • Loading spinners replaced across the app — Every centered spinner that hid the page during first load is now a layout-matching skeleton that holds the layout shell. Pages updated: expenses (list + detail + reports + approvals), documents (list + detail), clients (list + detail), projects (list + detail), training, schedules, admin, onboarding, time-tracking timesheets, time-tracking approvals, users profile, users data drive, users orgchart (view-aware: orgchart/list/directory each get the right skeleton), settings overview, billing. Plus the approval tab components, reports tab components, and 20 settings sub-page components.
  • No more "no projects found" flash on /projects — The empty state used to flash for one frame on every navigation because the loading flag started as false. The initial value was flipped so the skeleton renders from the very first frame.
  • Org chart card and connector redesign — Larger node cards, bigger avatars, outlined role badge, bottom-right reports/collapse toggle, dot-grid card background, smoother bezier connector curves, search input with match dimming, expand-all/collapse-all buttons, and per-node collapse toggles.

Browser Compatibility

  • PDF rendering on older browsers — The document signing flow could fail on browsers released before early 2025 due to a JavaScript syntax feature used by the modern PDF.js build. Switched the application to PDF.js's officially supported legacy build, which targets ES2020 and works on every browser from 2021 forward (Chrome 88+, Firefox 78+, Safari 14+). The legacy build is built from the same source as the modern build and receives the same security patches in lockstep from Mozilla.
  • PDF.js version pinning — Tightened the dependency range so future patch releases land but a minor version bump (which could re-raise the minimum browser target) requires an explicit, reviewed upgrade.

v1.0.8

UI Redesign — Navigation & Settings

This release is a comprehensive redesign of the application's navigation and settings architecture, modeled after modern dashboard dashboard patterns.

New Features

  • Sidebar navigation — Replaced the horizontal header with a fixed left sidebar on desktop. Includes org logo, grouped nav sections (core, apps, management, admin), collapsible width with persisted state, user profile dropdown with logout, and theme toggle.
  • Mobile navigation drawer — Slide-in drawer from the left with backdrop blur, replacing the old hamburger dropdown. Sticky top bar with org logo, notification bell, and theme toggle on small screens.
  • Sidebar collapse with tooltips — Sidebar collapses to icon-only mode (68px) with hover tooltips showing labels. Collapsed state persists across sessions via localStorage.
  • Active route indicator — Sidebar nav items show a colored left accent bar on the active route, matching modern dashboard visual pattern.
  • Settings overview page — New card grid hub at /settings with grouped sections (Personal, Organization, Executive). Each card shows icon, title, description, and feature lock badges. Cards have shadow-on-hover effect with dark mode support.
  • Individual settings routes — Every settings section now has its own URL (/settings/password, /settings/general, /settings/quickbooks, etc.) instead of query parameters (?tab=password). 22 new route pages created.
  • Settings breadcrumb navigation — Each settings sub-page shows a back arrow linking to the settings overview, plus a page title with icon matching the overview cards.
  • Settings feature gating on sub-pages — Locked features show an upgrade prompt directly on the sub-page with plan badge and CTA button, consistent across all gated sections.
  • Inline QuickBooks sync history — The Sync History tab in QuickBooks settings now renders the full audit log inline with filtering and pagination, replacing the placeholder link.

Improvements

  • Documents page — sidebar removed — Replaced the left sidebar with horizontal horizontal tabs (My Documents, All Documents, Action Required, Completed, Drafts) with count badges and active underline indicator.
  • Documents quick links relocated — Templates and Bulk Send links moved from the sidebar to inline buttons in the page header, next to the Start dropdown.
  • QuickBooks sidebar → horizontal tabs — Converted the vertical sidebar navigation (Overview, Employees, Vendors, etc.) to a horizontal tab bar with a Disconnect button at the right end.
  • QuickBooks sync-history breadcrumbs — The full-page sync history now shows a breadcrumb trail (Settings → QuickBooks → Sync History) instead of a back-button card.
  • Settings backward compatibility — Old URLs (/settings?tab=password, ?changePassword=true, ?setup2fa=true, ?tab=calendar) automatically redirect to the new path-based routes.
  • Forced password/2FA on sub-routes — Middleware path matching updated from exact /settings to startsWith('/settings') so users aren't bounced away from settings sub-pages during forced flows.
  • Duplicate headers removed — Removed redundant title/description headers from all 20 settings components since the page wrapper now provides the heading.
  • Settings overview dark mode — Cards use gray-900 base with gray-800 hover and stronger shadow in dark mode for visual distinction.

v1.0.7

New Features

  • E-Signature Platform — Complete document signing system with drag-and-drop field placement, sequential and parallel signing workflows, typed or drawn signatures, and multi-signer support.
  • Drag-and-drop field placement — Place signature, initials, date signed, name, email, company, and title fields directly onto PDF documents. Fields are color-coded per signer.
  • Sequential and parallel signing — Configure signing order when assigning documents. Sequential mode notifies each signer only when it's their turn. Multiple signers can share the same order number to sign in parallel.
  • Typed signatures — Choose from 8 cursive font styles to generate a typed signature, in addition to drawing freehand. Signatures can be saved for reuse across documents.
  • Signature validation — Canvas signatures are validated for quality: minimum size, ink coverage, and stroke complexity. Trivial scribbles are rejected with a clear error message.
  • Audit trail PDF — Every signed document can be downloaded with a full audit trail appended, showing all signers' names, emails, timestamps, and signing status.
  • Document sharing without signature — Documents can be shared for review without requiring a signature. Recipients receive a notification and email.
  • Department head document permissions — Configurable read and create permissions for department heads. Create access implies read. Bulk Send remains admin/executive only.
  • Breached password detection — Passwords are checked against the Have I Been Pwned database in real-time as users type during registration and password changes. Breached passwords are blocked from being set. Existing users with breached passwords are notified via in-app notification and email on their next login.
  • Remember Me — Server-enforced session vs persistent login. Unchecked by default — users must opt in to stay logged in across browser sessions.
  • Leave approval from detail modal — Approvers can now approve or reject leave requests directly from the leave detail modal without navigating away.
  • Group booking restricted — Group leave booking is now restricted to administrators and executives only.

Improvements

  • Documents index redesigned — Sidebar navigation with My Documents, All Documents (admin), Action Required, Completed, and Drafts views. Admins and executives can see all documents across the organization.
  • Table layout rebuilt — Documents table uses a proper table layout with status badges, action buttons, and a Recipient column in Action Required view for admins.
  • Browser back button support — Switching between document views now uses pushState so the browser back button works correctly.
  • Signature timestamp on PDF — Each signature and initials field in the rendered PDF shows the signer's name and timestamp.
  • Email messaging for shared documents — Emails for non-signature documents say "shared a document with you" instead of "sent for signing."
  • Due date timezone handling — Due dates are now inclusive with a 24-hour buffer. A document due on March 20 is not marked overdue until March 21.
  • Dept head uploader filtering — When department heads upload documents, the user autocomplete is filtered to their department only.
  • Self-approval prevention — Users can no longer approve their own leave requests, even if they have approver permissions.
  • Dashboard scroll optimization — Scrollbar only appears on screens below 1320px width.

Security

  • Data encryption at rest — Sensitive personal data (phone numbers, addresses, tax IDs, emergency contacts, bank details, webhook URLs, leave reasons) is now encrypted at the application level using AES-256-GCM, in addition to database-level encryption.
  • HttpOnly cookie authentication — Refresh tokens are now stored in HttpOnly secure cookies instead of browser-accessible storage, protecting against cross-site scripting (XSS) token theft.
  • Security headers — Content Security Policy, Strict Transport Security, X-Frame-Options, Referrer-Policy, and Permissions-Policy headers are now set on all responses.
  • SSRF protection — Webhook URLs (Slack, Teams) are validated against private IP ranges, localhost, and cloud metadata endpoints before making server-side requests.
  • SSO domain verification — DNS-based domain verification for SSO prevents cross-organization domain hijacking. HMAC-signed state parameters protect against CSRF in SSO flows.
  • Breached password blocking — New passwords are checked against 900M+ known breached passwords via Have I Been Pwned before being accepted.
  • Auth gate hardening — Unauthenticated users no longer see a brief flash of authenticated UI. API requests are blocked immediately when no session exists.
  • Guided tour security — Tours no longer fire for unauthenticated users.

Privacy & Data Protection

  • Data export — Users can export all their personal data in machine-readable JSON format from their account settings.
  • Data deletion — Users can request deletion of their personal data. Data is anonymized to preserve organizational reporting integrity.
  • Data sharing opt-out — Users can opt out of third-party data sharing.
  • Data retention automation — Automated cleanup of data beyond the configurable retention period (default 7 years for tax/financial record-keeping).
  • Privacy policy updated — Added sections for legal basis of processing, international data transfers, data breach notification, children's privacy, and Do Not Track signals.
  • Cookie policy updated — Added details for authentication cookies and security cookies.
  • Terms of use updated — Added sections for data processing, data portability, and service availability.

Bug Fixes

  • Completed view duplicates — Fixed documents appearing multiple times when they had multiple signers.
  • Action Required filtering — Shared documents no longer appear in Action Required. View now only shows documents assigned to the current user.
  • Unassigned fields not rendering — Legacy fields with null assignment correctly render in the PDF.
  • Billing: immediate charge on upgrade — Plan upgrades and add-on purchases now charge proration immediately instead of deferring to the next invoice.
  • Settings access — Fixed /settings being blocked for non-admin users.
  • TOTP login flow — Fixed 2FA verification being blocked by the API interceptor during login.
  • Email verification resend — Fixed resend verification endpoint missing auth headers.
  • Password change token refresh — Fixed token handling after password change to prevent unnecessary logouts.

v1.0.6

New Features

  • Hourly / time-slot leave booking — Employees can now book leave for specific time windows (e.g. 09:00 - 13:00) in addition to full or half days. The system calculates the leave fraction automatically based on the employee's work schedule.
  • Per-leave-type "Allow hourly booking" toggle — Admins and executives can enable or disable time-slot booking on each leave type individually from Leave Type settings. Enabled by default for Working from Home and Paid Sick Leave. Shows a warning if no organization schedule is configured, directing admins to Time & Projects settings.
  • Schedule-aware time slots — When booking hourly leave, the available start/end times are generated from the employee's assigned shift, personal work schedule, or organization default hours (15-minute intervals).
  • Half-day visual indicators — Calendar and dashboard views now show a gradient-filled half circle for half-day leaves: morning off shows the left half colored, afternoon off shows the right half colored. Time-based partial leaves show a proportional bottom-up gradient fill.
  • Half-day and hourly leave tooltips — Tooltips on calendar days and dashboard cells now display "(Morning)", "(Afternoon)", or the actual time range (e.g. "09:00 - 13:00") for partial-day leaves.

Improvements

  • Leave balance display — Balance summary values (allowance, used, carried over, remaining) now display rounded to 2 decimal places, supporting fractional day tracking from hourly bookings.
  • Leave request modal — User avatar and name now always shown in the modal header. Paid/Unpaid status displayed as a colored pill badge (green for paid, amber for unpaid) instead of inline text.
  • View mode for time-based leaves — Leave detail view now shows start/end times, total hours with day equivalent, and a proportional gradient fill on the leave type badge.

Bug Fixes

  • Expense tab disappearing — Fixed an issue where toggling Company Card Expenses or saving any expense setting caused the Expenses tab to disappear from the header navigation until page refresh.

v1.0.5

New Features

Introducing MCP for BookYourPTO

  • Connect your AI assistant (Claude Code, Cursor, Claude Desktop) to BookYourPTO and manage PTO, expenses, time tracking, and more through natural language
  • 28 tools across leave management, expense reports, time tracking, calendar, approvals, org settings, and user profiles
  • Secure OAuth 2.0 login — sign in via your browser, passwords never touch the AI
  • One-click authorize when already logged in, full login with 2FA support otherwise

In-App Support

  • Submit bug reports, feature requests, general issues, and security vulnerabilities directly from the app
  • Attach files and screenshots to your support tickets
  • Rate limiting to prevent spam

Bulk Scanning

  • Bulk receipt scanning with redesigned scanner UI

Company Card Expense Settings

  • Enable/disable company card expenses per organization via Expense Settings
  • Role-based access control: Admins can allow all roles to create company card reports, or restrict to admins/executives only
  • Optional approval workflow: Company card expenses can optionally require approval before being marked as paid. When disabled, admins can mark as paid directly from draft status

QuickBooks Bill Payment Sync

  • When an expense is marked as paid and a Payment Account is configured, a BillPayment is automatically created so bills show as "Paid" in QuickBooks
  • If a bill's total changes after the initial payment, the amount is automatically updated on re-sync
  • Multi-currency support: payment amounts now correctly match the QuickBooks bill total regardless of currency

Tax & Gratuity in QBO Bills

  • Line item descriptions in QBO now include a tax breakdown and gratuity amounts for full financial visibility

Gratuity/Tip Field

  • Added gratuity/tip input to the bulk receipt import scanner (desktop and mobile)
  • Gratuity field available on individual purchase add/edit forms

Bug Fixes

  • Fix PDF receipts not viewable in purchases tab and receipts tab — PDFs now render inline instead of showing as broken images
  • Fix avatar errors cascading across all pages — gracefully falls back to initials when avatar file is missing
  • Fix calendar header misalignment on mobile
  • Fix dashboard mobile view — stack avatar and name vertically to prevent name overflow onto calendar dates
  • Fix email verification failing after registration
  • Fix support form not pre-populating when opened from a direct link
  • Fix expense data (purchases, per diems, receipts) disappearing after submit, approve, reject, or mark-paid actions
  • Fix QuickBooks Payment Account selection not persisting after page refresh
  • Fix "Sync All" not updating existing bills in QuickBooks for paid reports
  • Fix BillPayment amounts not matching bill totals for multi-currency expenses
  • Fix "All" status pill count not matching the number of visible expense rows

Theming / White-Label

  • Replaced all hardcoded purple/violet colors with theme-aware primary colors across Company Card UI elements

Improvements

  • QuickBooks integration now shows official logo in navigation (full color when active, grayscale when inactive)
  • Fix QuickBooks connect failing in production due to missing environment variable mappings

v1.0.4

New Features

QuickBooks Online Integration

BookYourPTO now integrates directly with QuickBooks Online, bringing your leave management, expense tracking, and time tracking into your accounting workflow. Available on Business and Enterprise plans.

  • OAuth 2.0 Connection: Securely connect your QuickBooks Online company with a single click. Disconnect at any time — your QBO data is never modified without your explicit action.
  • Employee Sync: Sync your employee directory between BookYourPTO and QuickBooks Online. Smart conflict detection compares records and highlights differences, so you always know what will change before it happens.
  • Expense Report Push: When an expense report is approved, it can automatically be pushed to QuickBooks Online as a Bill. Vendors are auto-created for reimbursements, with duplicate name handling built in.
  • Time Entry Push: Approved time entries are pushed to QuickBooks Online as TimeActivities, keeping your billable hours in sync with your accounting records.
  • Chart of Accounts Mapping: Map your BookYourPTO expense categories to your QBO chart of accounts. Auto-map suggestions make initial setup quick — just review and confirm.
  • Department & Client Mapping: Map your BookYourPTO departments, clients, and projects to their corresponding QBO entities for accurate cost allocation.
  • Leave Liability Journal Entries: Generate and push leave liability journal entries to QBO, helping your finance team track accrued leave obligations.
  • Sync History & Audit Trail: Every sync operation is logged with full audit history. Filter by sync type, status, and date range. Paginated history view keeps everything accessible.
  • Scheduled Sync: A configurable cron-based sync keeps your data up to date automatically across all active connections.
  • Settings UI: A dedicated QuickBooks settings section with a 7-panel sidebar covering connection status, employee mapping, account mapping, department mapping, sync history, leave liability, and payroll — fully mobile responsive.

Timesheets Redesign with Calendar View

The timesheets module has been completely redesigned. The old time board has been replaced with a modern calendar view that gives you a clear, visual overview of your team's tracked hours across the week or month. Navigate between days, weeks, and months with ease, and see at a glance who logged what and when.

Leave & Holiday Integration in Timesheets

Approved leave requests and public holidays now appear directly in your timesheets — no more switching between modules to understand why someone wasn't logging hours.

  • Calendar view: A compact leave summary strip sits between day headers and the time grid, showing per-day leave pills. When 4 or more employees are on leave, it collapses to a clean "X on leave" summary with a hover tooltip listing everyone.
  • Calendar view: Public holidays are highlighted with emerald-colored labels and subtle column tints, so holiday days are instantly recognizable.
  • List view: Holiday badges appear on day cards, with an emerald banner for selected holidays and an amber leave summary showing employee name pills.
  • Mobile view: Holiday dots appear on day tabs with a leave count in each day header.
  • Top bar: New "on leave" and "holiday" summary stat chips give a quick snapshot.
  • CSV export: Enhanced with a new Type column (Time Entry / Leave / Holiday), leave rows include portion info, a holiday column, and a per-employee summary section at the bottom of the export.

Scheduling System Redesign & User Detail Page

The scheduling interface has been overhauled with a fresh design. A new /user/:id detail page gives managers and admins a dedicated view for any team member, consolidating their schedule, leave history, and profile information in one place.

Per-Leave-Type Privacy Controls

Leave types can now be marked as private, giving organizations control over which leave information is visible to other employees.

  • When a leave type is marked private, other employees will see a gray "Private" label with an eye-off icon instead of the leave type name, reason, or notes.
  • Role-based visibility: Employees see only their own leave details. Department heads see their own department. Admins and executives see everything.
  • Annual Leave and Sick Leave default to private during onboarding.
  • A new isPrivate toggle is available in the leave type create and edit forms.
  • Leave balance badges are hidden from unauthorized viewers for private leave types.

Digest Privacy Filtering

Email digests now respect per-leave-type privacy settings. Private leave types are masked in digest emails using the same role-based rules as the dashboard — employees only see their own private leave details, department heads see their department, and admins/executives see all.

Smart Digest Scheduling

Daily digests are now smarter about when they send:

  • Business day awareness: Digests skip non-business days based on your organization's configured business days (e.g., no digest on Saturday/Sunday if your org runs Mon–Fri).
  • Holiday awareness: Digests are also skipped on public holidays specific to each user's assigned holiday calendar.
  • Upcoming holidays: Daily digests now include an "Upcoming This Week" section with a green-highlighted list of public holidays in the next 7 days, so your team knows what's coming.

Redesigned Welcome Email Flow

When admins add new users, the experience is now cleaner and more secure:

  • New users receive a "Set Up Your Account" email with a secure 24-hour setup link instead of a temporary password.
  • A new dedicated /setup-account page provides a streamlined onboarding experience — simpler than the standard password reset flow.
  • The Add User modal no longer displays or copies passwords. The success state now shows a clear "setup email sent" confirmation.
  • Password change and account setup flows now issue fresh session tokens automatically, so users don't need to log out and back in after completing setup.

Department Head Leave Allowance Editing

Department heads can now edit leave allowance settings (custom allowance, carry forward, manual carry over) for members within their department, giving them more autonomy over day-to-day team management without needing to involve an admin.

Expense Submission Notifications

When any expense report is submitted, all administrators and executives in the organization now receive an in-app notification — not just the assigned approver. This ensures visibility across leadership and prevents expense reports from getting stuck in a single approver's queue.


Bug Fixes

Fixed: Date of Birth Displaying the Wrong Day

Dates of birth and start dates could appear one day off depending on the user's timezone (e.g., October 19 showing as October 18). This was caused by the browser interpreting UTC midnight dates in the local timezone. Dates are now displayed in UTC consistently for date-only fields, so the displayed date always matches what was entered.

Fixed: Department Heads Seeing Their Own Expense Claims in Approvals

Department heads and managers were seeing their own expense claims in the approvals list, inflating pending counts and creating confusion. Their own claims are now excluded from the approvals view — they'll only see claims from team members they can actually approve.

Fixed: Expense Approval Counts for Department Heads

The pending approvals badge count on the expenses page was computed from the general expense list, which for department heads included their own pending claims. The count is now derived from the dedicated approvals endpoint with proper role-based scoping.

Fixed: Expense Report Controls Visibility

Non-owners could see edit and delete controls on draft expense reports. These controls are now properly gated behind submitter or admin role checks.

Fixed: Expense Reports User Filter for Department Heads

The user dropdown on the expense reports page now correctly scopes to department members for department heads, instead of showing all organization users.

Fixed: Avatar PNG Upload Failing on Windows

PNG file uploads were failing on Windows because certain browsers report an empty MIME type for PNG files. The upload flow now falls back to file extension detection on the client, and the server uses magic byte detection to confirm the actual file format regardless of what the browser reports.

Fixed: Avatar Not Updating Immediately After Upload

After uploading a new avatar, the old image would persist until a page refresh. The avatar component now properly invalidates its cache after upload, and all instances across the page refresh immediately with the new image.

Fixed: Avatars Not Loading Across the App

Avatars were failing to load in various components because they were using raw storage keys as URLs instead of the authenticated API endpoint. All 18 components that render avatars now use the authenticated fetch path, with a shared blob URL cache to avoid redundant network requests.

Fixed: Avatars in Expense List Views

Expense cards and table rows were rendering broken avatar images. These now use the standard UserAvatar component with proper authenticated loading.

Fixed: App Randomly Getting Stuck on Loading Spinners

Users were experiencing a frustrating bug where parts of the app would get stuck on infinite loading spinners, requiring a manual page refresh (sometimes twice) to recover. This happened randomly across the entire app — dashboard, settings, documents, time tracking, everywhere.

What was happening: When multiple API requests fired simultaneously with an expired authentication token, the first request would successfully refresh the token, but late-arriving responses from other requests would trigger redundant refresh attempts. This cascade left some requests unresolved, causing permanent loading states.

What changed: The app now detects when a token has already been refreshed by another concurrent request and skips unnecessary refresh attempts. All pending requests silently retry with the new token. Additionally, authentication headers are now managed in a single place, ensuring retries always use the freshest token.

Impact: Pages now load completely and reliably every time. Users can leave the app open, come back after their session token expires, and their next action works seamlessly — no more random loading freezes.

Fixed: "Takes X Days" Preview Not Counting Business Days Correctly

The leave request modal's "Takes X days" preview was counting raw calendar days instead of business days. It now uses your organization's configured business days, timezone, and public holiday calendar to give an accurate count. This also fixes an issue where holidays could shift to the wrong day in western timezones due to UTC date conversion.

Fixed: Session Becoming Unresponsive After 2FA Setup

After completing two-factor authentication setup, all API calls would fail because the session still carried outdated authentication state. Users had to log out and log back in to get a working session. The system now issues a fresh session immediately after 2FA setup completes.

Fixed: Department Head User List Showing All Organization Users

The user management page was showing all organization users to department heads instead of only their department members. Additionally, action buttons (edit, etc.) weren't appearing even when the department head had permission. Both the filtering and permission checks now work correctly.

Fixed: Email Addresses Getting Truncated in User List

Long email addresses were getting cut off in the user table. The email column has been widened, and a native hover tooltip now reveals the full address. The Add User modal success summary also now shows the full email without overlapping adjacent fields.

Fixed: Weekly Digest Duration Labels

Weekly digest emails were showing redundant duration information for leave entries that span multiple days. The weekly view now shows the employee name and pending status badge only, since the same leave naturally repeats across multiple days in the weekly layout.

Fixed: Digest Date Calculations Using Wrong Timezone

Digest date range calculations were using UTC midnight instead of the organization's configured timezone, which could cause off-by-one errors in which leaves appeared in a given digest. Date calculations now consistently use the organization's timezone.

Fixed: Settings Page Not Loading After 2FA Setup

The settings page could fail to load in certain scenarios after completing 2FA setup due to a route matching issue. The route matcher now correctly handles both the settings root and all sub-routes.

Fixed: Settings Navigation Items Briefly Showing Locked State

Settings navigation items would briefly flash lock icons on page load before the subscription data finished loading. The feature-locked check is now deferred until the subscription data is available, eliminating the visual flicker.

Fixed: Stale Authentication in Time Tracking and Expense Settings

The time tracking settings and expense settings pages were capturing authentication tokens at call time instead of using the app's automatic token injection. This meant that after a token refresh, these pages would retry with the old expired token. They now use the standard automatic authentication flow.


For questions or feedback about this release, contact support@anhourtec.com.

v1.0.3

New Features

Per-User Holiday Override Toggle

  • Added a "Use custom holidays instead of organization defaults" toggle to the Edit User > Holiday Overrides tab
  • When enabled, the user no longer follows the organization's public holidays — admins can configure a custom country, exclude specific holidays, or add custom ones
  • When disabled, the toggle clears any previously set custom holiday country/region and the user reverts to organization defaults
  • Dashboard calendar, leave requests, public holidays, and email digests all respect the new setting

Carry-Over Expiry Date

  • The carry-over balance now supports an optional expiry date — expired carry-over days are automatically excluded from balance calculations
  • Expiry is evaluated using the organization's timezone to prevent off-by-one date issues across time zones
  • The expiry date is cleared automatically when carry-over balance is set to zero

Improved Leave Balance Calculations

  • Manual carry-over adjustments are now included on top of automated carry-forward calculations in balance views
  • User-specific carry-forward day limits now correctly override the organization-level cap
  • Carry-forward eligibility threshold is now exposed in organization settings for accurate frontend display

Bug Fixes

Date of Birth Timezone Handling

  • Fixed date of birth shifting by one day in western timezones (e.g., Feb 25 displaying as Feb 24) by using the organization's timezone instead of UTC
  • Prevented selecting future dates in the date of birth picker

Department Filter Toggle

  • Fixed the users filter modal where unchecking "All departments" did not deselect individual department checkboxes

Leave Balance Carry-Forward Edge Case

  • Fixed an issue where setting a user's custom leave allowance to 0 was incorrectly treated as "use organization default" instead of zero

Guided Tour Popover Appearing After Logout

  • Fixed guided tour popovers occasionally persisting on the login page after signing out
  • Tour state is now fully reset on logout, preventing stale tour data from carrying over between sessions

Improvements

Quick Edit via Avatar Click

  • Clicking a user's avatar on the Users page now opens their Edit Profile modal directly
  • Available for Administrators, Executives, and Department Heads (restricted to their own department members)

Leave Allowance Tab

  • Simplified the Edit User leave allowance tab with a cleaner, settings-focused layout

SSO Domain Validation

  • Added validation to prevent public email domains (e.g., gmail.com, yahoo.com) from being added to SSO allowed domains
  • Instant client-side feedback with inline error messages

Plan Downgrade Handling

  • Paid integrations (notification channels, calendar sync) now gracefully pause when an organization downgrades and automatically resume on re-upgrade without losing configuration

v1.0.2

BookYourPTO v1.0.2 — Release Notes

Trusted Devices — Skip 2FA for 30 Days
Employees can now check "Remember this device for 30 days" when logging in with 2FA. Trusted devices can be viewed and revoked anytime from Settings > Trusted Devices. Disabling 2FA automatically revokes all trusted devices.

Slack & Teams Notifications Get your BookYourPTO notifications delivered straight to Slack or Microsoft Teams.

  • Personal: Each employee can connect their own webhook in Settings > Connected Apps > Slack & Teams to receive personal notifications (leave approvals, rejections, etc.)
  • Organization-wide: Admins can set up a shared channel for team notifications in Settings > Notifications Channel — pick which notification types get posted

Settings Redesign Calendar Integration and Slack & Teams are now combined under a single "Connected Apps" tab with a cleaner tabbed layout.

v1.0.1

Birthday Indicators

  • Users with a date of birth now see a 🎂 pink cake icon on their birthday in the /calendar yearly view and dashboard calendar grid
  • Hovering shows a "Birthday" tooltip (dashboard shows full name)
  • Birthday cells get a subtle pink border and background highlight

Balance Display Settings

  • Dashboard badge — controls which leave balance is shown on each user's avatar badge (Annual / Sick / Both)
  • Calendar balance summary — controls which leave buckets appear in the calendar sidebar (Annual / Sick / Both)
  • Configurable from Settings > General by administrators and executives

Birthday Reminder Emails

  • Monthly cron endpoint sends a birthday digest email on the 1st of each month to admins, executives, and department heads
  • Lists all team birthdays sorted by date with name, department, and date
  • Department heads only receive birthdays from their own department

Dashboard UX Improvements

  • User names in the dashboard are now clickable links to their /calendar page (for admins, executives, and same-department heads)
  • Weekend columns have consistent muted background across the full column height

v1.0.0

We're excited to announce the first official release of BookYourPTO — an all-in-one leave, time tracking, expense, and document management platform for modern teams.

Available as an open-source community edition for self-hosting and a cloud-hosted version with Pro, Business, and Enterprise plans at bookyourpto.com.

Development began on December 22, 2025, and v1.0.0 marks the culmination of six weeks of active development.


Core Platform (All Plans)

Leave Management

  • Submit, approve, reject, and cancel leave requests with full workflow support
  • Configurable leave types with custom allowances and accrual rules
  • Leave balance tracking with fiscal year awareness
  • Leave approvals and policies
  • Dashboard calendar with monthly navigation and team-wide visibility
  • Leave transaction audit trail

Time Tracking

  • Clock in/out with real-time timer
  • Manual time entry creation, editing, and deletion
  • Project and task management with client association
  • Billable hours tracking
  • Work schedules with customizable shift patterns
  • Timesheet approvals with individual and bulk actions
  • Geolocation capture for clock-in/out events

Expense Management

  • Expense submission with AI-powered receipt scanning
  • Mileage and per diem expense types
  • Receipt upload with secure access via short-lived Redis tokens
  • Expense approval workflow
  • Expense reports with filtering and export

Digital Document Signing

  • Upload and assign documents to single or multiple recipients
  • ESIGN Act compliant digital signature capture
  • Searchable documents with OCR
  • Document preview, view, sign, and decline workflows
  • Browser timezone capture displayed on signed documents
  • Document encryption for sensitive files

Notifications

  • In-app notification system with real-time bell indicator
  • Notifications for leave submissions, approvals, rejections, and cancellations
  • Notification management page with mark-as-read, clear-all actions
  • Pending items widget on dashboard with role-based display

Reports & Exports

  • Leave usage reports with filtering and export
  • Timesheet reports with date range selection
  • Time tracking billing reports
  • Security audit log export (CSV)
  • Sign-in log export

Calendar Integrations

  • Google Calendar sync for approved leave events
  • Microsoft Outlook calendar integration
  • iCal feed support for any calendar application

Role-Based Access Control

  • Four roles: Employee, Department Head, Administrator, Executive
  • Department Head role for team-level approvals and oversight
  • Role-based permissions across all modules
  • Granular permissions system with per-user overrides
  • Designate approvers with cross-department approval capabilities
  • Locked dates to prevent bookings on specific dates (admin/executive controlled)
  • Executive override for booking on locked dates
  • Project member roles: Owner, Manager, Member

Authentication & Security

  • JWT-based authentication with refresh token rotation
  • Force password change on first login
  • Redis rate limiting on authentication endpoints
  • Token audit logging
  • Sign-in logs with export

Pro Plan Features

  • Up to 10 users
  • 500 receipts/month
  • 1,000 documents with OCR
  • Email support

Business Plan Features

  • Up to 30 users
  • Unlimited receipts and documents with OCR
  • Advanced approval workflows
  • Custom reports
  • Email digest notification system with daily/weekly scheduling
  • Multi-currency expense support with role-based visibility

White-Labeling

  • Branding settings with logo, colors, and theme configuration
  • Domain-specific branding (custom domains show org branding)
  • Branding API with cache-control headers

Auth0 Integration (OIDC)

  • Enterprise Single Sign-On via Auth0
  • Configurable "Enable Auth0" and "Require Auth0 Login" settings
  • SSO enforcement option for organizations
  • Auto-create users on first SSO login with configurable default role and department

Audit Logs

  • Comprehensive audit logs with entity types for all modules
  • Security violations tracking and management
  • Time tracking compliance dashboard

Custom Domains

  • Custom domain support with DNS verification
  • Single custom domain per organization enforcement
  • Public branding endpoint for unauthenticated access

Priority Support

Enterprise Plan Features

  • Unlimited users
  • Everything in Business
  • Custom limits
  • Dedicated support
  • SLA guarantee

Stripe Billing

  • Subscription management with plan-based feature gating
  • Checkout session creation with discount/coupon support
  • Invoice tracking and billing history
  • Webhook processing with idempotency protection
  • Plan limits enforced across all API endpoints
  • Plan-based UI gating in settings
  • Refund processing with hardened input validation
  • Free plan default for new organizations

Email System

  • Configurable SMTP settings per organization
  • Test email functionality for verifying configuration
  • Branded email templates with organization logo support
  • Fallback to platform SMTP logo when org has no custom logo
  • Email notifications for leave workflows, document assignments, and more

Organization & Multi-Tenancy

  • Multi-tenant architecture with organization-scoped data isolation
  • Organization onboarding workflow
  • Department management (create, update, delete)
  • User management with role-based access control (Employee, Department Head, Administrator, Executive)
  • Organization statistics dashboard
  • Data backup/export endpoint

Public Holidays

  • Country-based public holiday management
  • Regional subdivision holiday support
  • Per-user holiday override system
  • Dashboard calendar displays user-specific holidays

Infrastructure & Deployment

  • Docker containerization with multi-stage builds
  • Docker Compose with PostgreSQL, Redis, and application services
  • Environment variable configuration for all services (Stripe, SMTP, OAuth)
  • Persistent document storage via Docker volumes
  • Nuxt 3 full-stack framework
  • Prisma ORM with PostgreSQL
  • Redis for caching, rate limiting, and session management
  • Cloudflare integration for domain management

UI/UX

  • Responsive design across all pages (mobile, tablet, desktop)
  • Dark/light mode support
  • App Launcher navigation for unified approvals and reports
  • Sticky dashboard calendar with team visibility
  • Custom modal components with scroll support and mobile optimization
  • Area charts for analytics visualization