Offer the billing term up front, and stop hiding it over Enterprise
Two separate ways this page withheld the choice. The org console HAD a three-term control, but it was tucked into the right of the "Choose plan" heading behind...
Quote the commitment saving in dollars on the plan cards
The discount is a flat $10 a year and $20 on three years, which is how the catalog sets it and how it is advertised everywhere else. Rendered as a percentage it...
Stop offering the internal platform-owner plan for sale
listPlansForMarket filtered on isActive alone, and active is not the same as on the menu. OWNER_UNLIMITED ("Platform Owner — Internal") is active, carries no pr...
Set "/seat/month" to the right of its own line
The price keeps the left edge and the unit takes the right, so the two bracket the card's width instead of stacking flush left — which left the number looking l...
Put "/seat/month" under the price, not beside it
Three-figure rates and "/seat/month" do not share a line on a four-across card. Inline at 1rem the label either wrapped mid-text or squeezed the number it was d...
A promotion that fails to resolve must not widen to every plan
An EMPTY applicablePlanIds means "no restriction" downstream, so a restriction that fails to TRANSLATE must not be allowed to become an empty list. A Business-o...
A promo code works on its own last day
The end date is the LAST day a code works, not the first day it does not — SubscriptionPromotions.check reads it that way on the server, and the offer band's co...
Land the header's Text, Chat and Note buttons on the tab they name
The lead view keeps its tab selection in the URL — `tab` for the main tab, `commsTab` for the Communications sub-tab — and a header quick action sets both: "Tex...
Say why a number could not be opened, instead of an empty dialpad
Opening the SMS tool on a phone number resolves a conversation for it first, and that resolve can be refused — VOIPService answers "no number to text from" with...
Stop offering an edit once somebody else has read the message
Every chat window that draws a MessageBubble — the member chat popups, and the social and ticket conversations that share ChatBox — offered Edit on the sender's...
Refuse an edit once another member has read the message
PATCH /api/media/messages/{id} was deliberately ungated on receipts, unlike the DELETE beside it. The reasoning written into it was that unsend makes read words...
Darken the xAI mark so it shows on the provider dropdown
Simple Icons ships the xAI glyph filled #FFFFFF, which is its brand color on a dark surface. The AI provider dropdown is white and the app forces light mode, so...
Let a bulk move reach the folders the member made
The "Move to folder" dialog behind the selection bar was built from a literal ['INBOX', 'Archive', 'Trash', 'Junk', 'Drafts'], so the only destinations it ever ...
Never leave a blank tab when a KamoDesktop launch stalls
The tab is opened blank inside the click gesture and only navigates once a link comes back. So anything slow upstream showed as a blank white page with no expla...
The agent health preflight must fail in seconds, not twenty
health() inherited the client's 20-second default. It is a liveness check in front of every KamoDesktop launch, and when the dev machine ran out of memory on 20...
Serve smart folders and labels as messages, not as index rows
Both endpoints answered out of email_metadata.search_index and handed the client what the table holds. A smart folder returned SearchIndexEntity — messageUid, f...
Make eslint actually run
`eslint .` reported nothing at all — not a clean tree, but a crash before it loaded a single file: TypeError: Converting circular structure to JSON at ...
The report form's questions move off the field's border
A member filed this against the report form itself: "What happened?" — and "What would you like to see?" on the enhancement side — had the field's border runnin...
A stamp that says which day, once the day is in doubt
Reported by a member: past chats cannot be identified or verified by the date they were sent or received — only a time is present. Their own history runs back t...
A new chat shows the exchange instead of going blank
Open a new AI chat, type a message, and the thinking bullets appeared and then nothing — no sent message, no reply, just the empty-conversation panel over a tra...
Let the sidebar's calendar checkboxes actually turn off
The checkbox beside each calendar was bound to `calendar.isVisible` and its onChange was `fetchEvents`. `isVisible` is sent by EmailService and written back by ...
Spend the site deletes before the replacements are written
Editing a report came back "The bug could not be saved: Internal Server Error" whenever the reporter left a site ticked — so every edit about the title, the des...
A join/leave row has to carry the name of who it is about
The id alone cannot be resolved in the one case these rows exist for. A "removed" or "left" line names somebody who is, by then, no longer in the session, so th...
The Favourites header must control an element that exists
Collapsing the section unmounted the list it named through `aria-controls`, leaving the header pointing at nothing. `aria-expanded` then describes a disclosure ...
Forward removedByMemberId to the browser
The relay copies named fields off ChatMessageDto onto the STOMP frame, so a field it does not copy is dropped even once the DTO declares it. MEMBER_REMOVED now ...
Declare removedByMemberId, or the relay drops it in silence
The NATS payload for MEMBER_REMOVED carries who did the removing, but the NATS→STOMP relay projects every frame through ChatMessageDto — so a field this class d...
One title rule, one group threshold, and a rename the list hears
Review of the Chats branch, four findings. Both surfaces were composing the conversation's name by hand instead of through the helpers @kamo/chat-core exports ...
No upstream error text reaches a member, whichever provider failed
memberTextFor fell through to getMessage() for anything that was not an AiProviderException. Only the HuggingFace adapter classifies its refusals; every other o...
Show why a text could not be started, not "check the number"
VoipService now answers an unprovisioned account with 409 and both a machine code (`error`) and a sentence a member can act on (`message`). apiCall read `error`...
An account with no number to text from is a 409, not a 500
Starting a text threw IllegalStateException when neither the member nor the instance had an outbound number, which Spring turned into a 500 with a stack trace. ...
Retire a model the provider won't route, and stop quoting it at members
Follow-up to the router-catalogue fix. That stops unroutable models being LISTED; this stops one already in an org's catalogue from breaking the assistant and n...
List the Hugging Face models the router will actually serve
Chat goes to the Inference Providers router **************** but discoverModels asked the HUB for models that are "warm" somewhere — a different and much larger...
Drop the security level control from the batch settings
It was a disabled Select behind a "locked" tooltip, and a chip in the collapsed summary. Both reported a value the admin never chose: security level is derived ...
Format the phone numbers on the customer's quote
The page a customer opens printed every number exactly as stored — "9495550134" on the letterhead, and the same again under Your Contact. This app had no phone ...
Format the phone numbers on the printed quote
The document printed every number exactly as stored — "9495550134" on the letterhead, and the same again under Your Contact. Formatting goes through `app/lib/ph...
Show Getting Started to the System Member too
The button asked for organization ownership AND CONFIGURE_SYSTEM. The System Member is provisioned as an Administrator in every organization — all 34 of its mem...
The Getting Started API had no route to answer it
The launchpad button never appeared, and neither did any progress: the Java controller existed, the client existed, and the Next route between them was never wr...
Probe the bridge's real health, not a port that is always open
kamo-meet let one person into a meeting and then ended it the moment a second joined. The bridge had been hard-unhealthy for 41 hours: an in-place container res...
The logo caption sits under the logo, and says the domain is not reserved
Two unrelated symptoms, both in the brand band. **The caption printed over the logo when the slots stacked.** The preview was clamped with `max-h-full`, and th...
Stop the server asserting a tab title it cannot know
An org with no domain of its own still ended up titled "KamoCRM" after a refresh, briefly showing its real name first. generateMetadata resolves the tab's org ...
Stop refusing a domain because somebody else typed it first
Creating an organization 409'd on any existing row for the domain, which meant the first organization to enter "acme.com" locked out every other one — including...
Upload the compact logo that was previewed, not the file that was picked
The compact ("simple") logo slot is SVG-only — the backend writes whatever lands in it to a fixed <theme>/img/logo.svg key regardless of the file's real type, a...
Stop the attention flasher reverting an org's tab title to the platform's
A child org with no domain of its own showed "KamoCRM", flashed to its real name, then flashed back to "KamoCRM" and stayed there. TabAttention wrote `document...
Let the tab icon follow a simple-logo change instead of pinning the one captured at mount
TabAttention badges the browser tab, and it read the favicon ONCE in a mount effect. That effect runs before DynamicMetaLoader's config.json fetch has resolved,...
Give the generated favicons the same revalidation requirement as every other theme asset
Replacing an org's simple logo updated every surface that reads img/logo.svg and left the browser tab on the old mark. Generation was never the problem: provisi...
Enter the new organization here, not on a domain with no DNS
Creating an organization handed the creator a button pointing at `https://internal.<the domain they typed>/`. That host does not resolve: the domain is one they...
The scan's colours, the colour window, and the stacked slots
Five reports from the brand section, and the first two turn out to be the same bug wearing two faces. **Colours were never adopted.** The rule latched a `color...
A transient database fault must not 500 the request
The lead Communications tab reported "We couldn't load this lead's communications" for org 1168485648209608710 on 2026-08-28. The endpoint returned 500 five tim...
The document must not scroll behind the workspace
The authed shell is a full-viewport layout — nav fixed, content absolutely positioned to exactly 100vh — with one scroller inside it (.kamo-page-scroll). <body>...
Fill the content box exactly, scroll inside it
The pages grew to their content and pushed past the box the chrome gives them, so the document scrolled behind the shell and left dead background below the form...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.