மாற்று பதிவேடு
என்ன புதிது.
அது கப்பல்கள் போல், தளத்தில் ஒவ்வொரு மேம்பாடு.
· kamo-internal
Tell the member what the event collides with, as they set it
The most useful thing a scheduling dialog can say is "you already have something then", and the endpoint to answer it existed all along with nothing calling it. The dialog now asks as the times change — debounced, because the datetime inputs fire per keystroke — and reports the clash inside the When card, directly under the controls that caused it, rather than as a banner somewhere else or an error after saving. Editing passes excludeEventId so an event does not report itself. Two more things that were two-field edits and are now one tap: - Today and Tomorrow move the event to that day while keeping its time and duration. "Same time tomorrow" is a far more common intent than picking a date out of a widget. - The save shortcut is shown in the footer, rendered per platform, so it is discoverable rather than folklore. Every string is an existing key: conflictsWith already carried a {names} placeholder for the week and day views, and common.time supplied the day labels. The shortcut hint is symbols, so it needs none. Verified with a production build in a detached worktree — another session holds the in-tree build lock.
· EmailService
Let the conflicts endpoint exclude the event being edited
ConflictDetectionService has always taken an excludeEventId; this endpoint passed null. Without it an event overlaps itself, so the moment the event dialog started asking, editing anything would report a clash with the thing being edited. Also answers with DTOs like the rest of this controller. The raw entity published a member, organisation and calendar that Jackson renders as null anyway, and a good deal more besides.
· kamo-internal
Rebuild the event dialog around when, not around fields
Ten identically weighted outlined fields in a column, in the order the columns happen to sit in the table: title, description, location, calendar, all-day, start, end, colour, private, recurring. Nothing signalled that the title and the times are the whole job and the rest is usually left alone. Reordered by what is actually decided, and given hierarchy to match: - The title is set like a heading being typed — large, borderless, autofocused — because it is the one thing filled in every single time. - When becomes the hero block: the two times in mono, with the duration derived and shown live between them, and 15m/30m/1h/2h presets that set the end from the start. That is how a meeting is actually decided ("make it an hour"), and the pair of datetime inputs never answered it. - Calendar and colour become chips and swatches instead of dropdowns. There are rarely more than a handful of either, the colour is as much the thing being chosen as the name, and a dropdown of colour *names* made you read a word to pick a swatch. The duration is also the validation. When the end precedes the start the chip turns red and reads a negative value — "−30m" states the problem more precisely than a sentence, needs no new translation key, and is right in every language. Save is disabled on that and on an empty title, both of which are already visible on the form. Cmd/Ctrl+Enter saves. This dialog is opened, filled and dismissed dozens of times a day. Fixes a real bug while here: the time fields were populated with toISOString(), which is UTC, into inputs that expect local wall time. In California the dialog opened offering a start seven hours away, and any event created without touching the field landed at the wrong hour.
· kamo-internal
Auto Assignment strategy, and open an assigned conversation automatically
Topics settings gains the fourth strategy. The list rows and the edit dialog share the same label, and the key shipped to kamo-translation-dictionary first — messages/ is gitignored here and next-intl throws on a key missing from en.json, so landing this ahead of the dictionary would ship a build whose page crashes. AssignedTicketOpener raises the ticket window the moment work becomes this member's, and again on sign-in for anything assigned while they were away. Both paths run one fetch of /assigned-unopened, so "it arrived while I was here" and "it arrived while I was gone" are one case rather than two mechanisms that have to agree — the ASSIGNED push is core NATS and is simply lost if nobody is listening, which is why it is treated as a hint and the server holds the record. Not gated on availability or on the accept-popup's rules: this is not an offer that can be decoderd, the work is already assigned. The popup exists for the opposite situation. Unlike the NEW_TICKET auto-open removed in addd9036, ASSIGNED only ever reaches the one agent it concerns, so it cannot open a window on every screen in the org.
· MediaService
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 special case is gone. AUTO_ASSIGNMENT picks the least-busy agent who is genuinely online — a live WebSocket via PresenceService, never availability_status, which only moves on login, logout and a manual toggle and so reads AVAILABLE indefinitely for anyone who never signs out. With nobody connected it decoders rather than pinning work to an absent agent: an assigned ticket leaves the claimable queue, so a bad pick strands it where no popup can reach it. For the same reason AUTO_ASSIGNMENT joins the claimable strategies — the tickets it decoders are exactly the ones that arrived while nobody was online, and they still have to reach somebody. ADMIN_ASSIGNS remains the only strategy that is never claimable. Workload counts every live status including PRE_TICKET (a person waiting in a chat is the heaviest thing an agent holds) and no terminal one. Two endpoints back the handover: GET /tickets/assigned-unopened, which the client reads on sign-in, and POST **************** which retires one. The read exists because the ASSIGNED push is core NATS with no replay — without it, work assigned while an agent was signed out simply waits in a list to be noticed. The write is scoped to the assignee and stamps only once, so two tabs racing is a no-op rather than a conflict.
· kamo-shared-library
AUTO_ASSIGNMENT strategy and a durable record of an unseen handover
AUTO_ASSIGNMENT(3) is appended to SupportAssignmentStrategyType — assign immediately to an agent who is genuinely online, preferring whoever holds the least work, rather than ROUND_ROBIN's strict presence-blind rotation. Appended, never reordered: the column is @Enumerated(ORDINAL). The column also carries a numeric range CHECK Hibernate emitted on CREATE TABLE and never revises. Because that clause contains AND it is not an enum whitelist, so SchemaCleanupRunner's generic dropStaleEnumWhitelistChecks() sweep skips it by design; widening it is a matching change in that runner, without which every insert using the new value fails. SupportTicket gains ASSIGNMENT_OPENED_AT. Assignment is announced over core NATS with no replay, so an agent who was signed out when work landed never hears about it. Null from assignment until a client actually puts the conversation on screen, it makes "assigned to me and never seen" a server-side fact that survives a logout, a crash, a redeploy or a different machine. Two repository queries to go with it: countOpenForAgent (the workload tie-break) and findUnopenedAssignmentsForAgent (the sign-in catch-up).
· kamo-internal
A pinned app's chip launches the app, not its settings
The leading chip of a pinned band was linking to the app's settings landing — the same place its first entry already goes. It now borrows that app's nav option wholesale, so it carries the app's own icon and behaves exactly as the nav does: CRM opens /leads under the Leads bolt, AI Support opens the assistant window, Support and Mail and Docs and Phone drop down their sub-option menus, Chat opens the interaction center, Meetings opens the new-meeting invite. Chat and Meetings are fixed nav chrome with no registry option, so the band opens them itself — the interaction center through a window event NavTop listens for, since its open state lives there. A member who can configure an app but cannot use it holds no nav option for it, and that band keeps the old settings-landing chip rather than being handed a door that would refuse them. Also fixes the band eyebrow, which was inheriting the operator-label ink meant for light surfaces and disappearing into the org band.
· EmailService
Sync the organisation calendar to phones, without opening a hole
The shared calendar has no owner, so the account-scoped DAV listing could never return it: company events were plainly there on the web and invisible on every phone. The home collection now unions the account's own calendars with its organisation's shared one. The reason this needed care rather than one extra query: CalDAV authenticates an account, not a membership, so simply exposing the calendar would have made it writable by anyone whose phone could reach it — a way round the publishing right the web path enforces. PUT and DELETE now resolve the membership behind the DAV credentials and apply the same rule: CREATE_ORG_CALENDAR_EVENTS to write to the shared calendar, ownership to write to a personal one, 403 otherwise. Read access stays deliberately wider than write. The shared calendar is meant to be visible to everyone in the organisation; that is the point of it. Only publishing is restricted. Tests cover the bypass specifically: no right, no membership in that organisation, and another organisation's shared calendar are each refused, while a holder of the right is allowed.
· InitializerService
Add and backfill accounts.date_created
Hibernate cannot add this one itself: ddl-auto emits it NOT NULL, which CockroachDB refuses on a table that already has rows. So it is added nullable here, backfilled, then constrained. The backfill decodes each account's unique_rowid() primary key, which packs its insert time into the high bits — that recovers each account's real creation time instead of flattening all 23 existing rows onto the migration timestamp, which would have made the list's ascending date order meaningless for exactly the accounts that have been around longest. That layout is a CockroachDB implementation detail, so the decoder is first checked against members, which have both a unique_rowid() key and a recorded creation date; it is only applied if it reproduces theirs. On the production run it matched 62 of 62 and recovered all 23 accounts, none falling to the fallback. Order 0, ahead of every other runner: the entity maps the column as soon as this build starts, so any runner that loads an Account emits it in its SELECT. Ordered late, this one never ran at all — LeadContactPointSeedRunner died on "column a1_0.date_created does not exist" and took the run down with it.
· kamo-shared-library
Record when an account was created
Account carried no creation timestamp at all — not a null column, no column. The account list has a Date Created column and orders by it, so that column has been blank for every account since the table was created. Sourced from the database rather than the JVM, per the house rule for timestamps. The matching column and the backfill for existing rows are in KamoInitializer's AccountDateCreatedMigration, which has already been run.
· kamo-internal
Pin an Enabled App to the home launchpad
Each Enabled Apps topic now carries a pin toggle, in the Settings sub-option menu and on its section header on the settings landing page. A pinned app gets its own horizontal shortcut band on the home launchpad — the member-chosen counterpart to the administrator bar, carrying every screen that app opens. A pin is a display preference, never an entitlement: the pinned list is intersected with the member's live gates on every render, so a revoked right or an app the org switches off removes the band immediately, and the stored preference is pruned to match rather than lying dormant. The settings tree behind both the menu and the bands now comes from one resolver (useSettingsTopics), so they cannot drift.
· kamo-internal
Give Mobile Copilot setup its own full-width page
The phone-provisioning surface was the third tab of the Interaction Center slideout, which capped it at a 500px rail — too narrow to show the install code and the sign-in code side by side, so the two steps were collapsed behind a toggle and the whole flow read as a column of stacked fragments. band whose sign-in cell goes live and counts the code down, then a deck of panels that widens with the viewport — two on a tablet, three on a laptop, four across past 1800px. Both codes share one viewfinder frame with a scan sweep, which is what makes the two steps read as one instrument. The OTK flow moved into useMobileSignIn so the hero and the sign-in panel read the same state. The nav rail and home launchpad entries now route to the page instead of dispatching open-interaction-center, which leaves that event with no dispatcher, so it and the panel's initialTab prop are gone.
· kamo-internal
Rebuild the calendar around the current moment
The page was a functional grid with the chrome doing the talking: a 3px rainbow rule under the toolbar, a second one over the content, gradient buttons, and four loose icon buttons for the view switch. Loud where it did not matter and quiet where it did — nothing on screen answered the question a calendar is actually opened to answer. The organising idea is now: what is next, and how much of the day is left. - NextUpBar sits above the grid with the time, the next event and a live countdown, over a hairline showing how far through the working day it is. - NowSpine draws the current moment across the week grid with its time in the gutter, and PastVeil desaturates the hours already gone so what is left of today reads at a glance. The colour is reserved for these: they are the only live thing on the page, so they are the only thing allowed the tenant's colour at full strength. - ViewSwitcher replaces the four icon buttons. Four icons read as four unrelated actions; a segmented control reads as one setting with four positions, which is what it is. The selected pill slides between them via a shared layoutId rather than crossfading. Time is set in Geist Mono with tabular figures throughout — hour rail, clock, countdown, date numerals — so columns of time are a straight edge and digits do not jitter as they tick. CalendarThemeScope mirrors the contacts console: MUI's alpha() cannot parse var(), so painted properties are overridden and color-mix supplies the shades. Every token derives from --color-primary, so this reads as designed under any tenant's brand rather than assuming one. Month event chips become a tinted ground with a solid leading edge instead of white text on saturated fill, which was illegible at 11px. Week rows are a fixed height because the spine converts time to pixels — a row that grew would leave it pointing at the wrong time; a crowded hour scrolls instead. Reduced motion is respected: the pulse and the slide stop, the clock does not. Its position is information, and a stopped clock would be wrong rather than calm. Also fixes two real bugs found while working here: the calendar picker ran UUID ids through Number(), so every value was NaN, and it offered calendars the member cannot write to. It now lists only writable ones, which is how the organisation calendar stays read-only without the publishing right.
· InitializerService
Move calendars onto memberships and give each org a shared one
Calendars and events were owned by an account and carried no organisation. This attaches each to its owner's earliest membership — the organisation the account has belonged to longest, which in practice is where the calendar was made — so a calendar lands in exactly one organisation rather than being duplicated across all of them. Accounts where that choice was material are counted in the log, because for them the calendar stops being visible from their other organisations. Events follow the calendar they live on rather than their own user, so an event can never end up in a different organisation from the calendar displaying it. The DROP DEFAULT on user_id is the load-bearing statement. That column predates this change and had carried DEFAULT unique_rowid() from User.id all along, harmlessly, because it was NOT NULL and always written explicitly. Making it nullable so the shared calendar could exist without an owner turned it lethal: an INSERT that omits user_id fires the default, invents a user id belonging to nobody, and fails the foreign key to users. That is exactly how the first run provisioned zero organisation calendars. The entities now pin the column type so ddl-auto cannot reinstate it. Idempotent throughout: the backfills only touch rows with no member yet, and the shared calendar is inserted only where an organisation has none.
· kamo-shared-library
Scope calendars to the member, and add the org-wide publishing right
UserCalendar and CalendarEvent hung off USER_ID alone and carried no organisation. A user is global and a member is that user inside one organisation, so an account belonging to several organisations had one pile of calendars visible from all of them. Both entities gain MEMBER_ID and ORGANIZATION_ID, and a calendar gains IS_ORG_CALENDAR — the one shared calendar an organisation has, readable by every member and writable only with the new right. USER_ID stays, populated and nullable: CalDAV and the external-sync jobs authenticate an account rather than a membership, so they legitimately read by user. It simply stops being the authorisation scope. Every new join column pins columnDefinition = "INT8", including the pre-existing user ones. Left unpinned, Hibernate copies the referenced key's INT8 DEFAULT unique_rowid() onto the FK; an insert that omits the column then invents an id belonging to nothing and fails the foreign key. That is not hypothetical — it is what stopped the first migration run from provisioning a single organisation calendar. New right CREATE_ORG_CALENDAR_EVENTS (196). Reading the calendar stays VIEW_CALENDAR; this governs only who may put something on the calendar the whole organisation sees. findByOrganizationMemberId is renamed findByUserId. It never scoped by member — the query is and was c.user.id = :userId — and the name is what invited this class of bug. findVisibleToMember and its event equivalent are the member-scoped reads.
· InitializerService
Hand shared address-book contacts back to their owners
EmailService now scopes every contact read to contact_books.owner_id, which leaves the contacts already sitting in an ownerless organisation-default book unreachable. CONTACT.OWNER_ID is NOT NULL and has been populated on every row since the create path started setting it, so the member who added each contact is known exactly — nothing is inferred. This provisions a personal book per (organisation, owner) and moves each contact into its own owner's. Two things are deliberately stranded and counted in the log rather than forced: - UID collisions. uk_contact_uid_book is unique on (UID, CONTACT_BOOK_ID), so a contact whose UID already exists in the destination would fail the whole UPDATE and move nothing. Those rows stay put; rewriting a UID would break the CardDAV client that issued it. - Mixed-ownership groups. A contact_groups row has a book but no owner, so a group whose members belong to several people cannot be attributed to one of them. Only groups whose members share a single owner are moved. The ownerless book itself is kept: its check constraint is what makes it legal, and dropping it would cascade into groups and memberships. The stranded-count check tests contacts and groups, not just contacts. A group is attributed through its members' owners rather than through where those members live, so groups stay movable after every contact has already moved; counting contacts alone would skip them on a second run.
· VOIPService
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 **************** to PostgreSQLDialect.
· SecurityService
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 **************** to PostgreSQLDialect.
· RAGService
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 **************** to PostgreSQLDialect.
· MediaService
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 **************** to PostgreSQLDialect.
· MCPGatewayService
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 **************** to PostgreSQLDialect.
· KBService
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 **************** to PostgreSQLDialect.
· kamolos
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 **************** to PostgreSQLDialect.
· ESigService
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 **************** to PostgreSQLDialect.
· EmailService
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 **************** to PostgreSQLDialect.
· DocsService
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 **************** to PostgreSQLDialect.
· DaemonService
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 **************** to PostgreSQLDialect.
· ConversionService
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 **************** to PostgreSQLDialect.
· APIService
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 **************** to PostgreSQLDialect.
· BillingService
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 **************** to PostgreSQLDialect.

உங்கள் உலகம் காத்திருக்கிறது.
நிமிடங்களில் இலவசமாகத் தொடங்கவும். நீங்கள் தயாராக இருக்கும் போது பொருட்களை சேர்க்கவும் - எந்த மாற்றமும் இல்லை, புதிய உள்நுழைவுகள் இல்லை.
எப்பொழுதும் இலவசம் — கடன் அட்டை தேவையில்லை