Changelog
What's new.
Every improvement to the platform, as it ships.
· kamo-internal
Members choose to scroll the message list or browse it by pages
The second column only ever loaded more mail as it was scrolled. A member can now pick Scroll or Pages under Filters ("Browse the list"). The choice is kept per member in ui-preferences (emailListBrowse) so it follows them between devices; Scroll stays the default, so nobody sees a change until they make one. Pages shows fifty messages at a time with First / Previous / Page X of Y / Next / Last at the foot of the list, and "51-100 of 1,240" where the folder tree has a count (the range alone for a label or a smart folder). Turning a page replaces the rows rather than appending, clears the selection and the Live updates keep the member's place: a mailbox change reloads the page on screen instead of returning to page 1, and a page that has emptied steps back one. Search results keep their own pager; only a folder's first page is cached. Switching modes starts again from the top when more than the first page was loaded. Strings: kamo-translation-dictionary 135c6cc5 ****************
· kamo-internal
Chess and Hold'em background music loops while their window is in use
Three new Games sound events — Chess Background, Holdem Background and Holdem Lobby — played on repeat with startLoopingEventSound. Chess loops whenever a chess window is open; Hold'em plays the background at a table and the lobby music in the lobby. Each is heard only while its window is the window in use and the browser tab has focus, and muted (not restarted) otherwise. The loop follows the member's sound settings live.
· kamo-internal
Chess and Hold'em background music loops while their window is in use
Three new Games sound events — Chess Background, Holdem Background and Holdem Lobby — played on repeat with startLoopingEventSound. Chess loops whenever a chess window is open; Hold'em plays the background at a table and the lobby music in the lobby. Each is heard only while its window is the window in use and the browser tab has focus, and muted (not restarted) otherwise. The loop follows the member's sound settings live.
· kamo-internal
Picture frame says when an upload lands, plays videos once or on repeat, and opens full screen
- Uploads show the file, its place in the batch and byte progress, then a toast when it is in. - A frame's videos play on repeat (loop while showing, held for whole plays) or once (later passes show the last frame as a still). - Sound plays only while the pointer is over the frame; "Mute videos" becomes "Sound on hover". - Clicking the frame pauses it and opens what is showing full screen with a download, and a video player with play/pause, restart, seek, elapsed/total, mute, volume and full screen. Closing it resumes the slide with the time it had left.
· kamo-internal
Picture frame says when an upload lands, plays videos once or on repeat, and opens full screen
- Uploads show the file, its place in the batch and byte progress, then a toast when it is in. - A frame's videos play on repeat (loop while showing, held for whole plays) or once (later passes show the last frame as a still). - Sound plays only while the pointer is over the frame; "Mute videos" becomes "Sound on hover". - Clicking the frame pauses it and opens what is showing full screen with a download, and a video player with play/pause, restart, seek, elapsed/total, mute, volume and full screen. Closing it resumes the slide with the time it had left.
· kamo-internal
Onboarding panel for a market with no vendors yet
A market with no vendor products can never show a lead (a lead needs a product), so /leads now replaces the grid for that market with the same onboarding panel used when no markets exist, pointing managers at Vendors & Products. "All Markets" shows it only when no market has any. The CTA opens the vendor settings on that market via ?marketId=, and the markets/vendors cache is re-fetched once per mount while it holds a product-less market, so returning after adding a vendor shows the grid.
· kamo-internal
Onboarding panel for a market with no vendors yet
A market with no vendor products can never show a lead (a lead needs a product), so /leads now replaces the grid for that market with the same onboarding panel used when no markets exist, pointing managers at Vendors & Products. "All Markets" shows it only when no market has any. The CTA opens the vendor settings on that market via ?marketId=, and the markets/vendors cache is re-fetched once per mount while it holds a product-less market, so returning after adding a vendor shows the grid.
· kamo-internal
Getting Started Org Config Wizard button gets a red attention border
· kamo-internal
Getting Started Org Config Wizard button gets a red attention border
· EmailService
A composed message carries its meeting, and calendar edits ask before telling guests
- /api/email/send takes meetingInvite: the event is filed on the sender's calendar in the same transaction, its ics rides on the composed message with an X-Kamo-Calendar-Event header, and recipients are recorded as invited. No second Invitation email. - A calendar invite keeps the member's own attachments. - PUT /api/calendar/events takes notifyGuests: true raises the SEQUENCE and emails every invited guest; false saves without writing to anyone. - Guest invitations and updates go out as the org's branded letters. - GET **************** links a message's invitation to the member's event; events publish guestsInvited and attendees invited.
· kamo-internal
Composer meetings ride on the email, edits ask before emailing guests, mail links to its event
- The composer sends its meeting with the message instead of a second invite. - Saving an edit to an event with invited guests asks whether to send them an update (GuestNoticeDialog); yes sends notifyGuests and keeps email invites on. - A meeting the composer sent before audiences were stored reopens with its guests. - /messages shows a message's meeting with a link that opens it on /calendar.
· kamo-shared-library
Branded meeting invitation and update letters for calendar guests
Adds calendar.meeting_invite and calendar.meeting_updated canonical templates and MeetingInviteEmail, the variable builder EmailService fills them from.
· kamo-internal
Team-member assignment in lead mappings; automated imports on the mapping canvas
Assignment. Manual and automated imports get an "Assigned Team Member" target (replacing "Assigned User", which no importer ever honoured). Its dialog sets a specific team member, unassigned, or matching from fields: an ordered list tried in turn (a blank or unmatched value moves on), with a fallback of nobody or a chosen member. The server matches format-tolerantly and never guesses between two people; values that did not resolve are listed after a manual import and noted on the automated-import queue item. Automated imports move from a table with free-text transformations (which the server never read) onto the same drag-and-drop canvas as manual imports, with sample values from the endpoint's recent submissions. One adapter (leadMappingAdapters.ts) translates the canvas for all four screens. Canvas: transformations are editable from each mapping (the dialog was never reachable), with every server type, config inputs, reordering and a live preview that uses the server's rules; several sources can feed one field, ordered by priority; a split column's part is saved as a splitPart transformation so imports actually take that part; lines draw on load; Delete/Backspace no longer fire while typing; cards work from the keyboard. Template editor: mappings whose column had capitals were dropped on load and deleted on save, because the template's headers come back lowercased.
· DaemonService
Intake job maps through the shared engine, with team-member assignment
LeadIntakeProcessingJob builds each lead with kamo-shared-library's LeadRequestMapper instead of its own field switch and transform copy, so automated imports apply every canvas transformation (defaults now fire on a missing field), reach every lead field, and honour the assignment target: a fixed member, unassigned, or an ordered chain of fields matched to the team (agent email, then agent name, then caller name for Harmony's JustCall). The candidates are the active VIEW_LEADS holders (IntakeAssignableMembers, loaded once per endpoint batch). A value that matches nobody, or a fixed or fallback member who can no longer take leads, is written as a note on the queue item, so the admin can see why a lead arrived unassigned. IntakeAssigneeResolver is replaced by the shared matcher.
· SecurityService
Manual imports and the intake backlog through the shared mapping engine
LeadImportController maps every row with kamo-shared-library's LeadRequestMapper instead of its own switch, so manual and template imports get the full field set, every transformation, and the team-member assignment target. The import result now carries totalAssigned and the assignee values that did not resolve to one team member (assignmentIssues). Mappings and templates are validated on save: a transformation type the server cannot apply is a 400, not a silently ignored string. LeadIntakeController loses its duplicate intake engine. Process Backlog now requeues FAILED payloads and reports what is queued; the DaemonService intake job imports them within a minute, the same path a live submission takes. Saving an endpoint's mappings requires MANAGE_CRM_SETTINGS; replaying the backlog keeps CREATE_LEADS. GrantAllRoleResolver and RightHolderResolver delegate to the shared-lib GrantAllRoles/RightHolders, so the assignable-member list is the same one DaemonService uses.
· kamo-shared-library
One mapping engine for lead imports, with team-member assignment
Manual imports, automated (intake) imports and the backlog each carried their own copy of the mapping code, and they disagreed: date, regex and split transformations were missing everywhere, the intake copies skipped empty values before transforming (so "default value" never fired), several paths the canvas offers were dropped, and "Assigned User" was ignored on all three. leads.mapping is now the single engine: - LeadRequestMapper: rule list -> CreateLeadRequest. Mappings run in order and the first non-empty value per field wins; every leadFields.ts path, every mortgage field and custom form fields land (LeadRequestMapperTest pins it). - LeadFieldTransforms + TransformationsJson: every canvas transformation, one implementation; unknown types are refused at save time. - Assignment target "assignedTeamMember" (legacy "assignedUser" still read): a fixed member, unassigned, or matched from fields. Match fields form an ordered chain - a blank or unmatched value falls through to the next, the first that names one person decides, else the mapping's fallback (nobody or a chosen member) applies. Harmony's JustCall endpoint relies on that chain. - TeamMemberMatcher: format-tolerant, typo-safe matching on email, username and name ("Last, First", initials, nicknames, accents, one small typo); a value that fits two people is never guessed. - LeadAssignableMembers: the candidates are the active VIEW_LEADS holders, the same people the AgentSelector picker offers (RightHolders and GrantAllRoles move here from SecurityService so DaemonService can use them). Also: LeadImportResponse reports totalAssigned and assignmentIssues; LeadIntakeService validates transformations before replacing mappings and can requeue FAILED payloads for the intake job.
· kamo-internal
Call and meeting options on an AI member's voice card (SP92 calls)
Under the AI member's voices in the profile's Identity room: "Transfer callers to a person when they ask for an agent or a human" (on), "Keep full call and meeting transcripts in notes" (off) and "In meetings": "Take notes; speak when addressed by name" / "Talk with everyone, like a phone call". Each saves at once through the BFF relay **************** to AIService; a viewer AIService does not let change them sees them disabled and why. The defaults are how every AI worked before.
· VOIPService
V7 — which colleague is calling from an internal extension (SP92 calls)
GET **************** (KAMOAI_INTERNAL_SECRET) answers the active member of the phone server's org that the extension is ACTIVE-ly assigned to, or 404 NO_MEMBER. kamoai-service asks when a call reaches an AI member from an extension, so the AI knows which colleague called. A phone number, an unassigned or disabled extension, another org's member or one who left is no one.
· AIService
An AI member's call and meeting options, beside its voices (SP92 calls)
keywordTransfer (default on), keepFullTranscript (default off) and meetingMode COMPANION | CONVERSATION (default COMPANION), in ai_member_call_options (hand-applied, re-runnable DDL; no row = the defaults = the behaviour before SP92). GET/PUT **************** with the voices' rights (MANAGE_AI_WORKFORCE, or its supervisor with SUPERVISE_AI_MEMBERS, changes; the AI itself does neither; a retired AI is read only), and the internal twin **************** that kamoai-service reads for every call and meeting.
· kamo-shared-library
MEETING_COMPLETED run trigger, for a conversation meeting's follow-up (SP92 calls)
A meeting an AI member took part in as a conversation partner wakes it once it ends, with its transcript note, the way a phone call's CALL_COMPLETED does. STRING-persisted in ai_runs.trigger_kind (no CHECK); a guard test keeps every trigger name within the column.
· kamo-shared-library
The TRAIT resolver and PersonalityProfile — bounds, locks, the archetype virtual layer, tendency write-through, adaptations, ordered groups, the view and the personality stamp (KamoCollab CP03 T9)
· kamo-internal
The Org Chart tab on Essential Setup, in the settings menu, on the Settings landing page and on the home admin bar (KamoCollab CP02 T24)
**************** mounts OrgChartPanel after Teams: who reports to whom across the organization, read-only, from GET /api/security/org-chart (new BFF relay, forwardToApi + relayUpstream). orgChartApi parses the chart with zod and keeps every id a string; a number id is refused, never rounded. orgChartTree (pure) places each person under their manager when shown, else an AI under its supervisor when shown, else at the root; owners first, then by name; a reporting loop cut at its smallest id ("Reporting loop"); a hidden manager flagged ("Manager inactive"); the department filter keeps the department's subtree and the search keeps name or job-title matches, both with the ancestors that place them. The panel draws a card per person (initials, name linked to the profile through next/link, job title, department and up to three team chips then +n, badges AI, Owner, Inactive, Head of, Lead of and Supervised by when the supervisor is not the card above, the direct-report count), connector rails in --accent-people, three levels open, EmptyState, ErrorState with retry, and a live refresh like its siblings. ErrorState gains an optional retryLabel. Also exposed per master R1-37: { key: 'orgChart' } after teams in the settings menu, an orgChart entry after Teams on the administrator bar, and a landing card after Teams. essentialSetupExposure, settingsLandingCards and teamsTab follow the new tab.
· kamo-shared-library
The seven built-in archetypes — 56 base values, the ANY groups and four adaptations each, the verbatim prose, fill and the built-in lookup (KamoCollab CP03 T8)
· kamo-internal
Teams on the member card and in the directory (KamoCollab CP01 T25)
The member card shows a team member's teams under their title and department: up to three chips in the server's order, then "+n", whose tooltip names the rest. A card with no teams, or whose team read failed (teams left out, CP01-T16-1), draws no team row. Directory rows carry teams through normalizeDirectoryPerson, filterPeople matches a team title, and the member picker puts up to two team titles and "+n" on a person's detail line, beside the department, with the whole line in its tooltip. One parser (app/lib/teams/teamRefs.ts) reads [{id, title}] for both.
· kamo-shared-library
The charter model for the ORG layer, PERSONALITY and DocsTION — the vocabulary, the twelve item columns, the org charter item, org archetypes and their repositories; validation still refuses the new sections (KamoCollab CP03 T7)
CharterLevel appends ORG after CP01's TEAM; CharterRule appends TRAIT and COLLAB; CharterSection appends PERSONALITY (TRAIT, 160) and DocsTION (COLLAB, 60), both aiOnly and not suppressible; CharterValidationReason appends AUTHORITY_LANGUAGE and GROUP_INCONSISTENT. CharterItem, CharterItemData and CharterItemDTO carry the twelve fields of spec section 5.2, and write, toData, toDto, rejectFields and CharterView.item carry them (write refuses a number a SMALLINT cannot hold). MAX_ITEMS_PER_OWNER is 500 (D4). Until Task 12, validate refuses a PERSONALITY or DocsTION item with UNKNOWN_SECTION, and markers are refused at ORG as at ROLE (D2). New ItemMark for the section resolvers. New entities OrgCharterItem (org_charter_items), OrgPersonalityArchetype and OrgPersonalityArchetypeItem with their repositories, and countByArchetypeRef on the role, department, job title and member item repositories. The title check compares lower() like the unique index. The columns and tables exist in production (CP03 Task 3).
· kamo-internal
The mirror knows PERSONALITY and DocsTION (hidden until CP09), the ORG level and the resolved personality and Docstion types (KamoCollab CP03 T21)
- sections.ts: SectionSpec.editor; the two sections appended with editor false; TRAIT and COLLAB rules; sectionsInBand lists only edited sections; MAX_ITEMS_PER_OWNER 500 (D4). - items.ts: hide markers refused at ORG instead of ROLE (D2); sections the editor does not edit are counted toward the cap and otherwise left to the server, like unknown ones (D21, D22). - types.ts: ORG level, OUT_OF_BOUNDS, the twelve item fields, resolved.personality and resolved.Docstion per spec 5.6.4. - The preview panel's counts and the template gallery's chips skip hidden sections. - New charterSectionParity test reads CharterSection.java and CharterItems.java.
· kamo-internal
A department's head — the Head column (name, Vacant, Inactive), the dialog's Head field and picker (active staff; AI members listed but not pickable), set through PUT …/head after the department save; and a member's manager becomes an administrator's to change on the Position card (KamoCollab CP02 T23)
· kamo-shared-library
PersonalityTextLint — the seven authority-language patterns of spec §5.9, the look-forward and {orgShortName} exceptions, typographic apostrophes and Unicode spacing (KamoCollab CP03 T6)
· kamo-internal
Teams on the create-member form, for a person and for an AI hire (KamoCollab CP01 T24A)
/settings/team-member/new gains Teams on its Role card: a multiple pick over the organization's teams (the teams client), offered to whoever could change a member's teams on the profile (teamsEditAtCreate, the Teams field's positionRights gate: MANAGE_MEMBER_SECURITY), and hidden otherwise. The picks go out as teamIds, for a person and for an AI hire (aiHirePayload keeps them); no pick, no key. A refusal of the teams (400/403/409 with field teamIds) shows on the field and in the banner, in the dictionary's words where it has them, NOT_ALLOWED included. An AI hire's charter preview now includes the teams chosen, the TEAM layer it will start with. The live summary names the teams.
· kamo-shared-library
Tendency links — integer derive, the minimal-move inverse with the two-term carry, and the two gates (KamoCollab CP03 T5)
Weakness tendencies are "own controls, linked and option to be fully independent" (owner decision 6). TendencyLinks.LINKS holds the seven links of CP03 spec §5.4; derive is 2.5 x (s - pivot) rounded half up in integers; inverse rounds the target sum once and carries a clamped term's shortfall to the other term (rulings CP03-T5-1..3).

Your universe is waiting.
Start free in minutes. Add products whenever you are ready — no migration, no new logins.
Free forever — no credit card required