Stamp unseen counts on the ticket list, and keep the queue live
getTickets now bulk-loads an unseen-message count per row for the caller, beside the visitor rows it already loads that way, and getTicketDetail computes its ow...
One conversation, many hands — join on open, name every message, re-open on a reply
A support ticket is worked by whoever is helping, not only by whoever it is currently stamped with. Assignment decides who the work is offered to and whose list...
Declare the object-storage secret the ConfigMap no longer carries
The MinIO key was moved out of the ConfigMap into the minio-app-credentials secret, but the envFrom that supplies it was only ever applied to the live deploymen...
Stop shipping a shared credential as a config default
The platform's shared password was baked in as the fallback for DB_PASSWORD and MINIO_ROOT_PASSWORD, so it lived in this repo and in the built image as well as ...
Reassignment reaches the new agent, and the requestor is told who is there
Reassignment did not reach anybody. assignmentOpenedAt records that a client put the conversation on screen, and it is what raises a ticket at sign-in — but it ...
Assigning by hand must not consume the PRE_TICKET status
assignToAgent and reassignAgent set ASSIGNED unconditionally, which quietly made a chat request impossible to convert afterwards — convertPreTicket accepts noth...
Validate who a ticket may be handed to by hand
assignToAgent and reassignAgent checked only that the id resolved to a member. Assignment adds that member to the ticket's chat session, so an id from another o...
Auto Assignment offers the ticket, it does not allocate it
Auto Assignment pages every available support agent and goes to the first one who accepts. It was implemented as "silently pick the least-busy agent who is onli...
Run the topic's strategy for widget chats too
A chat opened in the web widget is the same unit of work as one filed inside the app, but this path skipped the topic's assignment strategy entirely — the only ...
Endpoints for the sign-in auto-open handover
GET /sessions/chat/auto-open returns the chat conversations this member's client should raise; POST /sessions/{guid}/auto-opened records that it did. The durabl...
Put the session type on the activity frame too
The unread/ACTIVITY broadcast is published for every session type and carried no indication of which. The chats list, having nothing to go on, typed an unseen s...
Tell the client which kind of conversation a message arrived in
Every member of a media session receives the incoming-message notification, and assigning a support ticket makes the agent a member of that ticket's session. Th...
AUTO_ASSIGNMENT, and raise an assigned conversation on the agent's screen
A chat request and a submitted ticket are the same unit of work; the topic's strategy is the only thing that decides how either is handed over. The PRE_TICKET s...
Reading a session no longer renews it
The expiration on the Redis record IS the session; when it lapses the member is signed out. This service holds its own copy of KSessionService against that SHAR...
A live chat is claimed through the popup, never allocated
Routing chat requests through round-robin was the wrong model and it broke the thing it was meant to fix. An assigned ticket leaves the claimable queue (Support...
Stop assignment failures destroying the ticket that triggered them
Routing a new ticket ran inside the creation transaction, so anything it hit rolled the ticket back with it and the member asking for help got "Failed to create...
Route a chat request through the topic's assignment strategy
A support chat request is filed as a PRE_TICKET, and createTicket skipped assignment for those outright. The guard was written for the web-chat widget, where a ...
Rebuild against shared-library @Lob LONGVARCHAR fix
Hibernate's PostgreSQL dialect read @Lob String columns as OIDs via getLong(). Affects notes, contacts, calendar, OAuth tokens, IMAP passwords, email campaign b...
Point at YugabyteDB and use PostgreSQLDialect
CockroachDB has been replaced by YugabyteDB. Connection strings move from cockroachdb-public:26257 to yb-tserver-service:5433, and the Hibernate dialect from **...
Restore CRLF endings in KSessionService
The previous commit rewrote the file with LF, turning a 15-line change into a 577-line whole-file diff. Content is unchanged; this puts the endings back so the ...
Stop renewing the shared *** TTL for idle users
This service keeps its own copy of KSessionService, and it slid the TTL on every read. Gating SecurityService alone therefore fixed nothing: kamo-internal point...
Accept a BLAKE3-only claim so dedup is faster than uploading
The link endpoint demanded both hashes. A browser has no fast SHA3-256, so proving it already had a 358 MB file took ~20 seconds against an 18.8 second upload —...
Attach an already-stored file without uploading it again
Forwarding a 2 GB video to three colleagues cost three full uploads. Storage deduplicated them into one object, but only after each transfer had completed and b...
Fail a dead upload in minutes instead of holding it for an hour
connectionUploadTimeout is the tolerated SILENCE between reads, not a budget for the whole transfer. Setting it to an hour to 'match Traefik' confused the two: ...
Stop Tomcat's 60s connection timeout killing a long upload
A 2 GiB chat attachment failed after nearly four minutes of successful transfer with MultipartException wrapping a bare SocketTimeoutException, and the member w...
Accept 3 GiB attachments by streaming them to storage
file.getBytes() cannot represent an upload this large — a Java array stops just above 2 GiB — so every attachment past that died on an OutOfMemoryError regardle...
Catch OptimisticLockException, the type 40001 actually wraps as
Round 3 review fix: hibernate-core's **************** tests LockAcquisitionException (what SQLState 40001 converts to) against four specific types in order, non...
Catch the exception type the claim conflict actually throws
Round 2 review fix: the statement-level 40001 (the common case, detected inside executeUpdate() rather than at commit) surfaces as **************** via Hibernat...
Mirror Maven Central and retry, so one bad byte stops killing the build
The startup fix could not ship: the build died resolving byte-buddy with "Tag mismatch", which reads like a missing artifact but is a download corrupted mid-tra...
Return 409 on every lost claim race, add manager claim path
Round 1 review fixes for the accept-popup claim endpoint: - an ordinary lost race (loser's isClaimableBy already false post-commit) now raises TicketAlreadyCl...
Scan the email repositories DepartmentService now needs
MediaService CrashLooped on every pod since shared-lib 1.5.0: DepartmentService is a @Service, so the wildcard @ComponentScan("com.kamo") builds it here whether...
GET /types/{typeId}/slots so the settings editor can list slots
The settings UI has always fetched this path, but only POST/PUT/DELETE were mapped, so the Availability Slots tab got a 405 and rendered empty. Unlike /slots/av...
Accept the timeline provider filter as repeated params
The web client sent ?providers=facebook,x as one CSV value. A comma in a query VALUE has to travel as %2C, and a hop that re-encodes turns that into %252C; we d...
Send missed-message emails to the user account, not the org mailbox
The notice existed to say "you have a message waiting, sign in and read it", and it was being delivered to the mailbox this organization assigned the member — w...
Give every agent a live badge instead of one owner an unread count
The nav badge and /support?tab=tickets answered different questions. The page lists every ticket in the org; the badge counted unread chat messages belonging to...
Drop client SENDs to guarded topics, and repair the build at HEAD
TWO THINGS, both of which stop MediaService main from shipping. 1. main does not compile. MediaController:1361 calls **************** which does not exist: ...
Relay the legal-package count and guard per-member SUBSCRIBEs
legal.packages.> -> **************** core NATS, no stream — the same shape SupportStompRelayController and PlatformStompRelayController already use. topicFor is...
Announce a message once, when it is whole
Both callers that finish building a message after the row is saved now tell createMessage not to announce it: MediaController attaches the images and publishes ...
Store attachments through the dedup pipeline instead of one object per upload
Attachments wrote their own "<imgId>/<fileName>" object on every upload, bypassing the content-addressed store entirely — the same logo-draft.png attached three...
Rebuild for the unified imaging bucket
Picks up kamo-shared-library 3045cc3: ImageAssocType.getBucket() now returns the single "imaging" bucket for every association, so a deduplicated file is stored...
Relay SecurityService platform events onto STOMP
SecurityService owns the platform-admin surface — system configuration, DNS provisioning, commerce sync — but has no WebSocket endpoint, so every screen backed ...
Rebuild against kamo-shared-library 8f3c1ec
Picks up the intake-pool fix: LeadService.createLead now flags unowned leads with requiresAssignment so imported leads are counted by countAssignablePool and ca...
Capture the demo lead before the booking step
The marketing site's demo form collected name, email, company, company size, industry and a message, then advanced to scheduling without telling anyone. Lead cr...
Meeting provider abstraction with Zoom and Microsoft Teams
Meetings were Kamo Meets by construction — MeetService persisted a Meet room name and nothing else. This introduces the abstraction and two remote providers beh...
Persist MediaService PHI access trail to phi_access_log
Chat message reads were recorded through the Slf4j default writer, which puts the §164.312(b) trail in a 14-day log stream against a six-year retention obligati...
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.
