Never lock the provider an org is already using
Two production orgs run 26 and 41 live mailboxes while holding no mail add-on code, so the picker would have told them KamoMail requires the KamoMail Hosting ad...
22 of the 26 clinical rights were never enforced
Only the four population-level rights — bulk export, quality measures, portal administration and EHR settings — were ever checked. Prescribe, sign, cosign, plac...
A contested primary is a 409, not a 500
The catch-all turned an expected refusal into a server error, which lost the message the member needed and made a routine conflict look like an outage in the lo...
Ask a host that exists for the organization ?org= names
A new organization was created with its logo, colours and three backgrounds. All of it provisioned correctly into public/wienerschnitzel/ — 15 objects, the righ...
An organization can be looked up by the reference ?org= carries
GET /org/ref/{ref} — the same projected record as /org/domain/{host}, for an organization named by its id or its web alias. Nothing could answer for one. The s...
Refuse a primary mailbox another member holds
Primary is a login identifier, so a silent takeover changes which account an address authenticates from a screen that never mentions logins. Assign still works ...
Every catalogued event fires where the event actually happens
Most of the sound catalogue was configurable and inaudible. Two causes, both silent by construction: a sound that never plays looks exactly like one the member ...
Actually measure the bulk-export bucket
StorageDomain.BULK_EXPORTS shipped declared, documented as "measured by scanning the object store", and referenced by nothing. It reported zero for every practi...
Dropping in the goal scores, and aiming at it no longer gathers
Two complaints about the same gesture — taking a head to the goal. **Dropping in the goal did nothing.** Scoring lived entirely inside the throw RAF loop, whic...
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...
A signing page wears the sender's brand, not Kamo's
getThemePath took a hostname and used its FQDN with dots turned into dashes as the bucket folder. That could only answer for an org that HAD a hostname, and a d...
Brand the signup screen from the org's alias, not the host
register.kamocrm.com is the signup page for every organization with no register host of its own, so a host-derived theme folder painted all of them in the platf...
Brand the sign-in screen from the org's alias, not the host
login.kamocrm.com is the sign-in screen for every organization that has no host of its own. The theme base was derived from that hostname, so all of them were p...
A child org's lobby offers its own backgrounds, not the platform's
The folder holding an org's meeting backgrounds and theme config was derived from a hostname — the request's, or the org domain the sidecar passed — by taking t...
The signer app gets the sender's brand, not Kamo's
Two separate ways the same wrong assumption showed up. The signing page's logo URL was https://<org fqdn>/theme/logo-full.<ext>, which is not a path anything s...
Build the org logo URL from its alias, not its domain
Transactional email is sent to real inboxes, so the header and footer logos have to resolve without any client-side theme JavaScript — they are built exactly th...
Editing top-level lines no longer deletes every option's lines
Quote.lineItems is orphanRemoval=true and option rows set both quote and option — which is why QuoteMapper filters on getOption() == null twice. A bare clear() ...
An option quote keeps the tax and shipping staff entered
recalculate zeroed taxTotal and shippingTotal whenever a quote had options. Both were still stored on the row and still rendered by QuoteMapper, so a staff memb...
Terminal guards on cancel and revise, and stop swallowing bad input
cancel() and revise() had no isTerminal() guard, so an ACCEPTED quote could be flipped to CANCELLED or REVISED while acceptedAt and acceptedByName survived — a ...
A healthcare market could not be edited
The edit screen still built its picker by mapping over a hardcoded array of seven verticals. HEALTHCARE was not among them, so a healthcare market opened to a s...
Gate the population-level surfaces on their own rights
EXPORT_PATIENT_DATA, VIEW_QUALITY_MEASURES and MANAGE_PATIENT_PORTAL were added to Java in August and never mirrored into the client enum, so RoleRightType.THE_...
Stop dropping the market and the customer account
CreateQuoteRequest has always declared marketUid and accountUid and QuoteService.create has never read either, so Quote.market and Quote.customerAccount were NU...
The clinical rights were ungrantable
The permission editors render one section per enabled app and filter each role's rights by that section's serviceType. There was no EHR section, so all ~30 clin...
Remove the unreviewed-reply unread-count endpoints
Paired with the kamo-internal removal of the AI unread badge: the count never told the member anything actionable (no push channel backs it, so non-zero only ev...
Remove the AI unread-count indicator entirely
It never told the member anything actionable: AIService has no push channel for assistant replies, so a nonzero count only ever meant "you minimized the window ...
The new workspace is opened by a click, from /network
Creating an org disabled the form, did the work, and then appeared to do nothing: no new tab, and the button back to reading "Create Organization". window.open...
Patch a self-created session's guid back onto the window's props
When opened without a sessionGuid (launcher / nav / command palette), AiChatTool POSTs a new session and calls setSessionGuid(guid) but never told the tool-wind...
Switcher column opts out of presence it cannot honestly show
Passes showPresence={false} to its ConversationsPane mount. This column renders under ToolDock, a sibling of **************** (see ToolProviders.tsx's own doc c...
Thread showPresence to rows, and gate the empty state on prefsLoaded
- Adds `showPresence` (default true), forwarded to every ConversationRow — the pane-side half of the switcher-column presence fix (see ConversationRow's own...
Row honors showPresence, and formats a TEXT preview's E.164 sender
Two whole-branch-review findings against this component: - ConversationRow always read **************** directly. Mounted inside the switcher column (a sibli...
Give the Chats tab pane a definite height, not just min-height
The chats-tab motion.div had `height: undefined` (only `minHeight: '100%'`), so its containing block was auto-height and ConversationsPane's own `height: 100%` ...
Close review round 1 on the chat-switcher column
- ChatSwitcherColumn: move the collapse preference's PUT out of the setCollapsed updater into the click handler itself. The updater was impure (it fired a f...
Stop the fallback-bucket flash, guard the double-click
Review found the pane had dropped the old chats-list component's prefsLoaded gate: activeBucket started at initialBucket/DEFAULT_BUCKET and was handed to useCon...
A restored maximized window remembers where it came from
A snapshot written while a window was maximized used to lose what that window would return to, so a restored session's first conversation-switch dropped it in t...
Stop the text branch's retry storm, pin the observer re-root
voipApi.getConversations goes through apiCall, which throws on any non-2xx — unlike loadMore's other two branches, which fetch() directly and check res.ok. The ...
Pin ServiceJob's join columns to INT8
Exporting the table's DDL showed org_id, engagement_id and customer_uid each emitted as INT8 DEFAULT unique_rowid() — inherited from the referenced key. Three f...
Stop the previous commit's CRLF rewrite of memberColor
The previous commit's one-word doc-comment edit (dropping the ChatsList reference) rewrote the whole memberColor function from LF to CRLF: this file mixes line ...
Stop discarding status changes and answering 200
updateWorkOrder parsed the requested status inside catch (Exception ignored), saved the entity unchanged, and returned 200 with a DTO showing the old value. The...
Stop deriving the org's theme domain, and freeze the form while it works
The wizard derived the new org's domain from GET /org/{id}. That endpoint returns the entity, and its `domains` is LAZY — serialized as null outside a transacti...
Create returns the domain its theme is provisioned under
The caller cannot work this out and must stop trying. GET /org/{id} answers with the entity, whose `domains` is LAZY and serializes as null outside a transactio...
Stop the tracking-link strip clipping its own text
Marketing Tools -> Tracking Codes drew the member's link strip with the eyebrow cut through its capitals, the icon sliced top and bottom, and the line under it ...
Give the text-conversations coder a tiebreaker so tied timestamps aren't dropped
lastActivityAt, so a conversation sharing that exact timestamp was excluded from the lost from the list permanently, not just misordered. Reachable in practice ...
The new workspace opens once, ready, and as itself
Three faults with one cause, reported after creating Carl's Jr: the new tab said ?org=kamocrm.com, showed Kamo's logo and none of the org's colours — while its ...
Delete findConflictingEvents, which never returned a row
Four independent defects, any one of which emptied the result set: it filtered e.user.id while both callers pass a member id and the two columns never coincide;...
Conflict detection now finds conflicts
Delegates to findBusyForMemberInRange and excludes the event being edited in Java, where a null exclusion cannot poison a SQL predicate. The test it replaces m...
Remove the recurrence control, which discarded what it collected
The switch, frequency select and occurrence count rendered on the calendar page and reached no payload: composeDraft never read them and onSave never sent them....
Settle the loading gate on the gate, not on a "load started" flag
Two follow-ups to the session store, one of them a real defect. ensureLoaded decided foreground-vs-background from an `everLoaded` flag set when the first fore...
A push token may be claimed by one device row, not every past sign-in
Companion to MediaService 8a809b3, which stopped members getting the same chat message three-plus times on their phone. That change made dispatch collapse dupli...
One phone, one alert — collapse duplicate push destinations
Members were getting the same chat message three-plus times on their phone. Registrations are unique on (device_token_id, org_id, push_kind), NOT on the push t...
A tab only names an organization it actually has
Opening TacoBell from /network put ?org=kamocrm.com on the new tab. The tab lands on /validate, which renders inside this shell BEFORE its session exists — so t...
Like what you see shipping?
All of it arrives in your workspace on its own. Start on the free plan and read this page again in a month.
