feat(02-03): colorUtils + calendarConfig; turn RED calendarConfig stubs green

- Create colorUtils.ts: hexToContainer (15% alpha over white), hexToOnContainer
  (darken 40%), deriveScheduleXColors() returning { main, container, onContainer }
- Create colorUtils.test.ts: hex blend math assertions for #4A90D9 and #F25C7A
- Create calendarConfig.ts: WEEK_START_DAY=0, SX_FIRST_DAY_OF_WEEK=7 (0→7 translation)
  buildCalendarConfig() keyed by String(userId) + 'shared'; returns { firstDayOfWeek, calendars }
- calendarConfig.test.ts (Plan 01 RED stubs) now GREEN: all 4 assertions pass
This commit is contained in:
Lucas Berger
2026-06-05 09:44:19 -04:00
parent 0911a2330a
commit 43554f491b
3 changed files with 272 additions and 0 deletions
+92
View File
@@ -0,0 +1,92 @@
/**
* Schedule-X calendar configuration factory.
*
* Week-start-day translation:
* Project convention: WEEK_START_DAY = 0 (Sunday, JS/date-fns convention)
* Schedule-X v4: firstDayOfWeek = 7 (Sunday, Temporal convention)
*
* Passing 0 to Schedule-X silently defaults to Monday. The constant
* WEEK_START_DAY === 0 ? 7 translation below encodes this as the source of
* truth so the pitfall cannot recur.
*
* calendarId routing contract (must align with hydrateEvents.ts):
* Shared-family calendar → key 'shared'
* Per-member calendars → key String(userId) (NOT String(db calendarId))
*
* Source: https://schedule-x.dev/docs/calendar/configuration
* Source: https://schedule-x.dev/docs/calendar/calendars
*/
import { deriveScheduleXColors } from './colorUtils.js'
/** Week start day in JS/date-fns convention: 0 = Sunday. */
export const WEEK_START_DAY = 0
/**
* firstDayOfWeek in Schedule-X/Temporal convention: 7 = Sunday, 1 = Monday.
*
* Translation: WEEK_START_DAY === 0 (JS Sunday) → 7 (Temporal Sunday).
* Hard-code to the project convention; one edit here when the user wants Monday.
*/
export const SX_FIRST_DAY_OF_WEEK: number = WEEK_START_DAY === 0 ? 7 : WEEK_START_DAY
/**
* Per-member calendar config entry passed to buildCalendarConfig.
*
* id = String(users.id) — the Schedule-X calendars config key
* name = users.displayName
* color = users.color hex
*/
export interface MemberCalendarConfig {
id: string // String(users.id)
name: string // users.displayName
color: string // hex from users.color
}
export interface ScheduleXCalendarEntry {
colorName: string
lightColors: {
main: string
container: string
onContainer: string
}
}
export interface CalendarConfig {
firstDayOfWeek: number
calendars: Record<string, ScheduleXCalendarEntry>
}
/**
* Build the Schedule-X calendars configuration object.
*
* Always includes a 'shared' entry (rose #F25C7A).
* Adds one entry per member, keyed by String(userId).
*
* The keys here are the routing contract: hydrateEvents() must produce
* calendarId values that exactly match these keys:
* isShared:true → 'shared'
* isShared:false → String(ownerUserId) (NOT String(db calendarId))
*/
export function buildCalendarConfig(members: MemberCalendarConfig[]): CalendarConfig {
const calendars: Record<string, ScheduleXCalendarEntry> = {}
// Shared-family calendar: reserved rose color, confirmed by user
calendars['shared'] = {
colorName: 'shared',
lightColors: deriveScheduleXColors('#F25C7A'),
}
// Per-member calendars keyed by String(userId)
for (const m of members) {
calendars[m.id] = {
colorName: `member-${m.id}`,
lightColors: deriveScheduleXColors(m.color),
}
}
return {
firstDayOfWeek: SX_FIRST_DAY_OF_WEEK, // 7 = Sunday in Temporal convention
calendars,
}
}