Yuvomi
Self-hosted family planner for tasks, calendar, shopping, meals, and budget
Alternative to: cozi, familywall, skylight

v2.49.0
2026-08-27Fixed
-
An appointment nobody picked a colour for lends the colour of the person it belongs to (#891). Since v2.36.0 every appointment looked as though someone had chosen its colour, so the assigned member’s colour never showed - the reporter saw the avatar next to an appointment that no longer matched it.
It was a missing state, not a wrong rule.
calendar_events.colorwasNOT NULLand rejected the empty string, so there was no way to say this appointment has no colour of its own: the dialog wrote the first palette entry into every new appointment and the sync wrote the calendar’s colour into every imported one. Both are inherited values, and once stored they were indistinguishable from a deliberate choice - which is why they outranked the person’s colour, the rule from #815 being that an explicit value beats a derived one. That rule stands; a never-made choice simply is not an explicit value.The column may now be
NULL, the import paths of all four sync providers stop copying the inherited calendar colour into it, and the event dialog gains a first swatch, “colour of the assigned person”, which is where a new appointment starts. Choosing a colour still keeps it, for the appointment and across syncs.Existing appointments are left untouched.
#007AFFlooks like an old default - no current palette contains it - but it was the first entry of the event palette before the OKLCH switch, so an appointment from the v1 era may well carry it on purpose. A migration cannot tell the two apart, and discarding a real choice is worse than changing nothing: synced appointments normalise themselves on the next sync, local ones through the dialog.Clearing a colour reaches Google too. The outbound push is an
events.patch, and a PATCH touches exactly the fields present in the body - so omittingcolorIdmeans “leave it alone”, not “clear it”. Google would have kept the old colour while Yuvomi showed the assignee’s, and because the same edit setsuser_modified = 1, no inbound run would ever have reconciled the two again. The payload now carries an explicit null. Not when the colour merely cannot be mapped, though: a missing palette is not a missing colour, and a null there would throw away a colour nobody asked to remove.The inherited colour belongs to the primary assignee, the one named in
assigned_to- not to whichever row the assignment query happens to return first, since it aggregates without anORDER BY. With several people on an appointment the colour could otherwise belong to a different member than the assignment means, and change between reloads without anyone touching it.The countdown tile borrows the colour too. It read
colorstraight off the event and fell back to the module tone - fine while every event carried a colour, but the same appointment would now have shown the assignee’s colour in the calendar and a generic tone on the tile right next to it. It resolves through the shared rule as well; only when no source has anything does it keep falling back to the module tone, which says more than a neutral grey.A
PUTthat does not mentionassigned_tono longer re-picks the primary assignee. The route reloaded the assignment ids and wrote the first one back - but that query has noORDER BY, so it returns them by user id rather than in the order the form sent. With the borrowed colour followingassigned_to, an appointment could change colour because someone split a series (that request carries onlyrecurrence_rule). Same rule as for the colour itself: not sent means not touched.A deleted member no longer leaves a countdown colourless. When the primary assignee is deleted, the foreign key clears
assigned_toand takes that one assignment row with it while the others stay. The calendar falls back to the first remaining assignee; without the same step the tile would have been the only place showing a generic tone.Two more things surfaced while building it. The overview resolved event colours on its own (
color || cal_color, without the assignee branch) - harmless while every appointment carried a colour, but two visibly different answers to one question as soon as one might not; both pages now read the same rule frompublic/utils/event-color.js. And an ICS subscription has noexternal_calendarsrow, so its colour had to reach the display over its own path - it is now read fromics_subscriptions, and appointments from a subscription keep their feed’s colour.