Skip to content
~/tariqul.islam
/projects

/projects/hr-time-tracking

2021–2024

Remote-team HR with screen recording & time tracking

client:
Spondon IT — internal platform for a fully remote company
role:
Developer → TPM
  • Laravel
  • Vue.js
  • MySQL
  • AWS S3

01 — the problem

What was actually at stake

A fully remote team needs what an office gives for free: visibility into whether work is actually happening — without a daily interrogation. Spondon ran its whole delivery operation remote, so the internal HR system had to track tasks and time with screen-recording evidence, while staying fair to the people being recorded and cheap on their laptops and bandwidth.

02 — the architecture

How it was built

Capture clients batch screenshots and activity samples locally, then upload to S3 via short-lived signed URLs — the application server never proxies media bytes. Time entries reconcile client clocks against server windows so offline stretches and clock drift don't corrupt timesheets.

Sessions link to tasks, so a manager reads a timeline of work-against-tickets rather than raw surveillance footage; retention policies expire raw artifacts while billing-grade summaries persist. The same HR core later carried the company's standard modules — attendance, leave, payroll inputs.

architecture — at a glance
upload
pre-signed S3, zero server proxying
time
drift-tolerant reconciliation windows
context
sessions linked to tasks, not raw footage
retention
artifacts expire, summaries persist

03 — the visuals

What it looks like

Illustrated architecture overview of Remote-team HR with screen recording & time tracking
fig. 01 — /projects/hr-time-tracking/cover

screenshot pending (NDA-safe crop)

drop /public/images/projects/hr-time-tracking/01.jpg · 1600×900 · see docs/images.md

fig. 02 — screenshot, primary view

screenshot pending (NDA-safe crop)

drop /public/images/projects/hr-time-tracking/02.jpg · 1600×900 · see docs/images.md

fig. 03 — screenshot, detail view

04 — from the codebase

Issuing short-lived signed upload slots

Issuing short-lived signed upload slots
public function reserve(UploadBatchRequest $request): JsonResponse
{
    $session = $request->session();

    $slots = collect($request->files)->map(fn ($meta) => [
        'key' => $session->artifactKey($meta['checksum']),
        'url' => Storage::disk('s3')->temporaryUploadUrl(
            $session->artifactKey($meta['checksum']),
            now()->addMinutes(10),
        ),
    ]);

    // Ledger first, bytes later: a slot that never lands is auto-reaped.
    $session->artifacts()->createMany(
        $slots->map(fn ($s) => ['key' => $s['key'], 'status' => 'reserved'])
    );

    return response()->json(['slots' => $slots]);
}

05 — the outcome

What the numbers say

remote company, tracked & trusted
100%
media bytes through the app server
0
timelines instead of raw surveillance
task-level

Remote stopped being a leap of faith — for management and for the team.

/projects/hr-time-tracking/next

Need something in this neighborhood?

If this case study sounds like your problem — same domain, same scale, same kind of mess — I've already made the expensive mistakes for you. Tell me where you're stuck.