Improve workforce scanning, site transfers and timesheet exports.
Audit plant imports, duplicate identities, rates and dashboard totals.
Repair delivery PO selection, periodic rechecks and receiving guards.
Align financial ledgers, supplier periods and mobile layouts.
Add the site map preview, shared theme controls and audit evidence.
Verify 3,455 application tests, six fuel export tests, TypeScript and
an optimized production build. Clean up a leaked test timer and keep
local worktrees and generated reports out of ESLint traversal.
Changed-file lint passes; repository-wide lint retains 33 existing
errors in unchanged files.
Removed the DialogFooter component and restructured the ConfirmDialog layout for improved usability. Increased AUTO_DISMISS_MS from 5 seconds to 30 seconds and adjusted DOUBLE_CONFIRM_WINDOW_MS from 4 seconds to 6 seconds to enhance user experience during the scanning process. Updated button styles for better visibility and interaction. This change aims to streamline the confirmation flow and reduce accidental dismissals.
Enhanced the PersonScanner component to support a two-step flow for scanning employee badges. The new process includes a lookup phase to identify the employee and their last scan status, followed by a confirmation phase where the operator can verify the scan before recording the clock event. This change addresses issues with accidental double scans and improves user feedback with warnings for recent scans. Updated state management and error handling to reflect the new flow, ensuring a smoother user experience.
Updated the vehicle scan API to include optional GPS coordinates (latitude, longitude) and location accuracy. The API now validates these fields, ensuring both latitude and longitude are provided together and within valid ranges. Enhanced error handling for invalid inputs and updated the API reference documentation accordingly. Additionally, modified the FleetScanner component to capture GPS data and reflect its status in the UI.
Introduced three new server-only API endpoints for the MEGA Clock PWA:
- `POST /api/clock/scan` for clocking workers in and out via badge scanning.
- `POST /api/clock/vehicle-lookup` for looking up vehicle details using QR codes.
- `POST /api/clock/vehicle-scan` for logging odometer readings from scanned vehicles.
Updated the README and API reference documentation to include these new endpoints and their functionalities. Replaced the placeholder for fleet scanning in the ClockView component with the new FleetScanner component.
A delivery note that runs to two or three sheets has to arrive as ONE
batch. Sent a page at a time it became separate batches, and then
nothing could tell that page 2's line items belonged to page 1's note —
the totals sat on one batch and the items on another, with no way back.
Pages now accumulate. Take one, then Retake, Add page, or Send all.
A thumbnail strip shows what has been taken so far, ringed green or red
by how each page measured, so a mis-shot can be dropped without starting
the document over and nobody has to remember whether they got page 2.
Several pages are bundled into a single PDF and enter the pipeline
exactly like a scanned multi-page document: split.ts rasterises each
page, extract.ts reads each in isolation, and segment.ts decides from
the printed document numbers whether it is one note over three pages or
three separate notes. The person holding the phone does not have to know
which, which matters, because standing next to a truck they often can't.
The bundle is verified against the pipeline's own tools rather than
assumed compatible: the test writes one and runs pdfinfo and pdftoppm
over it — the exact binaries split.ts shells out to — checking the page
count, that every page rasterises, that portrait stays portrait, and
that page order survives. A bundle the pipeline could not open would
otherwise fail at the far end of an upload, on site, after the truck had
gone.
One page still uploads as its own JPEG: smaller, and it never loads the
PDF library at all. pdf-lib is imported dynamically and webpack keeps it
in a 412K chunk of its own, leaving the clock page's bundle at 24K —
checked in the build output, not hoped for.
Size is checked before sending, mirroring MAX_FILE_BYTES on the upload
route, so an oversized bundle is caught while the document is still in
front of the person rather than as a 400 afterwards.
1027 tests, typecheck clean, production build clean. The capture path
itself still needs a real phone — the browser pane here has no camera
and cannot reach localhost.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds a third tab to the clock PWA: point it at a delivery note or
invoice, take the photo, send it. It produces one dn_batches row per
photograph — the same thing the office upload button produces — so
splitting, PO matching, the checks and the board all already handle it.
One photograph per send, which sidesteps the multi-note splitting that
causes most of the trouble with scanned stacks.
Focus is measured, not hoped for. A soft photo does not fail loudly; it
extracts almost right — a 3 that reads as 8, a decimal comma lost — and
those become numbers a supplier gets paid on. Variance of the Laplacian
runs on the live preview four times a second and again on the frame
actually captured, because the shutter frame is not the one measured
250 ms earlier.
The thresholds were read off a real delivery note rather than invented.
The fixture's three pages rasterised at 800 px score 4825/4863/4801 as
scanned, then 322, 88, 41 and 16 through one to five box-blur passes.
Two passes is where the print starts to fight you, three is unreadable,
so the bands sit at 120 and 45. Those are flatbed scans and a handheld
photo will score well below 4800, which is why the bands come from the
degradation ladder and not from the top figure.
One check had to be deleted rather than tuned: "washed out by glare"
fired on every good document. A clean delivery note is 95.2% pure white
and reads 246 mean luminance, so any upper brightness threshold that
catches a blown-out photo also condemns the real thing. Real glare is a
local hotspot, not a global mean, and detecting it needs samples we do
not have. Measured, not assumed — see the test.
The frame is cyan at rest, not the app's orange: a delivery note is warm
white and orange on cream disappears exactly where it needs to be seen.
It turns green when the photo will read and red when it will not, so red
means something instead of being the colour of the frame. A bad photo
can still be sent — the truck does not wait, and a soft record of a real
delivery beats no record of it — the button just stops looking like the
obvious thing to press.
Site comes from geolocation because it has to: badge scanning reads the
site off the employee record and a delivery note has no employee to ask.
The fix proposes and the dropdown disposes; it is never submitted
unseen, because a delivery filed against the wrong site lands on another
site's purchase orders. A site stored at 0,0 is treated as unplaced —
the live collection has one, and null island is a real point in the Gulf
of Guinea that a bad fix would otherwise match.
1023 tests, typecheck clean, production build clean. Not verified in a
browser: the pane cannot reach localhost here (net::ERR_FAILED on every
resource), and it has no camera to drive the capture path with. curl
confirms the route renders and ships both sites with their coordinates.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The kiosk phone needs the app installed for the full-screen
standalone experience, but no browser auto-prompts on day one —
Chrome's mini-infobar waits for an engagement heuristic that's
too slow for the kiosk use case, and iOS Safari never fires
beforeinstallprompt at all.
New InstallPrompt component mounted in the clock layout (so it
only appears on /clock, not the dashboard). Three branches:
* Chrome / Edge / Android: defer beforeinstallprompt, surface
our own Install button that calls prompt() on tap.
* iOS Safari: detect iOS user agent and show 'Tap Share then
Add to Home Screen' (Apple doesn't fire the install event).
* Already installed: detect display-mode:standalone +
navigator.standalone and render nothing — silent on the
kiosk itself.
Dismissal is per-session (sessionStorage), so a fresh visit on
a new device re-shows the prompt. The banner sits above the
clock footer (bottom: 6rem) so it doesn't fight the Person /
Fleet tabs for taps.
The kiosk phone is mounted on a wall with no physical UI to confirm
'this is the scanner' — without the overlay the camera feed alone
is a confusing blank screen. Now the scanner paints:
* a centred orange-cornered viewfinder (the QR alignment frame)
* a black vignette dimming everything outside that frame
(achieved via a 9999px box-shadow on the viewfinder, the
cleanest way to punch a rounded hole in an overlay without SVG
mask math)
* an orange scan line sweeping top-to-bottom inside the frame
(transform-based keyframes, pauses under prefers-reduced-motion)
* a pulsing 'Scanning for badge' status pill at the top
* a one-line 'Hold the QR badge steady inside the frame' hint
at the bottom
Everything is pointer-events-none so touches still reach the
camera. The overlay hides during the green/blue/red result banners
so it doesn't compete with the success message.
The new PWA icon matches the supplied reference: dark background, a
clock face with an orange upper-right arc and hands at 12 and 4
o'clock, sitting on an isometric cube whose two visible faces each
have the MEGA 'M' carved into the background colour, with two small
orange base wedges under the cube.
When the /clock URL is shared (Slack, iMessage, etc.) the static
1200x630 opengraph-image.png is picked up automatically and renders
the icon alongside the 'MEGA Clock' wordmark and tagline, via
openGraph + twitter metadata added to the route.
Footer buttons gain icons and more padding for easier tapping on a
wall-mounted kiosk phone. The header now shows a running count of
badges scanned since the app was opened. PersonScanner also requests a
screen wake lock while its camera is active (re-acquired on
visibilitychange, since the OS revokes it when backgrounded) and adds
vibration feedback per scan outcome, so a kiosk phone doesn't need to
be watched closely to confirm a scan landed.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>