Skip to content
KAMOUniverse

Changelog

What's new.

Every improvement to the platform, as it ships.

  1. · kamo-shared-library

    Bounded count/sum/recent queries for one org+assoc live set

    HrResourceSummaryService was materialising an org's entire non-hidden Img collection into memory to compute a count, a byte sum and a 5-row recent slice. Adds count/sum/paged-recent queries so a summary never has to load the collection to answer three numbers.

  2. · DocsService

    Legal, training and resource summary for the HR Overview

    One read per tab domain, each block gated on its own right and omitted rather than refused when out of reach.

  3. · SecurityService

    Attendance summary for the HR Overview

    One MANAGE_TIMECARDS-gated call composing the roster and manager-queue relays with the local exception, employment and pay-period rows.

  4. · kamo-marketing

    Localize the hub cards and stop bundling the English datasets

    The four hub pages rendered client components that imported the collection modules directly and took no props, so every card — industry names, integration taglines, glossary definitions, competitor summaries — was English in all 22 locales even though the surrounding chrome was translated. It also meant the full English datasets shipped in the client bundle. The server now localizes and passes only the fields each hub actually renders, so the client keeps the structural constants (category labels, display order) and drops the data modules. Vendor and competitor brand names stay untranslated but are still passed, since the cards print them. The ItemList/DefinedTermSet JSON-LD on these pages also hardcoded /en URLs for every locale; they now follow the page's own locale.

  5. · kamo-shared-library

    Summary queries for the HR Overview

    Status histograms, overdue slices and per-member rollups for legal and training, plus template/course status counts. No entity change.

  6. · kamo-internal

    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 absence is not a signal. The control now carries a breathing red glow whenever the member is clocked out. A breathing glow on a 1.8s cycle rather than a hard blink. That is well under the three-flashes-per-second photosensitivity threshold, and a strobe in the corner of every screen would be intolerable for the eight hours a day it is meant to be visible. Under prefers-reduced-motion the glow is held STEADY rather than removed — unlike the obligation ring next to it, the motion here is not redundant with a badge or a count, so removing it would remove the only signal. The glow only appears once the state is actually KNOWN. Painting it during the first fetch, or mid-punch while the optimistic toggle settles, would announce a problem that may not exist, and a warning that cries wolf on every page load is one people learn to stop seeing. The clocked-in dot is green instead of --accent-people. That token is the HRS module's amber identity tint (#c47d1a), which reads as a warning sitting next to a red glow — on the clock is the good state and has to look like it. The trailing bullet is gone. It carried no information the dot did not already carry, and it took the .tabular !important workaround with it. overflow is pinned visible on the button rather than inherited: the glow sits at a negative inset, so a clipping ButtonBase would swallow it entirely and the control would look merely unstyled rather than broken — the hardest kind of visual bug to notice.

  7. · kamo-internal

    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 created — so **************** said "not currently being tracked" with nothing anywhere in the product able to change that. EnrollmentSubTab is that control, on /hr/attendance where HR manages the workforce. It uses the canonical MemberPickerDialog in multi-select, and enrolls one member per call rather than as a batch: each enrollment writes an employment row AND an effective-dated assignment, so a partial failure must leave the people who succeeded enrolled instead of rolling everyone back. The screen says why it is not on roles and permissions, because that is the first question anyone will have: security rights are removed when a membership stops being active, which would switch someone's timecard off at exactly the moment their pay records need to outlive their employment. Non-exempt is the default and the dialog says why — classifying someone exempt who is not is the most expensive mistake in wage law, and it is made by job title far more often than by any real test. The member Timecard tab is now hidden entirely unless that member is actually enrolled. It previously rendered for anyone in an HRS org and then announced it had nothing to show, which is a dead end: nothing on that page could turn it on. Enrollment stays `undefined` until known rather than defaulting to false, so the

  8. · SecurityService

    Relay enrollment

    MANAGE_TIMECARDS to read or change who is on timecards — enrollment decides who gets paid from this system, so it is held to the HR administrator right rather than to a manager's own reporting line.

  9. · kamo-marketing

    Translate the collection pages instead of hiding them

    The 2,400 noindexed detail pages were noindexed because their body copy has no translation: competitors, industries, integrations and glossary keep their prose in app/lib/**/*.ts, which the dictionary never saw, so /fr/solutions/real-estate rendered French headings around English paragraphs. scripts/extract-collections.ts pulls the 3,799 translatable strings out of those 120 items into a structure-mirroring tree, skipping anything a translator must not touch — slugs, category enums, matrix statuses, logo paths, relatedTerms cross-references, and vendor/product names. That tree ships as the `collections` namespace in kamo-marketing/en.json. At read time localizeItem merges the translated tree over the English object. Only keys present in the translation are replaced, and an array whose length drifted from the source is dropped wholesale rather than rendering a list with missing entries, so stale translations degrade to English instead of breaking the page. `collections` is stripped from NextIntlClientProvider: it is ~420 KB that only server components read, and next-intl serializes whatever the provider gets into the RSC payload of every page. The per-locale merge in i18n/request.ts is now memoized for the same reason — it was cheap at 150 KB and is not at 514 KB. collectionRobots still reports noindex until the translations land; widening COLLECTION_CONTENT_LOCALES is what lifts it.

  10. · kamo-internal

    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 working endpoints. ApprovalsSubTab is the manager queue. A timecard carries two attestations — the employee's on submit and the manager's here — so the only action with no reason attached is a plain approval of a card the employee submitted themselves, where their own attestation is the record. Sending a card back and approving on behalf both take a mandatory reason, because sending someone's timecard back without saying why is how an employee loses a day and never finds out which one. A card with blocking exceptions has its approve button DISABLED rather than allowed to fail: the server refuses anyway, and a button that always errors is worse than one that is visibly unavailable. The blocking chip navigates to the Exceptions tab, which is where the work actually is. ExceptionsSubTab shows two deliberately different numbers — everything open, and the subset that BLOCKS a close. isBlocking is rendered exactly as the server sent it and never re-derived from the type string. Waiving is presented as what it is: nothing was fixed, a person is choosing to proceed anyway, and the reason is what makes that decision survive being asked about later. The placeholder fallback stays in the page even though all seven indices are now wired — appending a sub-tab id without a panel should say so rather than render a blank screen.

  11. · SecurityService

    Relay the exception queue

    MANAGE_TIMECARDS to read the queue and to waive, because the queue names every member with a problem and waiving changes what the finalize gate reads. The per-member re-detect goes through resolveRole, so a manager can re-run detection on their own report without an org-wide right.

  12. · InitializerService

    The partial indexes that stop a double payment and a code collision

    **************** is the teeth behind the export service's refusal to re-issue. A second committed file for one pay period double-pays an entire company, and the service-layer check alone loses to two operators clicking at the same moment. A correction carries reverses_batch_id and is therefore excluded from the predicate, which is the whole reason this cannot be a plain unique constraint on the entity — and the test asserts that exclusion, because without it a correction could never be issued at all. ux_tclabor_code_active scopes labor-account code uniqueness to the ACTIVE rows. A retired department re-created under its old code is ordinary accounting housekeeping; a blanket constraint would force the org to invent "SALES-2" while the retired row still has to exist for historical segments to point at. The entity's blanket constraint was dropped in favour of this. EXPORT_REVOKE revokes DELETE but deliberately NOT UPDATE: a batch legitimately transitions DRY_RUN -> COMMITTED -> REVERSED, and revoking UPDATE would freeze a dry run before it could ever be committed. The test pins that distinction.

  13. · kamo-internal

    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 succeeded" is unusable: the person running it now has to find the eighty-eight by hand, which is exactly the work the bulk operation was supposed to save. The refusal table carries the downstream sentence verbatim, so "this timecard has 2 blocking exceptions" tells the approver what to do instead of telling them to open a ticket. The approval dialog says plainly that the reason is recorded on every card, and that each card still goes through the same checks. Both are true and both are the point: a bulk approval IS one decision applied to many records, and pretending otherwise by demanding a per-card comment would produce four hundred copies of the word "ok".

  14. · SecurityService

    Relay bulk operations and attendance

    The bulk approval role is DERIVED here from the caller's rights and appended to the path — never read from the body. A body-supplied role is a body-supplied promotion, and this endpoint approves hundreds of wage records at once. Attendance points follow the same identity-first rule as everything else about one named person: own on identity, anyone else's needs MANAGE_TIMECARDS.

  15. · kamo-internal

    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 alert, because "FAVOURS_EMPLOYER" is a wage exposure and burying it in a number nobody reads defeats the point of computing it. BreakAttestationPanel is the employee's own account of their breaks. Rest periods are paid and therefore never punched, so this is the only evidence rest compliance will ever have — a punch-only design reports perfect compliance for every org forever, which manufactures evidence against the employee rather than merely lacking it. Nothing in it is pre-filled with a compliant answer, and an empty rest count stays NULL rather than becoming zero: zero is a claim that no rests were taken, which is a violation, while null is "not answered", which is not. MyRecordsPanel gives an employee their leave balances and their own evidence pack. The download is available with no right at all, and it stays available after employment ends, because in several states the right to these records outlives the employment by statute and the people most likely to need them are the ones who just left.

  16. · SecurityService

    Relay the compliance reports, evidence pack on identity

    The org-wide reports need MANAGE_TIMECARDS. The evidence pack does not, when a member pulls their own — and that is the point. The pack is what an employee needs to bring a wage claim, and gating it behind an HR right would mean the only people who can produce the evidence are the ones it would be used against. A member in the restricted tier holds no rights whatsoever and is exactly who needs it.

  17. · kamo-internal

    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 org default schedule. The rounding field explains itself rather than being a bare number. Rounding is lawful federally only when NEUTRAL over time (29 CFR 785.48(b)), California has effectively ended it for meal periods (Donohue v. AMN) and cast doubt on it generally (Camp v. Home Depot) — an admin turning it on should know they have taken on an obligation to prove which direction it drifts, and the warning appears the moment the value goes above zero. Attestation says why it exists next to its own switch: rest periods are paid and therefore never punched, so attestation is the only evidence rest compliance will ever have. Without it the system reports perfect compliance forever, which is worse than reporting nothing because it manufactures evidence against the employee. The geofence switch says "flag", never "refuse". Refusing to record work that was actually done is the exact violation this system exists to prevent. The schedule section states plainly that a schedule never pays anybody — it is compared against what happened, and where the two disagree the actual record governs.

  18. · SecurityService

    Relay the timecard configuration surface

    MANAGE_TIMECARDS to read the org's rules — the workweek and rounding settings describe how everyone in the org is paid — and FINALIZE_PAY_PERIODS to change them, because they are the numbers a period freezes when it closes. A member may read their OWN schedule on identity, without any right. A schedule is what someone is expected to work, and being unable to see it is not a defensible product.

  19. · DaemonService

    Schedule the nightly timecard sweeps

    Cron lives here rather than in TimecardService because that deployment is RollingUpdate with maxSurge: 1 — two pods overlap on every rollout and every @Scheduled method there would fire twice, which for a sweep that writes exceptions means duplicate rows. This service is replicas: 1, strategy: Recreate, and is the only place on this platform where "once" means once. Runs at 02:20 daily: after midnight so a full business day has closed everywhere in the continental US, and off the hour so it does not contend with every other platform job. Stamps mlos.internal-auth-secret, not internal.auth.secret. DaemonService's own internal.auth.secret resolves to the public-chat value and would 403 every call, which is the trap StewardEmailClient already documents. When the secret is missing the job refuses to run and says so at ERROR rather than logging a false success — a sweep that quietly never runs is indistinguishable from one that finds nothing, and the difference is a month of undetected missing punches. The three sweeps are attempted independently. A break sweep failing is a compliance gap while an exception sweep failing is an unpaid day, and coupling them lets the cheaper failure take out the expensive one.

  20. · kamo-shared-library

    Org configuration, holiday calendars and schedules

    TimecardOrgConfig holds the workweek, workday boundary, rounding, rule-set pointers and the review switches. Nothing in it is read at calculation time — every value is snapshotted onto a pay period when the period closes and the engine reads the snapshot, which is what lets an org change its rules without rewriting what it has already paid. TimecardHoliday is DATED, not ruled. "Fourth Thursday in November" is a rule and rules drift, so a calendar generated three years ago under a since-edited rule would silently disagree with what was actually paid. The paid/worked split is the reason the entity exists at all: holiday PAY is hours not worked, so 778.218 excludes it from the regular rate and it does not count toward the overtime threshold, while holiday hours WORKED are ordinary hours that may carry a premium. Treating them as one thing overstates the regular rate for everyone who took the day off and understates overtime for everyone who did not. TimecardSchedule is one table for both the org default and a member's override, because the resolution is "the member's row if there is one, the org's otherwise" and two tables would duplicate every column and let them drift. It is effective-dated so a schedule change in June cannot retroactively make March look like absenteeism. A schedule NEVER pays anybody. It is compared against what happened and never used in place of it — paying from a schedule is the auto-deducted lunch of shift planning. What it legitimately drives is exception detection, attendance points, open-shift alerts and predictive-scheduling premiums.

  21. · kamo-internal

    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 wrong in a way a number reveals: a clock-out at 17:00 looks fine in a list and is obviously wrong on a track where the shift before it ended at 13:00 and nothing follows. Shape is what makes a bad day visible to a reviewer holding fifty timecards. Dragging PROPOSES. It never writes. The handle's release opens the mandatory reason dialog and the proposal is held until the reason is written, because a slider that saved on release would put an unexplained wage adjustment one stray reason for dragging rather than typing a time. An unclosed shift is drawn as an open-ended bar rather than closed at "now". Guessing an end is exactly what the engine refuses to do, and the picture must not imply an answer the calculation will not give. The new instant is derived by SHIFTING the original rather than rebuilding a local wall-clock time, which is what keeps it correct across a DST boundary: "09:00 on this date in this zone" is ambiguous twice a year and simply wrong once. PayPeriodsSubTab makes both period rules visible — only one period in progress at a time, and closing freezes the rule versions. PayrollExportSubTab leads with the dry run: same numbers, commits nothing, so the variance can be read before anyone is paid. Commit is confirmed separately and disabled once a period has one, and a reversal is offered instead of a re-issue. RestrictedMembershipGuard confines a member whose employment has ended to their own records via a prefix allowlist — an allowlist so a page added later is denied by default, where a blocklist would silently admit it. It is a UX boundary, not the security one: every endpoint behind it is independently authorized in SecurityService, because a client-side redirect stops nobody who can open devtools. `chat` now maps to canSendTextMessages in TOOL_CAPABILITY, the one floor capability the restricted tier takes away. The payroll proxy passes text/csv through untouched — parsing it as JSON would fail the download as a 500 while the file was perfectly fine on the other side.

  22. · SecurityService

    Relay the editor, authorized by identity before rights

    An EMPLOYEE may correct their own record — that is the point of the editor, and refusing it leaves the employee with no way to say the clock was wrong about a day they are the only witness to. What they cannot do is make it count: the correction lands as an adjustment on a timecard that still has to move through the approval chain. Identity is resolved through resolveRole, which checks the subject BEFORE consulting any right. God mode makes checkRight return true for every right, so anything describing one named person has to admit that person on identity alone. It also has to work for a member in the restricted tier, who holds no rights at all and still has a statutory right to their own hours.

  23. · kamo-shared-library

    Carry the adjustment reason on the punch itself

    A corrected or inserted time now records WHY on the wage record rather than only in the audit log. Under Anderson v. Mt. Clemens Pottery an employer with inadequate records loses the benefit of the doubt on hours, and a record that carries its own justification is materially stronger evidence than one needing a join to an audit table to explain itself. It also lets the editor show the reason beside the time without a second query, which is what makes anyone actually read it.

  24. · SecurityService

    Sign former employees in to a restricted tier instead of refusing them

    A team member whose employment is not ACTIVE was refused at the door with "Your account exists but is not active at this time." That was the wrong shape. A terminated employee has a statutory right to their own wage records — California Labor Code 226(c) gives 21 days and a per-violation penalty after that — and an ex-employee who cannot sign in has no way to exercise it. Their compliance acknowledgements are the same story. They now sign in and land in MemberCapabilities.restricted: a strictly tighter tier than unlicensed and orthogonal to it. Unlicensed means the org has not paid for you; restricted means it no longer employs you. What survives is what they have a right to reach after separation — own timecards, own compliance record, own profile, own billing, support. Chat is the one floor capability this tier takes away, because a former employee holding a live line into the company's internal conversation is a different problem from one who cannot send an email. canViewOwnTimecards and canViewOwnLegalCompliance are never withheld in any tier: the people most likely to need their pay records are the ones who just left. The record-active check still REFUSES. A soft-deleted member is not a former employee, it is a member who no longer exists. Three gates opened, one deliberately left closed: - SecurityController login now refuses only on record-active - SessionRefreshController no longer kills an off-boarded session on the next poll, which would have made the access last about a minute - DeviceAuthController matches, so a terminated employee can reach their records from the phone they already had signed in - EnterAs and Impersonation are unchanged: those are admin paths with their own semantics and widening them is not what this is about restrictedIfNotEmployed fails OPEN, and it is the only thing on this path that does. Every other eligibility check fails closed because admitting an unknown member is the dangerous direction. Here it inverts: this runs on every entitlement read for every member, and a repository blip would strip chat from an entire org of perfectly employed people. The login gate upstream already decided whether the session exists. Also adds PayrollRelayController — the authorization boundary for pay periods, payroll export, break compliance, leave and job costing. FINALIZE_PAY_PERIODS gates anything touching the payroll file (committing one pays a company), MANAGE_TIMECARDS gates the HR surfaces, and the /me/** endpoints take no member id at all so they are structurally incapable of acting on someone else — which matters most for break attestation, an employee's own statement about their own day, and for a restricted member who holds no rights whatsoever.

  25. · kamo-shared-library

    Add the meal start deadline to the break rule

    California requires the first meal to BEGIN before the end of the fifth hour. A 30-minute meal taken at hour seven is a full violation even though it was the right length, so the deadline needs its own column — a length check cannot stand in for it, and BreakEvaluator already tests MEAL_LATE against it.

  26. · kamo-shared-library

    Pay periods, breaks, premiums, labor accounts, accruals, export

    Batches the remaining wage-and-hour schema into one shared-lib change so the whole domain lands in a single KamoInitializer run rather than a migration per phase. Entities: pay periods with rule-version freeze columns, earning lines, break rules and violations, labor accounts and work segments, accrual policies, balances and ledger, leave cases, export batches, attendance occurrences. Four pure engines, 51 tests: BreakEvaluator — never auto-deducts a lunch, because an auto-deducted meal books an unpaid half hour the employer cannot prove was duty-free. Premiums are WAGES (Naranjo), so they flow into earnings rather than a report. One premium per CATEGORY per day (UPS v. Superior Court): two missed meals owe one, a missed meal plus a missed rest owe two. A meal waiver is honoured only where the statute permits one — an employee cannot consent outside it. Rests are unpunched, so silence on attestation is never scored as a violation. RegularRateCalculator — overtime is owed on the REGULAR rate, not the base rate. 778.115 weighted average, 778.209 retroactive overtime on nondiscretionary bonuses, 778.216/218 exclusions, and the half-time premium: straight time is already in the total, so overtime adds 0.5x, not 1.5x. AccrualEngine — a ceiling stops accrual without deleting balance, carryover is capped separately from the ceiling, and a deduction that would breach the floor throws rather than silently approving half a day off. PayPeriodCalendar — generated, not hand-entered, and verifyContiguous refuses a gap (work belonging to no period, never paid) or an overlap (paid twice).

  27. · kamo-shared-library

    Exception model and detector

    Makes the blockingExceptions gate real. Until now the transition table read a count that nothing wrote, so every timecard looked clean and 'a missing punch is never paid as zero' was a comment rather than a control. The BLOCKING distinction is the taxonomy's whole point: an open shift, an unpairable punch and an unattested provisional punch stop finalization, because closing over them asserts something nobody can stand behind. A geofence miss and a clock skew are ADVISORY and never block -- refusing to record or finalize time that was actually worked is the exact wage violation this system prevents. The detector reports and never repairs. Every way of guessing an unclosed shift is wrong: leaving it open accrues absurd hours, auto-closing under-reports, a cap invents a figure nobody witnessed. So it becomes an exception instead. Idempotent by construction via the unique (member, date, type) constraint -- duplicates would inflate the count and make a timecard unfinalizable for a reason nobody could find.

  28. · kamo-internal

    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 seats until then. Deliberately not the shared ConfirmationDialog. That one requires typing DESTROY, DELETE, REMOVE or ERASE before its button enables, which is right for the irreversible deletions it was built for. Cancelling is neither irreversible nor destructive: no data is lost, access continues to the end of the paid period, and you can subscribe again whenever you like. A type-the-word gauntlet in front of stopping a payment is friction on the one action a customer is unambiguously entitled to take easily. Neither button says "Cancel". In a dialog about cancelling, that word means both "abandon this dialog" and "cancel my subscription" and cannot be read reliably as either, so both buttons name their outcome: "Keep my plan" and "Stop future payments".

  29. · BillingService

    Carry the renewal date on the coverage and group payloads

    Both surfaces now report currentPeriodEnd and cancelAtPeriodEnd. The date is what a cancel confirmation actually needs to say — "you keep your seat until the end of the period" is reassurance in the abstract, "you keep your seat until 14 March" is reassurance someone can act on.

  30. · kamo-internal

    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 only as apps that no longer opened — restriction with no explanation, which reads as broken software rather than an unpaid invoice. Raised only for bills they can act on: their own seat, and groups they manage. It renders nothing when there is nothing owed. Cancelling is now visible where the person can see who pays: a "Cancel automatic payments" action on their own seat, with the help text saying plainly that they keep the seat until the end of the period they have already paid for. The confirmation names the date, because the fear when pressing that button is that access stops immediately. The "Let self-payers choose their own plan" switch is gone. The plan belongs to the organization and everyone who pays inside it — individually or as a group — buys that same plan. The group panel now states the organization's plan rather than offering a picker.

Your universe is waiting.

Start free in minutes. Add products whenever you are ready — no migration, no new logins.

Free forever — no credit card required