**The Plant is not a site.** Mega hires people out *from* the Plant to sites, and sites run their own clock-in (the Infrastructure app). Plant Clock records only when Plant staff — mechanics, operators and everyone else on the Plant roster — start and stop working **at the Plant**. Client ruling, 2026-10-22: it must be "just the scanning on the main screen".
So `/clock` opens straight into the camera. **There is no site picker, no confirmation step and no GPS.** Scan a badge and the scan is saved; a banner shows the name, IN or OUT, and the time, then the scanner is ready for the next person. Typing the employee code is the fallback when there is no camera.
- **The server decides IN or OUT** (`clockAction`): the first scan of an Africa/Johannesburg day is IN, the next is OUT, the next IN again (lunch out and back works). An IN left open past midnight stays a missing clock-out for office review; it never becomes the next day's OUT.
- **A repeat within 60 seconds is refused, not saved** (`REPEAT_SCAN_MS`). The camera reads a held badge several times a second, and without a confirm tap this is what stops one person flipping IN/OUT/IN. The screen says who and when the scan that counts was. The client also ignores the same QR for six seconds after any result.
- **A failed network save offers "Retry this scan"**, which reuses the original request ID, so a retry can never save twice.
-`clock_events.site` is **optional and left empty** on Plant scans. It was required in the first build; `setup-clock.mjs --apply` relaxes it.
Existing operators and mechanics are the roster. Plant badges encode `NAME | EMPLOYEE CODE`; the scanner also accepts Infrastructure's code-only badges. Both resolve by employee code, and the saved name is the current roster name. **Workforce → Employees & attendance** (`/employees`) shows who is in, missing clock-outs, recent scans and the QR badges. Session duration is elapsed time between IN and OUT, rounded down to whole minutes. It is not payroll time.
Setup is additive and idempotent. It creates only private `clock_events` and `clock_scan_locks`, and reports incompatible schema drift rather than rewriting it. No seed is required. The existing operators, mechanics, users, sites and financial records are untouched. Any authenticated, active Plant user who has completed password onboarding can operate this first phase, matching Plant's current account policy. Direct PocketBase attendance access is superuser-only; app APIs enforce user authentication and same-origin writes.
The integration check creates one temporary mechanic, scans it IN, retries, checks the repeat refusal and an unknown badge, back-dates the IN and scans it OUT. It and removes only its own events, locks and employee in `finally`. It uses a short-lived impersonated session for an existing onboarded user without changing that user's password or records.
Cross-process locks are unique per worker. An interrupted scan lock can be reclaimed after two minutes. Ownership is the lock's record ID: releasing an old lock cannot delete a new one. A unique `(employee, previous_event)` index prevents a stale writer from branching the event history even if its lease expires during an outage. If PocketBase is unavailable, scans are not queued or shown as saved. Retry the same confirmation once service returns.
Raw events preserve the employee code/name at the time of scan, server time, the signed-in account that scanned and the request signature. Vehicle and oil job logging, photo capture and payroll integration are deferred.
Plant Clock has its own manifest (`/clock.webmanifest`), application ID (`/clock`), icons and `standalone` display mode. Install from `/clock` or `/clock/login` on the HTTPS Plant host. It opens directly into the employee scanner with its own compact screen, without the dashboard navigation. The dashboard keeps its existing separate manifest.
On Android Chrome/Edge, use **Install Plant Clock** when offered or the browser’s Install app menu. On iPhone/iPad Safari, use **Share → Add to Home Screen**. Sign-in and first-password setup stay within `/clock`, and successful sign-in returns to the scanner. The app still needs a network connection to save attendance; it never claims that an offline scan has been saved. Camera and PWA installation need HTTPS on a phone; localhost is only a development exception.
## Forgejo integration (2026-10-21)
Local `main` was fast-forwarded from `2594dea` to Forgejo `b0c70e9` (13 commits). The recovery branch is `codex/plant-before-forgejo-merge`; the pre-merge clock work is also retained in the recovery stash. Upstream invoice, running-cost, operator, mechanic and oil features remain in place. The only QR package added beyond Forgejo is `jsqr`; the shared `QrCode`, `qrMatrix`, mechanic payload builder and vector PDF builder are reused. No seed or financial data migration was run.