Normalize shared_with email casing across all project access paths
Project sharing in Mike was silently failing for users whose stored email casing didn't match the lookup. bmersereau traces the root cause to three different normalization rules scattered across four endpoints, extracts them into a single access module, and routes every project route through it.
The inconsistency was specific. The single-project GET ran a case-sensitive membership check against shared_with. The project list query filtered on the raw, un-normalized requester email. POST stored entries verbatim. PATCH was already lowercasing. The result: legitimate shared users got denied access depending on which casing path their email had taken on the way in.
The fix extracts two helpers into a dedicated access module: one to normalize a shared_with array on write, one to do a case-insensitive membership check on read. Both are small, focused, and independently testable. Every project endpoint now routes through them.
The write normalization does more than lowercase. It also deduplicates entries and drops empty strings, which is light cleanup bmersereau folded in alongside the casing fix.
Eight unit tests cover the obvious edges: lowercasing, dedup, empty entries, null and undefined emails, and the case-insensitive membership check. Before this change those tests couldn't exist because the normalization logic was inline, duplicated, and drifting across three handlers.
Spotted something wrong? Or know the PR text has fresher detail than the writeup above?