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...
Stop two first loads provisioning two of each calendar
The launchpad paints two calendar widgets and each fetches its own feed, so two requests reached listCalendars at once. Provisioning is find-then-create, both p...
Saving a settings page no longer repaints the tab as the platform
Signed into Wienerschnitzel, opened Settings → Branding, saved Company Info, and the entire workspace reverted to Kamo's logo and colours. Closing the tab and o...
Allowlist Telnyx in both fail2ban jails
Adds the Telnyx US signaling addresses (192.76.120.10, 64.16.250.10 — sip.telnyx.com) to ignoreip now that the carrier trunk is provisioned. Also overrides ign...
Predial hooks must Return(), not MacroExit() — Asterisk 21
Asterisk 21 removed the Macro() application family outright, and FreePBX reaches these hooks with Gosub() (extensions_additional.conf: **************** The stoc...
Make fail2ban actually run — enable Asterisk's security log
fail2ban has never started on this PBX. Its asterisk-security jail watches /var/log/asterisk/security, a file Asterisk never created: FreePBX generates logger_l...
Scan the shared-lib email.billing repository package
@EnableJpaRepositories lists packages explicitly, so MailboxBillingDayRepository was invisible and the service refused to start outright — caught by booting it,...
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...
The lobby wears the meeting owner's brand, not the platform's
The overlay derived its theme folder from the browser's hostname. On the shared meet host that is the platform's for every tenant, so the CSS, favicon and logo ...
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 ...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.