Server-render the authenticated app, and fix the fallback org that 500s
MemberListProvider returned null until its mount effect ran, and it sits above NavTop AND {children} — so that one line kept the ENTIRE authenticated applicatio...
An AI sub-tab in the Interaction Center
AI sessions live in AIService and are not MediaSessions, so this bucket gets its own loader in front of the /api/media/sessions/chat fetch the other four share,...
The dedicated line window
The call now lives in a window a refresh of the workspace cannot touch. SoftphoneProvider becomes owner-or-follower. Only the owner REGISTERs and opens the STO...
Close the webhook idempotency gap BL0's final review found
Finding 1 (Critical): a losing claim on a redelivered Stripe event only proved a row existed, not that the prior attempt finished. A claimed row left behind by ...
A communications tab that rolls up the account's leads
The level most people mean when they say "this customer's calls". Several leads commonly hang off one account precisely because they are the same person enquiri...
Map the account timeline beside the account's other things
Everything belonging to an account already lives under **************** — its leads, orders, members, notes and history. Communications was the one at a plural ...
The KamoDesktop link is single-use now
The comment claimed a reload re-submits ?data= and restores the session. It no longer does: the link is spent the moment the tab opens it, so it is worthless af...
Bounce a bare visit to the Kamo login, and end the session on tab close
Closing the KamoDesktop tab left a live session sitting on the machine. Guacamole keeps its auth token in localStorage (GUAC_AUTH), which survives closing the t...
Make the KamoDesktop SSO link redeemable exactly once
The link is delivered as a URL, so it lands in browser history and in the reopen-closed-tab list, where it long outlives the session it was minted for. Until no...
The account rollup must not widen what a reader may see
The account timeline I added last commit was a way around the per-lead read gate. A member refused a lead's messages on the page that names the lead could open ...
Carry the assignee into the account rollup, over a LEFT join
Reading a lead's communications is gated on who it is assigned to. A rollup that returned uids alone could not apply that gate, so the account timeline built on...
Thump the first real message, not just the second
shouldThump(undefined, n) meant "first render" — but a spawned head's unread starts genuinely undefined, and applyUnreadToHeads's own undefined-vs-0 guard never...
Line ownership, the bridge, and the no-yank handover rule
Three pure modules with no consumers yet, so this ships inert. lineOwnership elects exactly one SIP registrar per browser via Web Locks. That is mandatory here...
Stop the plan instructing the money-path bug it was corrected on
Task 8 Step 4 still told the reader to key update/cancel/detach/del with "the current intent in the key". That is the Critical defect the final review reversed:...
The tab title counts everything waiting, not just chat
The tab strip has room for one number, and 'chat and support only' stopped being what a member is waiting on once SMS, AI, missed calls and unreviewed reports e...
Answer the softphone spec's biggest open question
Verified live on the cluster: every FreePBX AOR is max_contacts=1 with remove_existing=yes. That value was set nowhere in any Kamo repo, which is why it had gon...
Make every client component safe to render on the server
Next server-renders client components, so `window`, `document`, `localStorage`, `sessionStorage` and `navigator` do not exist during the render that produces th...
A total on the phone icon, the breakdown in the menu
This icon carried no badge at all, so a missed call while the softphone was closed reached the member nowhere. Opening the phone clears the missed count.
A total on the icon, the breakdown in the menu
pendingCount was the whole badge, so unreviewed bug reports and webinars booked on somebody's behalf had no way to reach anyone. Opening the webinar row stamps ...
The settings tab opens the calendar's editor
Sharing the day picker was the half-measure. It left two forms that still had to agree with each other about recurrence, times, effective range, note, save sema...
The chat badge counts chats
It was silently chat plus support, so a ticket lit the chat bell and nothing distinguished the two. Support has its own icon now; social joins chat, because a s...
Do not key ephemeral per-attempt Stripe creates
Round 2 review (Ruling 17) found "is this a create?" is necessary but not sufficient. The question that matters: can this subject legitimately be created more t...
Let a dropdown row carry its own count
The icon shows the sum and the rows show the breakdown — the point of splitting the indicators is seeing WHICH thing wants you without opening anything.
Thump on arrival, with the glow dissipating outward
Its own layer inside .hex-scale: that element already animates transform for the hover grow, and two animations on one element do not compose — the second repla...
An account-level communications timeline
The level most people mean when they say "this customer's calls". The spine is already keyed on an owner, so the account endpoint is the same merged, component ...
Let an account roll up its leads' communications
Several leads commonly hang off one account precisely because they are the same person enquiring twice, and each of their calls was matched to whichever lead he...
One context for the four indicators' counts
Composes what exists and owns the three that had no home: unreviewed bug reports, unreviewed webinars, and the phone's missed calls and voicemails. The 60s for...
Mark the softphone guardrails plan executed
Real commit SHAs in place of the placeholder, and 47 steps ticked. A finished plan showing all-unchecked boxes misreads as abandoned work.
A checkbox click should not re-render the folder sidebar
Six call sites read the email store with a bare `useEmailStore()`. In zustand that subscribes to the WHOLE store, so any change anywhere in it re-renders the co...
The drop beacon reports which path ICE actually chose
The beacon shipped iceCandidateType as a permanently-null field. That is worse than omitting it: it makes the telemetry look more informative than it is, and th...
Keys belong on Stripe creates only, not on mutations
Round 1 review (Ruling 15) found the Critical: a stable idempotency key on an update/cancel/attach/detach is not a safeguard, it is a bug. Those calls already s...
Memoize the app-wide unread value, fix a self-retriggering effect
UnreadContext published a fourteen-member object literal as its context value, so React saw the context change on every render of the provider and woke every co...
Stop polling for badges nobody is looking at, and stop waking every consumer
Two always-mounted providers ran a one-minute poll with no visibility gate. LegalPackageCount had none at all; TrainingPendingCount had a visibilitychange re-re...
The auto-reconnect reload-looped every page into a blank screen
I shipped this and it took the desktop down: a blank white page and a tab loading forever. Disabled in-cluster immediately; this is the real fix. `ng-switch` s...
A phone-less org should not open a call socket
The softphone opened its SockJS/STOMP connection to media.<domain> unconditionally at boot. getCapabilities() is awaited a few lines above and is the backend's ...
One day picker, shared by the calendar and the settings tab
The two surfaces had each grown their own answer to "which days?". The settings tab put Workweek Days, Weekend Days and Every Day behind a dropdown; the calenda...
Stop re-merging the whole catalogue on every request
getRequestConfig runs per request, and for any non-English member it called mergeWithFallback over the entire 23,252-key catalogue every time — rebuilding a res...
Badge a minimized window with its server unread count
The one component that sees both the window list and the unread maps — the engine knows nothing about conversations and the unread context knows nothing about w...
Set an absolute unread count from the server
Replaces hexhead:incoming, which nothing ever dispatched. Its +1 would have counted a re-delivered snapshot as a new message, and its spawn-if-missing branch wo...
Messages and calls, on the existing timeline component
CommunicationsTimeline and EmailEntry take an optional endpointBase and keep the lead path as the default, so every existing call site is byte-for- does not car...
A patient's calls, texts and email — on the platform's own spine
Not a second timeline. LeadCommunicationsService now takes a CommsSubject and the lead overload delegates to it, so /leads keeps its exact behaviour drift. A pa...
Stop losing calls to session expiry, teardown and stale devices
Four guardrails that share two files, so they share a commit. A live call now counts as session activity. ClientActivityTracker equated presence with input eve...
Read-fence SMS/AI mark-reads, fold sessionType into sameSessions
Three review findings against Task 21, all plan-mandated: - markSmsRead/markAiRead had no read fence: a refresh or an inbound push resolving between the opti...
Make every Stripe write idempotent, and fail the build on a new one without a key
A socket timeout on a Stripe write that actually succeeded is indistinguishable, from our side, from one that did not; without an idempotency key the retry crea...
One arriving message should not re-render the whole history
Batch 5 of the client-side performance audit. MessageList maps every loaded message to a MessageBubble, and MessageBubble was not memoized. With 300 messages o...
Take AG Grid's 187 KB of CSS off the critical path of every route
Batch 4 of the client-side performance audit, and the last of the big ones. app/globals.css opened with two @imports of ag-grid's stylesheets, and the root lay...
Commit the processed flag with the money write, not ahead of it
markProcessed was REQUIRES_NEW, so it committed processed=true while the handler's money writes were still uncommitted in the suspended outer transaction. JpaTr...
Widen the comms spine to an owner, in the order that keeps it safe
LEAD_CONTACT_POINTS and LEAD_COMMUNICATIONS gain the document manager's (assoc_type, assoc_object_id) pair so an account, a patient or a commerce record can own...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.