Don't wipe member assignment on sync from providers that lack metadata
**************** unconditionally overwrote PhoneExtension.assignedMemberId with whatever the provider returned. FreePBX (and any provider that doesn't track whi...
Show provider name in view mode + own extension in edit dropdown
Two bugs on **************** 1. View mode dumped the raw UUID — "Instance 32f520b0-347a-…" — because the instance list was only loaded inside handleVoipEdit...
Serialize ExtensionDto.assignedMemberId as a string
overflows JS's safe-integer range (e.g. 1168485658174423046 round-trips as 1168485658174423000 once a JSON number hits the browser). The frontend already typed ...
Make WebRTC config survive pod restart
The previous approach (http_custom.conf with [general] override) didn't actually take effect because Asterisk's INI parser doesn't honor multiple [general] sect...
Enable Asterisk built-in HTTP server on :8089 for WebRTC /ws
A chan_pjsip `protocol=ws` transport rides on top of res_http_websocket, which in turn requires Asterisk's built-in HTTP server to be running. The default FreeP...
Prioritize real first/last name over usernameAlias for display
usernameAlias is functionally a login handle (often equal to username), not a display name. Real human-readable names live on User.name (EmbNameFull). Apply to ...
Scope Add Lead scroll and footer to its content area
The form was using minHeight: 100vh inside a parent that's already calc(100vh - 56px), forcing an outer scrollbar even when content fit. The action bar also use...
Smart multi-column layout for Add Lead form
Restructure the page into a top-level grid so sections compose horizontally instead of stretching individual fields: - Market & Assignment uses a 3-col interio...
Use /api/security/members/names for assigned-member name resolution
The by-department endpoint filters to active TeamMembers only, missing assignments to base Members or inactive members. Switch to /names which returns all membe...
Full-width Add Lead form and clearance for sticky footer
Drop the 1200px max-width on /leads/new so the form spans the page area, and bump bottom padding so the sticky action bar no longer overlaps the last form secti...
Resolve display name with correct priority in by-department endpoint
Priority: member.usernameAlias → user.name.formatFull() → user.alias → user.username
Add missing isDisplayOnIntake field to FormField interface and new field literal
Serialize Long as string to prevent JS precision loss on large CockroachDB IDs
Probe master model endpoint and fix appliedModel org ID
- Replace appliedModel.source-based gate with a direct probe of /api/security/master-model on mount; r.ok=false (409) means the org is governed by a branch ...
Gate censorship tab options by applied model constraints
- Hide EDIT MASTER MODEL button when org is governed by a branch type (use appliedModel.source instead of unreliable org context branchTypeID) - Hide ATF fiel...
Mark-as-read and message fetch for shared/member mailboxes
Classic mode now loads shared mailbox messages via the correct /messages/{folder} endpoint (was calling the non-existent /conversations endpoint). Fetching a fu...
Mark-as-read for shared and member mailboxes
EmailController.setFlags now accepts an optional mailboxId query param and routes through resolveProviderWithMailbox so member-mailbox flags are set against the...
Use extensionId as external ID, not Relay node id
FreePBX returns the same Relay id (ZXh0ZW5zaW9uOg== = "extension:") for every extension because the id field is not populated. Using it caused all extensions to...
Handle IMAPInputStream in getMessage to prevent ClassCastException
JavaMail can return an IMAPInputStream instead of a decoded String from part.getContent() when message content has not been pre-fetched. The direct (String) cas...
Rewrite all GQL queries/mutations against live schema introspection
Verified every field name and type against the actual FreePBX 17 schema. Full set of corrections: Queries: - fetchAllExtensions: remove non-existent 'voicemail...
Pass selectedFolder to ThreadDetailView so email:folder-changed fires with a valid folder
conversation.folder is always undefined because the Java ConversationEntity has folderIds (text[]) not a folder string field. This caused loadThread to call mar...
Correct GraphQL field name to fetchAsteriskDetails
FreePBX API returns "Did you mean fetchAsteriskDetails?" — rename the health check query and its data path accessor accordingly.
Add XHR/Accept headers and String response parsing to GraphQL client
Same pattern as FreePBXTokenCache: add X-Requested-With + Accept: application/json to suppress FreePBX's HTML redirect responses, and switch all three call site...
Sync mailbox dropdown to primary mailbox on load
Dropdown defaulted to first list item because selectedMailbox initialized without a mailboxId, causing currentValue to resolve to bare 'personal' which matched ...
Raise file upload size limit to 500MB
Spring Boot defaults cap multipart parts at 1MB, causing background image uploads to fail with 500 in request.getParts(). Match SecurityService's existing 500MB...
Surface HTTP errors so upload failures are visible
Replaced silent console.warn with a thrown Error on non-OK status, so the user sees the actual failure reason (status + response body) instead of silently proce...
Add Accept+XHR headers and raw-string response parsing for token endpoint
FreePBX was returning text/html when called via cluster DNS because the Host header didn't match the configured PBX_HOSTNAME. Adding Accept: application/json an...
Drive minimized-tab entry through animation controls
Switching animate={...} to animate={tabControls} broke the entry animation: tabControls has no queued target on mount, so the button stayed at its initial { y: ...
Correct OAuth2 scope strings for FreePBX API
FreePBX scopes are top-level identifiers: 'gql' (all GraphQL) and 'rest' (all REST). The format 'gql:read' means GQL for a module named 'read', which doesn't ex...
Expose port 8088 directly on ClusterIP service
Service only had port 80→8088 mapping. Internal services using **************** (e.g. VOIPService API token requests) were timing out because port 8088 had no l...
Trust backend pendingCount, drop client offsets
Backend pendingCount is now unread-based (counts SUPPORT_TICKET sessions with messages newer than the owner's lastViewed). When the agent opens a ticket, markAs...
Add missing fail2ban-config ConfigMap for k1m1
Deployment references fail2ban-config ConfigMap (jail.local + asterisk-security.conf) but it did not exist, blocking the pod at MountVolume.SetUp and preventing...
MarkAsRead after agent send so lastViewed advances
On support tickets, both visitor and agent messages are stored under the same systemMember, so the unread-count query is member-agnostic and relies entirely on ...
Support tickets need member-agnostic unread counts
Visitor messages on support tickets are stored under the same systemMember that the agent authenticates as, so the existing CHAT-session unread queries — which ...
Pass folder to loadThread retry button
loadThread takes (threadId, folder); the error-state retry button only passed threadId, breaking the kamo-internal build. Match the call to the signature.
Correct JsonIdentityInfo property from 'uniqueId' to 'id'
Member entity declared @JsonIdentityInfo(property = "uniqueId") but the actual primary key field is named 'id'. Jackson threw "cannot find property with name 'u...
Remove SSR guard causing hydration mismatch; drop SockJS for native WebSocket
- Hero.tsx: remove typeof window check around ParticleField — ParticleField already has ssr:false so the guard produced a server/client tree mismatch (React #...
Clear unread indicators when conversation is opened
ThreadDetailView now dispatches email:folder-changed after markConversationRead succeeds so MailButton refetches the navtop inbox badge count. ConversationListV...
Move CORS to Traefik via kamo-middlewares, remove in-app CorsFilter
The CorsFilter @Bean in the Spring app was silently not applying headers after WebConfig.java was removed. CORS is now handled entirely at the Traefik layer by ...
Use /public/{mainDomain}-{tld} path matching actual MinIO bucket layout
The getThemePath() was generating /domain/{tld}/{split}/kamosite paths that don't exist in MinIO — theme files live in the public bucket under kamocrm-com/, bad...
Break min-w-0 chain to contain email scroll in body column only
Add min-w-0 to outer container and main content flex div so flex children cannot expand to email min-content width, which was causing the page-wide horizontal s...
Hoist providers above ToolProviders
Same root cause as the ChatWebSocketProvider hoist: SupportTicketWindow calls **************** and ChatBox calls useUnread().markAsRead(), but both tool windows...
Add suppressHydrationWarning and fix broken placeholder images
suppressHydrationWarning on <html> prevents React error #418 caused by browser extensions (e.g. translate, dark mode) mutating html attributes between SSR and c...
Allow /domain/ path prefix on theme IngressRoute for CORS
The theme config at theme.kamocrm.com/domain/... was not matched by the existing route rules (only /public/ and /internal/ were allowed), so Traefik returned a ...
Remove duplicate CorsFilter and strip upstream CORS headers in gateway
WebConfig.java defined a second CorsFilter bean competing with CorsConfig.java's bean, risking duplicate header writes. Deleted it so only one CorsFilter exists...
Remove duplicate CorsFilter bean causing double Access-Control-Allow-Origin header
The explicit CorsFilter bean caused a second CORS filter alongside Spring Security's built-in CORS support (which uses CorsConfigurationSource). Both filters re...
Like what you see shipping?
Every one of these updates lands in your workspace automatically. Start free and watch it grow week after week.