Real chat direction, and stop the timeline mapper writing or fanning out
Chat entries claimed no direction and attributed every message to its row's author. Social conversations record BOTH sides against a Kamo Member — the visitor's...
Isolate call ingest per row and link only after it commits
Two faults with one root: ingest participated in the sweep's transaction. A throw inside a participating transaction marks the SHARED transaction rollback-only...
Make communication source ids natural keys, not row ids
A source id derived from the source row's generated UUID is only stable if that row commits. The VOIP ingest paths write inside the sync transaction while LeadC...
Skip soft-deleted extensions in the call-history sweep
syncExtensions soft-deletes by flipping status to UNKNOWN rather than removing the row, so the list accumulates every extension an instance has ever had. Each o...
Page the lead sweep until the window is covered
A single fixed page silently lost mail whenever a mailbox took more than pageSize messages between ticks — a newsletter burst, a busy shared inbox, or just the ...
Submit the lead at step 1 instead of only on booking
handleFormSubmit checked the *** and advanced the step — it made no network call at all, so the contact details a visitor typed were only ever persisted if they...
Withhold the reply box from senders that cannot receive one
Sending a quick reply to a KamoCRM notification put the literal text `{"error":"Invalid Addresses"}` in the reply box. The mail server had rejected NoReply@Kamo...
Address replies at Reply-To, and stop leaking SMTP exceptions to the UI
A quick reply to a KamoCRM notification failed with `{"error":"Invalid Addresses"}` rendered verbatim in the reply box. That string is Jakarta Mail's own wordin...
Outbound Teams call party, live audio devices, provider-aware prompt
Three confirmed findings from the review of 08cecc6d. Outbound Teams calls showed the wrong remote party. TeamsAcsSession derived remoteNumber/remoteName from ...
Close a cross-mailbox hole and six more Teams defects
Adversarial review of 6df0008 confirmed 24 findings. The important ones: CRITICAL — any member could delete a colleague's mail. Teams voicemail ids were compos...
Fit the trail in one pane — no wrapped intro, no double scrollbar
Three things made the tab feel cramped: The intro was capped at 900px, which broke a 170-character sentence onto a second line for no reason; it now uses the w...
PHI access audit tab layout + its missing menu entry
The tab rendered its content flush against the card border: SettingsView's tab panel supplies no padding of its own, and this tab was the one manager that never...
Fall back through every number the agent holds
An extension with no org line to hang it off is undialable, so the resolver falls back to a number the member actually has rather than publishing nothing.
Publish the agent's cell when it is the only number they have
An agent's number can live in any of three columns and commonly sits in only one: e_phone_number on the team member, m_phone_number and m_cell_number on the mem...
Cleaner invite mark in the chat message
The chat invite led with the video-camera emoji, which the message renderer scales to 1.4em — so it landed as an oversized colour sticker rather than an icon. R...
STUN answered on the wrong node, so kamocrm.com:3478 was dead
coturn is hostNetwork with no nodeSelector, so it was scheduled on k3m1 (public 47.181.8.87). Its own --external-ip, and every client's `stun:kamocrm.com:3478`,...
Stop classifying a multipart/related HTML root as an attachment
Every transactional email we send with an inlined org logo arrived in our own /messages viewer with a correct subject and a completely empty body. isAttachment...
Bridge advertised only private ICE candidates — no meet had media for 35 days
The bridge never completed a single ICE connection in the 35 days this pod has been up (0 successes, 18 terminations). Every conference joined over XMPP fine an...
Send the company line as text, not markup
Pairs with the template fix in kamo-shared-library. Per-org template rows are insert-only, so the rows seeded before the markup change still wrap this variable ...
The opportunity templates own their own markup again
The company line was changed to arrive as a complete <p> element from the sender, so an absent company would not leave an empty paragraph. That was the wrong tr...
An approver who files a request still gets the approver email
The actor was stripped from every audience, so an approver who raised a request was cut from their own notification about it — and in an organization with a sin...
Liveness probe could never pass — cause of 920 restarts in 35 days
Root cause: the liveness probe was `tcpSocket: port 10000`, but Scylla's REST API binds to api_address 127.0.0.1 (its default) while a tcpSocket probe dials the...
Stop rounding the lead id through a JS number
Number("1189175700550025222") is 1189175700550025200 — lead ids are unique_rowid() values above Number.MAX_SAFE_INTEGER. The quotes pane converted the route par...
Carry lead ids as strings — a JS number silently corrupts them
Lead ids are CockroachDB unique_rowid() values above Number.MAX_SAFE_INTEGER: 1189175700550025222 becomes 1189175700550025200 the moment it touches a JS number....
Resync drifted manifests with live before enabling full reconciles
Auditing what a full reconcile would apply turned up two manifests that had drifted far enough from the cluster that re-applying them would have caused an outag...
Line the commission-split controls up with the text fields
The Percent/Amount and One-time/Recurring toggles rendered at MUI's default toggle height while the member picker and the share amount beside them come out at 4...
Pane owns its spacing and scroll; drop the addressee fields from the form
The lead view's content card is overflow:hidden with no padding — every pane supplies its own, as Details and History do. The quotes pane supplied neither, so t...
Review findings on the reserved list and split editor
Five defects an adversarial review confirmed against the first cut: - The page rendered "You don't have access" on every load. checkRole returns false while ...
Give the commitment and setup fee fields room for their labels
"Commitment (months)" shares its width with an info adornment and "One-time setup fee" is the longest label on its row; both were truncating.
Resolve the org's real hostname and logo extension for the letterhead
Correcting the theme URL was necessary but not sufficient — the logo still did not render, for two further reasons found in the live data. org_domains stores a...
Keep the profile heading on the current membership type
The heading reads "<Member> Profile" or "<Team Member> Profile" off the type MyAccountTab reports through onMemberTypeLoaded, which only fired from the mount-ti...
Fill the Position card the moment a member is promoted
Department, job title, official title, work location, membership status and the two Autocomplete option lists were only ever loaded inside the memberType === 'T...
Summary moves into the header, records get the full page width
The summary sat in a fixed 320px right-hand column on every record page, permanently costing a fifth of the width. On the quote editor that squeezed the line-it...
Point the letterhead logo at the path the theme bucket actually serves
The logo URL was built as **************** which that host answers with a 308 rather than an image — so every quote, sent as well as previewed, silently fell ba...
Department contact fields showed raw i18n keys
The contact block added with the bulk-email work called departments.departmentEmail / departmentEmailHelper / departmentPhone / departmentExtension, but the dic...
Deliver attached images, and stop two compose windows sharing a toolbar
AI chat attachments were never reaching the AI. AiChatInput sent the storage paths as `imageUrls`, but the REST body in AiChatWebSocketContext reads `imageObjec...
Stop tool windows losing and leaking member-typed content
Follow-up to the popup-window persistence work. Each of these was verified against real code first; three of the original reports turned out to be wrong or over...
Departments no longer open invisible on first render
The Directory tab came up empty until you switched to Chats and back. The members were always there — every department was parked at opacity 0. Sections were r...
Stop losing popup tool windows (and unsent drafts) on page changes
Tool windows lived only in a module-level in-memory store, so any full document load destroyed every open window and everything typed into it. Several internal ...
Require ACTIVATED god mode to read the trail, not eligibility
PhiAuditController.hasRight() bypassed the right check on session GD alone. GD means "may activate god mode"; godModeActive means "has activated it, deliberatel...
Selecting Quotes no longer bounces back to the first sub-tab
Clicking Quotes set the index to 2 and wrote ?sub=quotes, but the effect syncing state back from the URL mapped anything that was not "commitments" to 0 — so it...
Harden the original session-timeout stack
Review of the **************** path after removing the duplicate idle stack turned up four real defects: - sessionMonitor.start() left its 3s bootstrap setTime...
Let the stage grow so the chrome pod gets an even gap
The pod (now taller with the tone slider) becomes the stage's only in-flow child: fixed height becomes a minHeight, with 16px padding above and below the pod (c...
Retry npm ci in the Docker build against flaky runner egress
The k1m1 runner intermittently corrupts TLS streams mid-fetch **************** killing the image build in npm ci. Give npm fetch retries and retry the whole ins...
Route the transaction pipeline screen through ConsoleShell
The Pipeline steward screen was the only console screen rendered without a layout wrapper, so its heading, filter labels, and table sat flush against the consol...
Take the percent sign from Intl, not the translated string
A literal "%" in the source text does not survive machine translation — zh returned "保存 {percent}" — so non-English users saw a bare number where a percentage w...
Repair the quote-option FK defaults ddl-auto cannot reach
The existing dynamic sweep only covers nullable FK columns that already carry a FOREIGN KEY constraint. commerce_quote_options.quote_id is NOT NULL, so it is ex...
Pin option FK column definitions so they do not inherit gen_random_uuid()
Hibernate copies the referenced id's columnDefinition onto a new @JoinColumn, so quote_id and option_id were both created DEFAULT gen_random_uuid(). An insert t...
Unbreak application.yml, and replace the test that never ran
Three things, all from trying to answer "why not just fix the context test?". 1. **************** declared `kamo:` TWICE and therefore did not parse at all ...
Surface quotes in every vertical, not just retail
The Quotes tab was only wired into the RETAIL sub-tab set, so on an org whose active markets are SUBSCRIPTION / MORTGAGE / PERSONAL_LOANS there was no way to re...
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.
