Glow red when off the clock, green when on, and drop the bullet
Being off the clock while at work is the state that costs someone money, and it was the one state nothing announced: the indicator was simply absent, and an abs...
An Enrollment tab, and hide the member Timecard tab when not enrolled
There was no way to turn timecards on for anyone. Every read surface existed and every one of them correctly showed nothing, because no enrollment could be crea...
The Approvals and Exceptions tabs actually exist now
Both backends shipped earlier but neither panel was ever written, so /hr/attendance?sub=approvals and ?sub=exceptions rendered "this tab is being built" over wo...
Build log through completion
Records the 24-table final schema, the four partial indexes that carry the double-pay and double-code guarantees, the two late schema decisions that each cost a...
The period-close screen for an org with 400 people
Re-detect exceptions, recompute everyone's earnings, approve everything the caller can — each reporting PER MEMBER with the reason it refused. "312 of 400 succ...
Compliance reports, break attestation, and your own records
A Reports sub-tab on /hr/attendance that runs all three org-wide reports over one window. The rounding report shows the verdict as a chip and the finding as an ...
The real timecard configuration tab
Replaces the placeholder with the working surface: workweek start, workday boundary, open-shift threshold, rounding, the review switches, break-rule seeding and...
The visual editor, the pay calendar, payroll export, restricted tier
DayTimeline draws a worked day as a 24-hour track with a draggable handle on every punch. A graph rather than a table because a wrong punch is almost never wron...
Confirm before stopping a recurring payment
Both cancel actions — a member's own seat and a group manager's plan — now ask first, naming the date cover runs to and, for a group, how many people keep their...
Cancelling a group's plan keeps its seats to the end of the paid period
Drops the immediate flag from the client entirely — there is no longer a way to ask for access to stop today, because there is no good reason to want one. The g...
Tell people when a bill needs them, and let them stop paying
A banner on the home page when a payment this person is responsible for has failed, linking straight to their billing page. Until now a failed payment showed up...
Member Timecard tab and the HRS Timecards settings tab
The member tab is SELF-SERVICE ONLY and gated on identity, exactly like legal-compliance: its endpoints take no member id at all, so it is structurally incapabl...
/hr/attendance roster and the NavTop clock control
Replaces the HrTabPlaceholder body. The route, HR_TABS entry, MANAGE_TIMECARDS gate and tab i18n keys all already existed; nothing here registers anything. Ros...
Build log for phases 1-3
Records what shipped, the shared-lib push-ordering contract that bit once, the production census that settled the timezone questions, and the pre-existing KamoI...
BFF route for /api/security/timecards
TimecardService is ClusterIP with no ingress, so without this route the whole feature 404s in the browser while every service is healthy and every build is gree...
Deliver the org timezone to the browser
The client Organization model had no timezone field, useFormatters and next-intl are both configured with no timeZone, and timezoneService's backend call 404'd ...
HR timecards design and plan series
Design grounded in a 25-agent cross-repo survey. Part II closes the twelve blocker-level gaps an enterprise feature-completeness review found between the punch ...
Put a person's own bill somewhere they can actually reach
The self-pay and seat-coverage panels were on /settings/account?tab=billing. That page redirects anyone without CONFIGURE_SYSTEM or a finance right back to /set...
Show members who pays for their seat, and stop offering a second one
The coverage endpoint shipped with translation keys and no component, so the question it answers — who pays for my seat — still had nowhere to be asked. SeatCov...
A group you can put people on and pay for
Creating a group produced a name and nothing else — no way to add anyone, no way for its manager to choose a plan, nowhere to pay. Opening a group now shows who...
The HR course editor, assignments grid and member course player
/hr/training replaces its placeholder with the two sub-tabs the brief asked for, and the member's Assigned Training grid gains the action column it was holding ...
One member surface for both obligations, one badge for either
The member's compliance tab had two sub-tabs, Pending and Completed, for one kind of obligation. Those are a STATE, not a kind — so they collapse into a status ...
Let document uploads actually reach the 3 GiB backend ceiling
The backend now streams to 3 GiB, but three things in the browser and the BFF made that unreachable, each fatal on its own. The upload proxy did `await request...
Style the scrollbar on the element that actually scrolls
SettingsView declares an 8px primary-tinted scrollbar on the white card, but that card never overflows — every ancestor down to TabPanel is flex:1 with minHeigh...
Give the Plans & billing tab its page gutter and section surfaces
The console rendered flush against the settings card on all four sides. Nothing in the container chain pads a tab child — SettingsView's card, its inner Box, Ta...
Stop one failed call from blanking the whole console
Loading the console makes four calls, and any one of them throwing produced "Couldn't load your billing" with nothing else on screen. The subscription list is t...
Declare the object-storage secret the ConfigMap no longer carries
MINIO_SECRET_KEY was moved into the minio-app-credentials secret, but the envFrom supplying it was only applied live and never committed, so a CI reconcile woul...
Stop shipping the shared credential in this repo
The object-storage key sat in k8s/configmap.yaml, which kubectl will hand to anyone who can read the namespace, and the same string was hardcoded as the SSH pas...
Let a member license their own seat from the console
The panel that closes the loop on self-pay. It appears only where the organization's policy permits it, and only for people who are not already buying on the or...
Let the owner decide who can pay, and manage the groups that do
The owner had exactly one lever over self-payment and it was the wrong shape: an irreversible promote gesture, with no way to say in advance whether it was perm...
A Plans & billing console an owner can actually find
There was no way to buy a first subscription anywhere in the product. Plan lists were reachable only through a subscription you already held, so an organization...
Show the ceiling, not just the bytes
The panel reported stored, added, removed and lifetime bytes against no reference at all — so an owner looking at 400 GB had no way to tell whether that was com...
Show whether someone is there, and their extension
Every row now carries a presence dot and word beside the name, and its extension in the detail line — so choosing a person no longer means picking a name and ho...
Type the storage capacity the summary now returns
Keeps the client contract in step with BillingService, which now reports what the plan includes, what blocks were bought, the combined ceiling and the fraction ...
Tell the requestor whether their agent is actually there
A silent thread does not distinguish "nobody has looked at this" from "my agent stepped away", and the person waiting cannot tell which. The notice under the co...
Assign a chat request, and assign from the tickets list
Two gaps, same cause — assignment was treated as something that happens to a ticket rather than to a piece of work. The assign button was hidden for pre-ticket...
Quote engine for the plan picker, matching what Stripe will bill
The arithmetic behind a live running total, kept apart from any component so the numbers can be tested directly. It mirrors **************** exactly: per-seat a...
Gate paid actions on a licensed seat, at the chokepoint
Nearly every gateable action in the product — dial an extension, email a member, open the AI assistant, play a recording, start a social conversation — is a bar...
Pick the agent to reassign a ticket to, instead of typing their id
Assign and reassign opened a text box you typed a raw member id into — which meant already knowing the id of the person you wanted, for an action whose whole pu...
Design for the platform billing redesign
Records the audit finding that measurement, entitlement and money are three pipelines that were each built well and never connected, plus the 17 confirmed defec...
Decide the sign-in auto-open on the server, not in this tab
The per-tab sessionStorage flag was not a record of anything. Signing in on a second device replayed every conversation the first had already popped, and cleari...
Overhaul the preferences panel
Four dropdowns and a switch on a panel washed top to bottom in the tenant colour at full strength, so every control fought its own background. Each select hid t...
Replace the mailbox spinners with a skeleton that assembles
Every loading state on /messages was a Loader2 on an otherwise empty pane — the boot, the message list, the folder tree, the message body and the next page all ...
Restore the CRLF endings on the calendar types
Same script-normalisation slip as the attendee repository: the file was rewritten through Python, which converted every line ending and turned a handful of type...
Don't let one failed request silence the sign-in auto-open
Unread chats are auto-opened once per tab-session, guarded by a sessionStorage flag. That flag was written up-front, before the fetch that tells a chat session ...
Add and remove guests from the event dialog
The last thing the dialog could not do. It could display who was already on an event, but there was no way to put anyone there — the only guest control was "upd...
Tell the member what the event collides with, as they set it
The most useful thing a scheduling dialog can say is "you already have something then", and the endpoint to answer it existed all along with nothing calling it....
Stop typing an unseen support session as a plain chat
A live activity frame for a session not already in the list synthesized a row hard-coded to 'CHAT'. For a support ticket that put it in the wrong sub-tab, showe...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.