#23 — List Color & Icon Customisation #23
Labels
No labels
priority/could
priority/must
priority/should
priority/wont
status/blocked
status/claimed
status/done-migrated
type/bug
type/feature
type/infra
type/tech-debt
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
robert/todo#23
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Story: List Color & Icon Customisation
As a list owner,
I want to assign a colour and icon to a list,
so that I can tell lists apart at a glance in the sidebar.
Acceptance criteria:
story's broom example — lucide-react 1.24 has no broom glyph, see design doc)
TodoListBroadcastDtoWebSocket pathOut of scope for this story:
Blockers: None
Priority: Could — low effort, high perceived polish; good sprint filler alongside a heavier feature.
Resolution
Delivered 2026-07-21. New
TodoListColor/TodoListIconclosed enums,SetTodoListAppearanceCommand(owner-only), and a
ListAppearanceDialogpicker wired intoOwnerActionsMenu's new "Customise"item. See
23_list_color_icon_design.mdfor the full design rationale./code-review(8 finder angles, high effort) found and fixed one real bug:RemoveTodoListMemberCommandHandler'sbroadcast had never forwarded
IsArchived(pre-dating this story, but re-exposed on the exact linethis story touched to add
Color/Icon) — an archived list's members-changed broadcast silentlyreset
isArchivedtofalseon every connected client until the next full refetch. Fixed alongside.Review also surfaced a recurring pattern worth tracking rather than fixing inline:
TodoListBroadcastDtois hand-constructed from
TodoListDtoat 8 separate call sites, and this is now the second field(after
IsArchived) to have been silently dropped at one of them because the compiler can't catch amissing optional constructor arg. Drafted as
docs/features/new/62_todolist_broadcast_dto_mapping_helper.mdrather than refactored in this cycle, to keep the fix scoped to the one bug actually found.
design (
23_list_color_icon_design.md)Design: List Colour & Icon Customisation (
#23)Data model
Two new plain C# enums (not Vogen VOs — matches the
LabelColorprecedent for closed palettes;no validation logic is needed since the type system + JSON deserialization already reject
out-of-range values):
Common/Types/TodoListColor.cs11 values —
Greyis the neutral default the AC requires, plus 10 vivid colours for the picker(satisfies "at least 10 colours").
Common/Types/TodoListIcon.cs18 values —
Listis the generic default, 17 more for the picker (satisfies "at least 15 icons").The story's example set includes a broom (); lucide-react 1.24 has no broom glyph, so
Sparklessubstitutes for cleaning-flavoured lists — captures the same "chore" intent without a literal match.
Both stored as
textviaHasConversion<string>(), same asLabelEntity.Color— one EF migration(
AddColorAndIconToTodoList) adding two non-nullable columns with defaults (Grey/List) toTodoListEntity.API surface
One new command, matching the
RenameTodoListCommandshape (owner-only, single mutation, broadcaststhe updated list):
Both fields are set together in one call — the picker UI (colour grid + icon grid in one dialog)
never needs to persist a partial selection, so there's no reason to split into two commands.
Authorization:
AuthorizeTodoListOwnerAccessForCurrentUserQuery, identical toRenameTodoListCommandHandler.No activity-feed entry — cosmetic-only change, doesn't rise to the level of
ListRenamed/listmembership events already logged (keeps the activity feed focused on collaboration-relevant events).
TodoListDto,TodoListBroadcastDto, andTodoListSaveDtoall gainColorandIcon(defaultedto
TodoListColor.Grey/TodoListIcon.Liston the create-timeTodoListSaveDtopositionalparams, so
CreateTodoListCommanddoesn't need a frontend change to keep compiling — the AC onlyasks for edit-after-creation via the list menu, not a colour/icon picker in the create-list flow).
Frontend
ReactUi/src/lib/listColors.ts— mirrorslabelColors.ts:Record<TodoListColor, string>ofliteral Tailwind
bg-*-500classes (JIT scanner needs literal strings, not template-built ones).ReactUi/src/lib/listIcons.ts—Record<TodoListIcon, LucideIcon>mapping each enum value to itslucide-react component reference.
OwnerActionsMenu.tsx— new "Customise"DropdownMenuItemopening aDialog(consistent withRename/Delete/Transfer, all
Dialogin this menu — notPopover, since the trigger lives in thesidebar's owner menu where clipping was the reason
#58chosePopoverfor an inline rowcontrol; this dialog is invoked from a dropdown, same as Rename already is).
ListAppearanceDialog.tsx— colour swatch grid + icon grid,SavecallscallApi('SetTodoListAppearanceCommand', {id, color, icon}).TodoListItem.tsx— small coloured dot + icon glyph before the title in the sidebar row.TodoListHeader.tsx— same swatch + icon next to the<h2>title;TodoListHeadergainscolor/iconprops, passed fromTodoList.tsx'sselectedTodoList.updateTodoListalready does a spread merge(
{...t, ...todoList}), so new broadcast fields flow through automatically.Real-time
SetTodoListAppearanceCommandHandlerpublisheschangeSubject.Updated(id, TodoListBroadcastDto)with the new fields populated —
UserScopedTodoListChangePublisherneeds no change, it's alreadygeneric over
TodoListBroadcastDto.Out of scope (per story)
Custom icon uploads, colour themes beyond the list accent, per-member overrides. Not touching the
create-list flow's dialog — colour/icon are edit-only via the owner menu, per AC wording ("The list
menu ... includes a 'Customise' option").
Security note
Pre-reviewed: both new fields are closed server-side enums — no free-text input, no injection
surface. Mutation gated by the existing
AuthorizeTodoListOwnerAccessForCurrentUserQuery(owner-only,same as Rename/Archive). No new endpoint-exclusion-list concern — this command is meant to be
publicly callable over HTTP like every other list-mutation command.