Our price comes from the catalog, and can't be typed again
The comparison pages hardcoded KamoCRM at $29 Starter and $64 Business while the catalog sold them at $34 and $72. Nine files, forty-two mentions, and the build...
Quote our own price per seat, not per user
The competitor pages priced KamoCRM in a unit we do not sell in — "$29/user/month Starter, $64/user/month Business" — on the same pages that describe competitor...
Give the fallback cards the inherits eyebrow they were missing
The three upper fallback plans carried no inheritsFrom, so on a cold build — which is what a fresh deploy serves until the first revalidate — Starter showed "Up...
One storage allowance, seats not users, and stop parsing prose
Storage is 4 GB per seat on every plan. The cards need no special case to stop repeating it: ladderFeatures already shows a tier's delta over the one below, so ...
Stop putting green text on green, and switch storage to per seat
CONTRAST. I only fixed the solid gradient buttons last time and left green ink on green-tinted surfaces, which is the thing that actually reads badly. Audited e...
Starter is 5 seats, Business is 25
I had this right, then reversed it on bad reasoning and shipped 25 / 100. The volume-discount bands (Starter 11-25, Business 26-50, 51-100) are seat PRICING ro...
Plan cards show what a tier ADDS, not everything it has
The cards listed every included feature - 16 on Free, 30 on Starter, 52 on Business, 57 on Enterprise. Four cards of wildly unequal height, each a wall of text,...
Unbreak the build - undo a sweep I caused
c8ec175 was meant to be an empty rebuild commit. It was not: `git commit --allow-empty` PERMITS an empty result, it does not force one - it still commits the in...
Rebuild to pick up the early-bird offer copy
The dictionary change landed after the last build, and messages/ is pulled at build time.
The seat headline read a default, not a ceiling
The card printed "Up to 5 users" from plan.maxSeats, directly above its own feature list reading "Users (25)" and a comparison column reading 25 - the same card...
The page contradicted itself, and the calculator billed email per user
Included-in badge named the wrong plan. All four per-seat modules are included in Business and Enterprise, and the badge picked the FIRST id in includedInPlanId...
Give the phone add-ons a phone icon
PER_EXTENSION already renders as "extension/month" - addOnUnitLabel handles it and needs no change. The icon map does: it is keyed on the lowercased catalog cod...
A per-mailbox add-on is not priced per user
PER_MAILBOX fell through to the default arm, so the public pricing page advertised the two e-mail add-ons as "/user/month" against a per-mailbox price. PER_EXTE...
Read the platform's branding from its alias folder
The theme folder was derived from the hostname — kamocrm.com became kamocrm-com — which is where branding was written until a domain became optional and the org...
Drop the headline from the top banner
Removes "Running Your Business without Kamo is a Mistake." from the fixed top banner, as reported. Confirmed live before the change — the running pod served it ...
Stop counting a member's visits to their own link
A member checking that their own tracking link works is the most common first visit any code ever gets, and it is worse than useless: it inflates the number the...
Show cents on an add-on rate that has them
Additional Storage is $2.50 per 10 GB block, and the catalog stores that as a number — so 2.50 arrives as 2.5 and rendered straight into the card it read "+$2.5...
Set the headline in quotation marks
Quotes around the banner line, as asked — but rendered from CSS (`open-quote`/`close-quote` against `quotes: auto`) rather than typed into the string, for two r...
Rebuild e67f59b against the dictionary commit it needs
e67f59b renamed previewBanner.nowLive -> previewBanner.headline and was pushed before the matching kamo-translation-dictionary commit (f4627d31). The build step...
Trade the NOW LIVE badge for the headline it was hiding
The top banner led with a six-character pill — bordered, uppercase, pulsing — and put the actual message after it. The pill now carries the message itself: "Run...
Quote the annual rate under the "billed annually" caption
The homepage cards read the month-to-month rate out of the catalog and printed it above a line saying "billed annually", so Starter advertised $34 and Business ...
Compress every response, not just the document
server-nonce.js asks upstream for `identity` so the HTML can be stamped with a CSP nonce without a decompress/recompress round trip, then recompresses on the wa...
Drain deferred work one item per idle period
Four components waited for the main thread to go quiet before doing their work — the toast/consent bundle, the scroll chrome, the touch target sweep and the ref...
Read the scroll position from an observer, not from layout
The nav's scrolled/at-top state came from a scroll handler that read window.scrollY, and the mount effect called that handler synchronously during React's commi...
Serve static assets as build-time brotli
The flagged chunk is react-dom, and I checked it properly before concluding anything: it is the production build (no dev markers, and the profiling hooks `injec...
Drop the unused preconnect, and fix the 565 ms of brotli I added
Two findings, and the second one was mine. THE DOCUMENT TOOK 1,259 ms BECAUSE OF THE NONCE WRAPPER. Measured against the live site: the HTML answered in ~900 ...
Put the headline's swept band back
Restored exactly as it was — same two-layer gradient, same 120deg band, same 9s/1.1s timing and easing — after removing it in the compositing pass. The cost is...
Move every looping animation onto the compositor
All 26 elements Lighthouse listed under "avoid non-composited animations" were the same mistake in four places: animating a paint property. Measured with Chrome...
Catch the silent version of "sound is not allowed here"
Follow-up to the still. Testing tap-to-play against the deployed site turned up a failure mode the muted fallback did not cover. A refused `play()` rejects, an...
The microservice count reads 64+, not 30+
The number is 64+ services now. Five untranslated surfaces still claimed 30+: the technology answer in the homepage FAQ JSON-LD (which is what a search result q...
Show a still of the reel instead of a grey pulse
Adds public/img/hero-still.webp — 1280x720, matching the aperture's 16/9 exactly — and makes it what the hero shows until someone presses play. This also fixes...
63 HTTP requests down to 22
Implements the request-budget report. Measured the same way it was written — Chrome over the debugging protocol, mobile emulation, sixteen-second window. 6...
The referral parameter is ?discount= and the token is a UUID
The link a member shares is **************** It used to be ?mrt=<memberId>.<iteration>, which put a real member id in every shared URL and let anyone enumerate ...
The reel was never using hls.js, and phones paid for it
Chasing "make fewer HTTP requests" found two real things, and the second is much larger than the audit that led to it. THE HLS BRANCH NOBODY WAS TAKING. HeroSt...
Retry npm ci through the runner's transfer corruption
The build failed on: npm error code ERR_SSL_CIPHER_OPERATION_FAILED ossl_gcm_stream_update:cipher operation failed That is a GCM authentication-tag mi...
Clear every console error and DevTools issue
Diagnosed with Chrome over the debugging protocol rather than by reasoning, collecting exactly what Lighthouse's errors-in-console and inspector-issues audits c...
Serve a real per-request CSP nonce with strict-dynamic
Clears both remaining High findings on script-src: the 'unsafe-inline' one and "host allowlists can frequently be bypassed". Only a nonce with 'strict-dynamic'...
Enforce Trusted Types, path-scope the script allowlist
Clears the "No Content-Security-Policy header with Trusted Types directive found" finding by moving require-trusted-types-for into the enforced policy. This is...
Clean the console, and restore the static rendering I broke
Three things from the console, one of them much bigger than the other two. THE BIG ONE. Chasing the "Fetch failed loading" lines led to the prefetch payload, a...
The Trusted Types policy was being injected by next/script
The report-only console showed two violations, and one of them was the policy component's own doing: This document requires 'TrustedScript' assignment. Thi...
CSP and COOP enforced; Trusted Types report-only
Re-lands the security headers after I took the site down with them, minus the one directive that did it, plus a build guard so it cannot happen again. WHAT BRO...
Revert "feat(security): enforce a CSP, COOP and Trusted Types"
This reverts commit ****************
Enforce a CSP, COOP and Trusted Types
Adds the three headers the Trust and Safety audit asked for, plus Referrer-Policy and Permissions-Policy. Every origin in the policy was taken from what the sit...
Stop re-measuring the document on every scroll frame
Two scroll listeners on the homepage read `scrollHeight` per event. That is a layout query, and this page is the worst place to make one repeatedly: its section...
Drop backdrop-filter on phones
The homepage renders 94 elements carrying `backdrop-filter: blur(20px)`. Every one is a separate compositing layer that has to read back everything painted behi...
Take the footer and the scroll chrome off the client
Three changes, all removing work from the window where the page is being judged. `(pages)/layout.tsx` carried `"use client"` and never needed it — no state, no...
Stop three resources from outranking the LCP element
The LCP breakdown is where the remaining time is, and its two loading subparts — 270 ms of resource load delay, 500 ms of load duration — are a priority problem...
A fixed-position banner cannot use a scroll-driven reveal
Fallout from the framer sweep, caught before it could matter. The rewrite maps entrance animations onto `.reveal`, which is driven by `animation-timeline: view(...
Remove gsap, the second animation runtime on the homepage
Framer was the chunk Lighthouse named. GSAP is the one it did not, because it arrives late enough to fall outside "unused JavaScript" — but it is 69 KB in its o...
The platform runs YugabyteDB, not CockroachDB
Unrelated to the performance work — found while reading the homepage's inline payload, where the string turned up in the FAQ answers. CockroachDB was retired f...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.