@ninthforge/scheduler 0.0.0-stage → 1.0.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/Scheduler.tsx","../src/version.ts","../../core/src/diagnostics.ts","../../core/src/date.ts","../../core/src/scale.ts","../../core/src/zone.ts","../../core/src/instant.ts","../../core/src/hours.ts","../../core/src/number.ts","../../core/src/export.ts","../../core/src/roadmap.ts","../../core/src/host.ts","../../core/src/pointer.ts","../../tokens/src/text.ts","../../tokens/src/index.ts","../src/presets.ts","../src/layout.ts","../../editing/src/changes.ts","../../editing/src/useEditing.ts","../../editing/src/Toasts.tsx","../../editing/src/EmptyAction.tsx","../../editing/src/LicenceKeyField.tsx","../../editing/src/Watermark.tsx","../../license/src/codec.ts","../../license/src/browser.ts","../../license/src/index.ts","../src/useLicense.ts","../src/edit.ts","../src/Agenda.tsx","../src/styles.ts","../src/theme.ts","../src/export.ts","../src/sample.ts","../src/CapacityHeatmap.tsx","../src/capacity.ts"],"sourcesContent":["import * as React from 'react';\r\nimport { VERSION } from './version';\r\nimport { useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type CSSProperties, type KeyboardEvent } from 'react';\r\nimport {\r\n copyDiagnostics, cssScale, localPoint, addDaysIn,\r\n atHour,\r\n browserZone,\r\n createInstantScale,\r\n exportFileName,\r\n printDocument,\r\n dayLabelIn,\r\n dayToInstant,\r\n instantToDay,\r\n parseHour,\r\n MONTHS,\r\n MONTHS_LONG,\r\n parts,\r\n timeLabel,\r\n toDay,\r\n workingDaysBetween,\r\n type Zone\r\n} from '@ninthforge/core';\r\nimport {\r\n barTimeLabel,\r\n boardHours,\r\n bookedWorkingHours,\r\n bookingAppearance,\r\n buildRows,\r\n dailyUtilisation,\r\n type DayUtil,\r\n initials,\r\n isoOfDay,\r\n LANE_H,\r\n neighbour,\r\n normalise,\r\n RESOURCE_W,\r\n ROW_PAD,\r\n todayIn,\r\n totalHeight,\r\n unassigned,\r\n cleanResources,\r\n queueView,\r\n QUEUE_SORT_LABEL,\r\n type QueueSort,\r\n stepDate,\r\n viewRange,\r\n visibleRows,\r\n type Item,\r\n type ResourceRow\r\n} from './layout';\r\nimport { EmptyActionView, lockFor, Toasts, useEditing, type EditingSpec, nextUid, Watermark } from '@ninthforge/editing';\r\nimport { licenseMessage, renewalNotice as graceNotice, unlocksPro } from '@ninthforge/license';\r\nimport { markSvg, clampTextScale, scaleWithText, textVars, textSize } from '@ninthforge/tokens';\r\nimport { useLicense } from './useLicense';\r\nimport {\r\n canEdit,\r\n checkPlacement,\r\n createSpan,\r\n describeChange,\r\n dragSpan,\r\n keyEdit,\r\n overlapping,\r\n placeChange,\r\n SCHED_FIELDS,\r\n whenLabel,\r\n type Span\r\n} from './edit';\r\nimport { GROUP_H, HEADER_H, hourLabelFits, hourLabelStep, toMs } from './layout';\r\nimport { PRESETS, VIEW_LABEL, VIEW_STEP } from './presets';\r\nimport { Agenda } from './Agenda';\r\nimport { css } from './styles';\r\nimport { accentOf, palette, resolveTheme } from './theme';\r\nimport { agendaPrintHtml, boardPrintHtml, exportBoardPng, PRINT_TIMELINE_W, type BoardExport } from './export';\r\nimport type { SchedBooking, SchedChange, SchedFields, SchedulerProps, SchedulerView } from './types';\r\n\r\nconst DRAG_THRESHOLD = 4;\r\nconst EDGE_PX = 8;\r\n\r\n/** A pointer gesture in progress. */\r\ninterface Drag {\r\n kind: 'move' | 'start' | 'end' | 'assign' | 'create';\r\n pointerId: number;\r\n x0: number;\r\n y0: number;\r\n /** Instant under the pointer when it went down. */\r\n t0: number;\r\n started: boolean;\r\n /** move / resize / assign: the booking. */\r\n id?: string;\r\n span?: Span;\r\n resourceId: string | null;\r\n}\r\n\r\n/** Where a dragged booking would land (drawn in place of the booking until release). */\r\ninterface Preview {\r\n id: string;\r\n start: number;\r\n end: number;\r\n resourceId: string | null;\r\n}\r\n\r\nconst VIEWS: SchedulerView[] = ['day', 'week', 'twoWeeks', 'month', 'agenda'];\r\nconst SETTLE_MS = 700;\r\n\r\nconst Chevron = () => (\r\n <svg width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\r\n <path d=\"M3 4.5 L6 7.5 L9 4.5\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" />\r\n </svg>\r\n);\r\n\r\nconst WEEKDAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];\r\n/** Month view header: \"Mon 26\". */\r\n/** Whether a day heading fits its column (semibold text is about 0.56 em per character, plus 20 px of padding). */\r\nexport function fitsDay(text: string, width: number, px: number): boolean {\r\n return text.length * px * 0.56 + 20 <= width;\r\n}\r\n\r\n/**\r\n * The day heading that fits: \"Mon 2 Nov · Today\", then \"Mon 2 Nov\", then the compact form, so a\r\n * bigger text size shortens headings instead of cutting them off. When \"Today\" is dropped from\r\n * view, screen readers still hear it.\r\n */\r\nexport function dayHeadLabel(full: string, compact: string, today: boolean, width: number, px: number): string {\r\n if (today && fitsDay(`${full} · Today`, width, px)) return `${full} · Today`;\r\n if (fitsDay(full, width, px)) return full;\r\n return compact;\r\n}\r\n\r\nfunction compactDay(day: number): string {\r\n return `${WEEKDAYS[(((day + 3) % 7) + 7) % 7]} ${parts(day).date}`;\r\n}\r\n\r\n/** \"26 Oct – 1 Nov 2026\", \"Mon 26 Oct 2026\" for one day, or \"October 2026\" for a month. */\r\nfunction rangeLabel(startDay: number, days: number, view?: SchedulerView): string {\r\n const a = parts(startDay);\r\n const b = parts(startDay + days - 1);\r\n if (view === 'month') return `${MONTHS_LONG[a.month]} ${a.year}`;\r\n if (days === 1) return `${dayLabelIn(dayToInstant(startDay, () => 0), () => 0)} ${a.year}`;\r\n const left = a.year === b.year ? `${a.date} ${MONTHS[a.month]}` : `${a.date} ${MONTHS[a.month]} ${a.year}`;\r\n return `${left} – ${b.date} ${MONTHS[b.month]} ${b.year}`;\r\n}\r\n\r\nexport function Scheduler(props: SchedulerProps) {\r\n const preset = PRESETS[props.preset ?? 'custom'];\r\n const labels = { ...preset.labels, ...props.labels };\r\n const zone: Zone = useMemo(() => props.timeZone ?? browserZone(), [props.timeZone]);\r\n const accent = accentOf(props.theme, props.accent);\r\n const license = useLicense(props.licenseKey, { product: 'plan', tenantId: props.tenantId });\r\n const renewal = props.renewalNotice !== false ? graceNotice(license) : null;\r\n\r\n // Now: pinned by the host, else the clock, ticking each minute.\r\n const [clock, setClock] = useState(() => Date.now());\r\n useEffect(() => {\r\n if (props.now !== undefined) return;\r\n const t = setInterval(() => setClock(Date.now()), 60_000);\r\n return () => clearInterval(t);\r\n }, [props.now]);\r\n const now = props.now ?? clock;\r\n\r\n /* ---------- View, date, options ---------- */\r\n\r\n // Below 600 px the timeline can't show a day legibly, so an uncontrolled scheduler starts in\r\n // Agenda until the user picks a view themselves.\r\n const [rootWidth, setRootWidth] = useState(0);\r\n useLayoutEffect(() => {\r\n const el = rootRef.current;\r\n if (!el) return;\r\n const measure = () => setRootWidth(el.clientWidth);\r\n measure();\r\n if (typeof ResizeObserver === 'undefined') return;\r\n const ro = new ResizeObserver(measure);\r\n ro.observe(el);\r\n return () => ro.disconnect();\r\n }, []);\r\n const narrow = rootWidth > 0 && rootWidth < 600;\r\n const [viewState, setViewState] = useState<SchedulerView>(props.defaultView ?? preset.view);\r\n // A default view (e.g. a maker's choice in Power Apps) still gives way to Agenda on a phone;\r\n // only the user's own pick in this session keeps a timeline view when narrow.\r\n const [viewPicked, setViewPicked] = useState(false);\r\n const view: SchedulerView = props.view ?? (narrow && !viewPicked ? 'agenda' : viewState);\r\n const setView = (v: SchedulerView) => {\r\n if (props.view === undefined) {\r\n setViewState(v);\r\n setViewPicked(true);\r\n }\r\n props.onViewChange?.(v);\r\n };\r\n const [dateState, setDateState] = useState<string>(() => props.defaultDate ?? todayIn(zone, now));\r\n const date = props.date ?? dateState;\r\n const setDate = (d: string) => {\r\n if (props.date === undefined) setDateState(d);\r\n props.onDateChange?.(d);\r\n };\r\n const [hideOff, setHideOff] = useState(props.hideNonWorking ?? true);\r\n const [filter, setFilter] = useState('');\r\n const [collapsed, setCollapsed] = useState<Set<string>>(() => new Set());\r\n const [panelOpenState, setPanelOpenState] = useState(props.showUnassigned ?? preset.showUnassigned);\r\n const [panelTouched, setPanelTouched] = useState(false);\r\n // On a phone the panel would squeeze the board to nothing, and Agenda already lists unassigned\r\n // work under each day, so it starts closed there and covers the board when opened.\r\n const panelOpen = narrow && !panelTouched ? false : panelOpenState;\r\n const setPanelOpen = (open: boolean) => {\r\n setPanelTouched(true);\r\n setPanelOpenState(open);\r\n };\r\n const [selected, setSelectedState] = useState<string | null>(null);\r\n const setSelected = (id: string | null) => {\r\n setSelectedState(id);\r\n props.onSelect?.(id);\r\n };\r\n\r\n /* ---------- Editing (optimistic saves, undo, toasts: @ninthforge/editing) ---------- */\r\n\r\n const conflictMode = props.conflictMode ?? preset.conflictMode;\r\n const snapMinutes = props.snapMinutes ?? preset.snapMinutes;\r\n const can = (action: 'move' | 'resize' | 'create' | 'assign', b?: SchedBooking) =>\r\n canEdit(props.editable, action === 'create' ? !!props.onCreate : !!props.onBookingChange, action, b);\r\n // Host data, made safe (text as text, finite numbers, unique ids) once per change.\r\n const resources = useMemo(() => cleanResources(props.resources), [props.resources]);\r\n const resourceById = useMemo(() => new Map(resources.map((r) => [r.id, r] as const)), [resources]);\r\n // A warning to append to the next change's toast (set just before commit, read by `describe`).\r\n const warnRef = useRef<string | undefined>(undefined);\r\n const spec: EditingSpec<SchedBooking, SchedFields> = {\r\n ...SCHED_FIELDS,\r\n // View-only without a key that unlocks Pro actions (Matt, 2026-11-08). Quiet while checking.\r\n ...lockFor(license, unlocksPro(license), 'Plan', 'scheduler'),\r\n noun: labels.booking.toLowerCase(),\r\n describe: (change, nameOf) => {\r\n const text = describeChange(change as SchedChange, {\r\n name: nameOf,\r\n resource: (id) => (id ? (resourceById.get(id)?.name ?? labels.resource) : 'Unassigned'),\r\n // What the user sees (pending edits included), so a follow-up edit describes the right times.\r\n booking: (id) => shownRef.current.find((b) => b.id === id),\r\n zone\r\n });\r\n return warnRef.current ? `${text}. Note: ${warnRef.current}` : text;\r\n }\r\n };\r\n const editing = useEditing<SchedBooking, SchedFields, SchedChange>(props.bookings, props.onBookingChange, spec);\r\n const shownRef = useRef(editing.shown);\r\n shownRef.current = editing.shown;\r\n const announce = editing.announce;\r\n\r\n const dragRef = useRef<Drag | null>(null);\r\n const [preview, setPreview] = useState<Preview | null>(null);\r\n const [ghost, setGhost] = useState<{ resourceId: string; start: number; end: number } | null>(null);\r\n const [dragging, setDragging] = useState(false);\r\n\r\n const uid = useState(() => nextUid('nfs'))[0];\r\n // Text size (docs/specs/text-size.md): one scale for every role; rows, lanes, the header and\r\n // the resource column grow with it.\r\n const textScale = clampTextScale(props.textScale ?? 1);\r\n const headH = scaleWithText(HEADER_H, textScale);\r\n const laneH = scaleWithText(LANE_H, textScale);\r\n const resW = Math.round(RESOURCE_W * textScale);\r\n const rootRef = useRef<HTMLDivElement>(null);\r\n // Narrow toolbars scroll the view switcher; keep the chosen view in sight.\r\n const segRef = useRef<HTMLDivElement>(null);\r\n useEffect(() => {\r\n const seg = segRef.current;\r\n const on = seg?.querySelector<HTMLElement>('[aria-pressed=\"true\"]');\r\n if (!seg || !on || seg.scrollWidth <= seg.clientWidth) return;\r\n const a = seg.getBoundingClientRect();\r\n const b = on.getBoundingClientRect();\r\n seg.scrollLeft += (b.left - a.left - (a.width - b.width) / 2) / cssScale(seg).x;\r\n }, [view, narrow]);\r\n const scrollRef = useRef<HTMLDivElement>(null);\r\n\r\n // Right-to-left: from the prop, else inherited. The resource column mirrors; time runs left to right.\r\n const [inheritedRtl, setInheritedRtl] = useState(false);\r\n useLayoutEffect(() => {\r\n const parent = rootRef.current?.parentElement;\r\n if (parent) setInheritedRtl(getComputedStyle(parent).direction === 'rtl');\r\n }, []);\r\n const rtl = props.dir ? props.dir === 'rtl' : inheritedRtl;\r\n const dirAttr = rtl ? 'rtl' : undefined;\r\n\r\n /* ---------- Size and scroll ---------- */\r\n\r\n const [size, setSize] = useState({ width: 1000, height: 600 });\r\n const [scroll, setScroll] = useState({ top: 0, left: 0 });\r\n useLayoutEffect(() => {\r\n const el = scrollRef.current;\r\n if (!el) return;\r\n const measure = () => setSize({ width: el.clientWidth || 1000, height: el.clientHeight || 600 });\r\n measure();\r\n if (typeof ResizeObserver === 'undefined') return;\r\n const ro = new ResizeObserver(measure);\r\n ro.observe(el);\r\n return () => ro.disconnect();\r\n }, [panelOpen, view === 'agenda']);\r\n const rafRef = useRef(0);\r\n const onScroll = () => {\r\n if (rafRef.current) return;\r\n rafRef.current = requestAnimationFrame(() => {\r\n rafRef.current = 0;\r\n const el = scrollRef.current;\r\n if (el) setScroll({ top: el.scrollTop, left: el.scrollLeft });\r\n });\r\n };\r\n useEffect(() => () => cancelAnimationFrame(rafRef.current), []);\r\n const [settled, setSettled] = useState(false);\r\n useEffect(() => {\r\n const t = setTimeout(() => setSettled(true), SETTLE_MS);\r\n return () => clearTimeout(t);\r\n }, []);\r\n\r\n /* ---------- Scale ---------- */\r\n\r\n const { startDay, days } = viewRange(date, view);\r\n const hours = boardHours(resources, preset.workStart, preset.workEnd);\r\n // \"Working hours only\" (default on): Day shows the working window; Week and Two weeks also\r\n // drop non-working days, so a shift is wide enough to read. Off shows the full clock.\r\n const dayHidden = hideOff;\r\n const workWeek = props.workingWeek ?? 'mon-fri';\r\n const shownDays = view === 'day' || !hideOff ? days : workingDaysBetween(startDay, startDay + days - 1, workWeek) || days;\r\n const hoursPerDay = hideOff ? Math.max(1, hours.end - hours.start) : 24;\r\n const avail = Math.max(320, size.width - resW - 2);\r\n // Month keeps each day about 50 px wide (\"Mon 26\" fits); the board scrolls sideways.\r\n const minPxPerHour = view === 'day' ? 56 : view === 'week' ? (hideOff ? 16 : 6) : view === 'month' ? (hideOff ? 5.5 : 2.1) : hideOff ? 9 : 4;\r\n const pxPerHour = Math.max(minPxPerHour, avail / (shownDays * hoursPerDay));\r\n const scale = useMemo(\r\n () =>\r\n createInstantScale({\r\n from: dayToInstant(startDay, zone),\r\n to: dayToInstant(startDay + days, zone) - 1,\r\n zone,\r\n pxPerHour,\r\n workStart: hours.start,\r\n workEnd: hours.end,\r\n // Day view hides hours only; a Saturday is still shown when it's the chosen day.\r\n workingWeek: view === 'day' ? 'all' : workWeek,\r\n hideNonWorking: dayHidden\r\n }),\r\n [startDay, days, zone, pxPerHour, hours.start, hours.end, view, workWeek, dayHidden]\r\n );\r\n\r\n // Tell the host which days are on screen (whole days, not just working hours), so it can load that window.\r\n const rangeStart = dayToInstant(startDay, zone);\r\n const rangeEnd = dayToInstant(startDay + days, zone);\r\n const onRangeRef = useRef(props.onRangeChange);\r\n onRangeRef.current = props.onRangeChange;\r\n useEffect(() => {\r\n onRangeRef.current?.({ start: rangeStart, end: rangeEnd });\r\n }, [rangeStart, rangeEnd]);\r\n\r\n /* ---------- Data ---------- */\r\n\r\n // What the user sees: host data, pending edits on top, and a drag in progress on top of that.\r\n const baseItems = useMemo(() => normalise(editing.shown), [editing.shown]);\r\n const items = useMemo(() => {\r\n if (!preview) return baseItems;\r\n return baseItems.map((i) => (i.id === preview.id ? { ...i, start: preview.start, end: preview.end, resourceId: preview.resourceId } : i));\r\n }, [baseItems, preview]);\r\n const hasGroups = resources.some((r) => !!r.group);\r\n const [groupState, setGroupState] = useState<boolean | undefined>(undefined);\r\n const groupBy = props.groupBy ?? groupState ?? hasGroups;\r\n const setGroupBy = (on: boolean) => {\r\n if (props.groupBy === undefined) setGroupState(on);\r\n props.onGroupByChange?.(on);\r\n announce(on ? `Grouped by ${labels.group.toLowerCase()}` : 'Not grouped');\r\n };\r\n const rows = useMemo(\r\n () => buildRows(resources, items, { from: scale.start, to: scale.end, groupBy, collapsed, filter, textScale }),\r\n [resources, items, scale.start, scale.end, groupBy, collapsed, filter]\r\n );\r\n const bodyHeight = totalHeight(rows);\r\n const shownRows = visibleRows(rows, Math.max(0, scroll.top - 56), size.height);\r\n // The queue is all unassigned work, soonest first, not just this range: it is what still needs scheduling.\r\n const queue = useMemo(() => unassigned(baseItems, -Infinity, Infinity), [baseItems]);\r\n const [queueFilter, setQueueFilter] = useState('');\r\n const [queueSort, setQueueSort] = useState<QueueSort>('soonest');\r\n const queueShown = useMemo(() => queueView(baseItems, queueFilter, queueSort), [baseItems, queueFilter, queueSort]);\r\n const byDay = queueSort === 'soonest' || queueSort === 'latest';\r\n const byId = useMemo(() => new Map(items.map((i) => [i.id, i] as const)), [items]);\r\n const resourceName = useMemo(() => new Map(resources.map((r) => [r.id, r.name] as const)), [resources]);\r\n const colours = useMemo(() => {\r\n const seen = new Map<string, string>();\r\n return new Map(items.map((i) => [i.id, bookingAppearance(i.booking, accent, props.statusColors, seen)] as const));\r\n }, [items, accent, props.statusColors]);\r\n\r\n // Booked hours vs capacity in the visible range, for the row header (\"32 / 40 h\").\r\n const workdays = workingDaysBetween(startDay, startDay + days - 1, workWeek) || days;\r\n const workWindow = (row: ResourceRow): { ws: number; we: number; perDay: number } => {\r\n const ws = row.resource.workStart === undefined ? hours.start : parseHour(row.resource.workStart);\r\n const we = row.resource.workEnd === undefined ? hours.end : parseHour(row.resource.workEnd);\r\n return {\r\n ws: Number.isNaN(ws) ? hours.start : ws,\r\n we: Number.isNaN(we) ? hours.end : we,\r\n perDay: row.resource.capacityHours ?? preset.capacityHours\r\n };\r\n };\r\n const capacityText = (row: ResourceRow): { text: string; level: 'ok' | 'over' } | null => {\r\n // Hours for every scenario; rooms show their seats in the subtitle.\r\n const w = workWindow(row);\r\n const cap = w.perDay * workdays;\r\n const booked = bookedWorkingHours(row.items, startDay, days, w.ws, w.we, workWeek, zone, w.perDay);\r\n return { text: `${booked} / ${cap} h`, level: booked > cap ? 'over' : 'ok' };\r\n };\r\n // Multi-day views: a utilisation strip per working day under each row, hatching on over days.\r\n const showUtil = view !== 'day' && view !== 'agenda';\r\n const dayTicks = useMemo(() => new Map(scale.days.map((d) => [d.day, d] as const)), [scale.days]);\r\n const utilisation = (row: ResourceRow): DayUtil[] => {\r\n if (!showUtil) return [];\r\n const w = workWindow(row);\r\n return dailyUtilisation(row.items, scale.days.map((d) => d.day), w.ws, w.we, w.perDay, workWeek, zone);\r\n };\r\n const utilCache = new Map<string, DayUtil[]>();\r\n const utilOf = (row: ResourceRow): DayUtil[] => {\r\n let u = utilCache.get(row.key);\r\n if (!u) utilCache.set(row.key, (u = utilisation(row)));\r\n return u;\r\n };\r\n\r\n /* ---------- Scroll to the interesting time ---------- */\r\n\r\n const todayDay = instantToDay(now, zone);\r\n useEffect(() => {\r\n const el = scrollRef.current;\r\n if (!el) return;\r\n let left = 0;\r\n if (view !== 'day' && todayDay >= startDay && todayDay < startDay + days) {\r\n // Only scroll when today would be off-screen from the start of the range.\r\n const tx = scale.x(dayToInstant(todayDay, zone));\r\n const tEnd = scale.x(dayToInstant(todayDay + 1, zone));\r\n if (tEnd > tx && tEnd > el.clientWidth - resW) left = tx - 24;\r\n } else if (view === 'day' && !dayHidden) left = scale.x(atHour(startDay, hours.start, zone)) - 24;\r\n el.scrollLeft = Math.max(0, left);\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [startDay, view, dayHidden]);\r\n\r\n /* ---------- Navigation ---------- */\r\n\r\n const go = (d: number) => {\r\n setDate(isoOfDay(d));\r\n const r = viewRange(isoOfDay(d), view);\r\n announce(`${VIEW_LABEL[view]}: ${rangeLabel(r.startDay, r.days, view)}`);\r\n };\r\n const step = (dir: 1 | -1) => go(stepDate(date, view, dir));\r\n const changeView = (v: SchedulerView) => {\r\n setView(v);\r\n const r = viewRange(date, v);\r\n announce(`${VIEW_LABEL[v]}: ${rangeLabel(r.startDay, r.days, v)}`);\r\n };\r\n\r\n const bookingWords = (it: Item): string => {\r\n const b = it.booking;\r\n const who = it.resourceId ? (resourceName.get(it.resourceId) ?? labels.resource) : 'Unassigned';\r\n const sameDay = instantToDay(it.start, zone) === instantToDay(it.end - 1, zone);\r\n const when = b.allDay\r\n ? `${dayLabelIn(it.start, zone)}${sameDay ? '' : ` to ${dayLabelIn(it.end - 1, zone)}`}, all day`\r\n : sameDay\r\n ? `${dayLabelIn(it.start, zone)}, ${timeLabel(it.start, zone)} to ${timeLabel(it.end, zone)}`\r\n : `${dayLabelIn(it.start, zone)} ${timeLabel(it.start, zone)} to ${dayLabelIn(it.end, zone)} ${timeLabel(it.end, zone)}`;\r\n const extras = [\r\n b.status,\r\n it.kind === 'unavailable' ? 'unavailable' : '',\r\n b.location,\r\n b.attendees ? `${b.attendees} people` : ''\r\n ].filter(Boolean);\r\n return [b.name, who, when, ...extras].join(', ');\r\n };\r\n\r\n const select = (id: string | null, scrollIntoView = false) => {\r\n setSelected(id);\r\n if (!id || !scrollIntoView) return;\r\n const it = byId.get(id);\r\n const el = scrollRef.current;\r\n if (!it || !el) return;\r\n const row = rows.find((r) => r.kind === 'resource' && r.items.some((x) => x.id === id));\r\n const x = scale.x(Math.max(it.start, scale.start));\r\n if (x < el.scrollLeft || x > el.scrollLeft + el.clientWidth - resW - 40) el.scrollLeft = Math.max(0, x - 40);\r\n if (row) {\r\n const top = row.top;\r\n if (top < el.scrollTop || top + row.height > el.scrollTop + el.clientHeight - 56) el.scrollTop = Math.max(0, top - 40);\r\n }\r\n };\r\n\r\n /* ---------- Committing edits ---------- */\r\n\r\n const baseById = useMemo(() => new Map(baseItems.map((i) => [i.id, i] as const)), [baseItems]);\r\n\r\n /**\r\n * Check the conflict mode, then save optimistically. Block refuses with a toast; warn saves\r\n * and says what it overlaps; over-seat rooms always get a note. Returns whether it saved.\r\n */\r\n const tryCommit = (change: SchedChange | null): boolean => {\r\n if (!change) return false;\r\n const base = baseById.get(change.id);\r\n if (!base) return false;\r\n const span = { start: toMs(change.start ?? base.start), end: toMs(change.end ?? base.end) };\r\n const rid = 'resourceId' in change ? (change.resourceId ?? null) : base.resourceId;\r\n const others = base.cancelled || base.kind === 'unavailable' ? [] : overlapping(baseItems, base.id, rid, span);\r\n const verdict = checkPlacement(conflictMode, base.booking, others, rid ? resourceById.get(rid) : undefined);\r\n if (!verdict.ok) {\r\n const text = `Not moved: ${verdict.reason}`;\r\n editing.toast({ text, tone: 'error' });\r\n announce(text);\r\n return false;\r\n }\r\n warnRef.current = verdict.warning;\r\n editing.commit(change);\r\n warnRef.current = undefined;\r\n return true;\r\n };\r\n\r\n /* ---------- Pointer gestures: move, resize, create, assign ---------- */\r\n\r\n const pointAt = (clientX: number, clientY: number) => {\r\n const el = scrollRef.current!;\r\n // In the scroller's own px, whatever scaling surrounds the component (Power BI, canvas fit).\r\n const p = localPoint(el, clientX, clientY);\r\n return { x: p.x + el.scrollLeft - resW, y: p.y + el.scrollTop - headH, inside: p.x >= resW && p.x <= el.offsetWidth && p.y >= headH && p.y <= el.offsetHeight };\r\n };\r\n const rowAtY = (y: number): ResourceRow | undefined =>\r\n rows.find((r): r is ResourceRow => r.kind === 'resource' && y >= r.top && y < r.top + r.height);\r\n\r\n const startDrag = (e: React.PointerEvent, d: Omit<Drag, 'pointerId' | 'x0' | 'y0' | 't0' | 'started'>) => {\r\n if (e.button !== 0) return;\r\n const p = pointAt(e.clientX, e.clientY);\r\n dragRef.current = { ...d, pointerId: e.pointerId, x0: e.clientX, y0: e.clientY, t0: scale.msAt(p.x), started: false };\r\n };\r\n\r\n const onBookingPointerDown = (e: React.PointerEvent<HTMLDivElement>, it: Item) => {\r\n if (e.button !== 0) return;\r\n e.stopPropagation();\r\n select(it.id);\r\n const edge = (e.target as HTMLElement).getAttribute('data-edge');\r\n const mode = edge === 'start' || edge === 'end' ? edge : 'move';\r\n if (mode === 'move' ? !can('move', it.booking) && !can('assign', it.booking) : !can('resize', it.booking)) return;\r\n const base = baseById.get(it.id) ?? it;\r\n startDrag(e, { kind: mode, id: it.id, span: { start: base.start, end: base.end }, resourceId: base.resourceId });\r\n };\r\n\r\n const onLanePointerDown = (e: React.PointerEvent<HTMLDivElement>, row: ResourceRow) => {\r\n if (e.button !== 0 || e.target !== e.currentTarget || !can('create')) return;\r\n startDrag(e, { kind: 'create', resourceId: row.resource.id });\r\n };\r\n\r\n const onQueuePointerDown = (e: React.PointerEvent<HTMLButtonElement>, it: Item) => {\r\n select(it.id);\r\n if (!can('assign', it.booking)) return;\r\n startDrag(e, { kind: 'assign', id: it.id, span: { start: it.start, end: it.end }, resourceId: null });\r\n };\r\n\r\n const endDrag = () => {\r\n dragRef.current = null;\r\n setPreview(null);\r\n setGhost(null);\r\n setDragging(false);\r\n };\r\n\r\n // Window-level listeners read the latest render through this ref.\r\n const gesture = useRef({ move: (_e: PointerEvent) => {}, up: (_e: PointerEvent) => {} });\r\n gesture.current.move = (e: PointerEvent) => {\r\n const d = dragRef.current;\r\n if (!d || e.pointerId !== d.pointerId) return;\r\n if (!d.started) {\r\n if (Math.hypot(e.clientX - d.x0, e.clientY - d.y0) < DRAG_THRESHOLD) return;\r\n d.started = true;\r\n setDragging(true);\r\n }\r\n const p = pointAt(e.clientX, e.clientY);\r\n const t = scale.msAt(p.x);\r\n if (d.kind === 'create') {\r\n setGhost({ resourceId: d.resourceId!, ...createSpan(d.t0, t, snapMinutes, zone) });\r\n return;\r\n }\r\n const booking = baseById.get(d.id!)!.booking;\r\n if (d.kind === 'assign') {\r\n const row = p.inside ? rowAtY(p.y) : undefined;\r\n if (!row) return setPreview(null);\r\n const span = dragSpan('move', { start: t, end: t + (d.span!.end - d.span!.start) }, 0, snapMinutes, zone);\r\n setPreview({ id: d.id!, ...span, resourceId: row.resource.id });\r\n return;\r\n }\r\n const span = dragSpan(d.kind, d.span!, t - d.t0, snapMinutes, zone);\r\n // Moving onto another row reassigns (when assigning is allowed); resizing never does.\r\n const row = d.kind === 'move' && can('assign', booking) ? rowAtY(p.y) : undefined;\r\n const timeOk = d.kind !== 'move' || can('move', booking);\r\n setPreview({\r\n id: d.id!,\r\n start: timeOk ? span.start : d.span!.start,\r\n end: timeOk ? span.end : d.span!.end,\r\n resourceId: row ? row.resource.id : d.resourceId\r\n });\r\n };\r\n gesture.current.up = (e: PointerEvent) => {\r\n const d = dragRef.current;\r\n if (!d || e.pointerId !== d.pointerId) return;\r\n if (!d.started) return endDrag(); // a click: selection already happened on pointer down\r\n if (d.kind === 'create') {\r\n if (ghost && editing.locked) editing.refuse();\r\n else if (ghost) {\r\n props.onCreate?.({ resourceId: ghost.resourceId, start: new Date(ghost.start).toISOString(), end: new Date(ghost.end).toISOString() });\r\n announce(`New ${labels.booking.toLowerCase()} for ${resourceById.get(ghost.resourceId)?.name ?? labels.resource}, ${whenLabel(ghost.start, ghost.end, zone)}`);\r\n }\r\n return endDrag();\r\n }\r\n const base = baseById.get(d.id!);\r\n if (base && preview) {\r\n tryCommit(placeChange(base.booking, { start: preview.start, end: preview.end }, preview.resourceId, d.kind === 'assign' ? 'assign' : 'drag'));\r\n select(d.id!);\r\n }\r\n endDrag();\r\n };\r\n useEffect(() => {\r\n const move = (e: PointerEvent) => gesture.current.move(e);\r\n const up = (e: PointerEvent) => gesture.current.up(e);\r\n const cancel = (e: PointerEvent) => {\r\n if (dragRef.current && e.pointerId === dragRef.current.pointerId) endDrag();\r\n };\r\n window.addEventListener('pointermove', move);\r\n window.addEventListener('pointerup', up);\r\n window.addEventListener('pointercancel', cancel);\r\n return () => {\r\n window.removeEventListener('pointermove', move);\r\n window.removeEventListener('pointerup', up);\r\n window.removeEventListener('pointercancel', cancel);\r\n };\r\n }, []);\r\n\r\n /* ---------- Keyboard edits ---------- */\r\n\r\n /** Alt+arrows on a booking: move in time (a snap step in Day view, a day in week views), resize, or change resource. */\r\n const keyboardEdit = (e: { key: string; altKey: boolean; shiftKey: boolean; ctrlKey: boolean; metaKey: boolean }, id: string | null): boolean => {\r\n const ke = keyEdit(e);\r\n if (!ke || !id) return false;\r\n const base = baseById.get(id);\r\n if (!base) return false;\r\n const b = base.booking;\r\n const resourceRows = rows.filter((r): r is ResourceRow => r.kind === 'resource');\r\n if (ke.kind === 'resource') {\r\n if (!can('assign', b)) return true;\r\n const at = resourceRows.findIndex((r) => r.resource.id === base.resourceId);\r\n const target = at < 0 ? (ke.dir === 1 ? resourceRows[0] : undefined) : resourceRows[at + ke.dir];\r\n if (!target) {\r\n announce(ke.dir === 1 ? `No ${labels.resource.toLowerCase()} below` : `No ${labels.resource.toLowerCase()} above`);\r\n return true;\r\n }\r\n tryCommit(placeChange(b, { start: base.start, end: base.end }, target.resource.id, base.resourceId ? 'keyboard' : 'assign'));\r\n return true;\r\n }\r\n if (ke.mode === 'move' ? !can('move', b) : !can('resize', b)) return true;\r\n const stepMs = view === 'day' ? snapMinutes * 60_000 : 24 * 3_600_000;\r\n const minMs = snapMinutes * 60_000;\r\n let span: Span;\r\n if (ke.mode === 'move') {\r\n span =\r\n view === 'day'\r\n ? dragSpan('move', { start: base.start, end: base.end }, ke.dir * stepMs, snapMinutes, zone)\r\n : { start: addDaysIn(base.start, ke.dir, zone), end: addDaysIn(base.end, ke.dir, zone) };\r\n } else {\r\n const endStep = view === 'day' ? stepMs : 3_600_000;\r\n span = { start: base.start, end: Math.max(base.start + minMs, base.end + ke.dir * endStep) };\r\n }\r\n tryCommit(placeChange(b, span, base.resourceId, 'keyboard'));\r\n return true;\r\n };\r\n\r\n /** Insert: a new booking right after the selected one, an hour long, on the same resource. */\r\n const createAfterSelected = () => {\r\n const it = selected ? baseById.get(selected) : undefined;\r\n if (!it?.resourceId) return announce(`Select a ${labels.booking.toLowerCase()} on a ${labels.resource.toLowerCase()} first`);\r\n const start = it.end;\r\n const end = start + 60 * 60_000;\r\n if (editing.locked) return editing.refuse();\r\n props.onCreate?.({ resourceId: it.resourceId, start: new Date(start).toISOString(), end: new Date(end).toISOString() });\r\n announce(`New ${labels.booking.toLowerCase()} for ${resourceById.get(it.resourceId)?.name ?? labels.resource}, ${whenLabel(start, end, zone)}`);\r\n };\r\n\r\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\r\n if ((e.ctrlKey || e.metaKey) && !e.altKey) {\r\n const k = e.key.toLowerCase();\r\n if (k === 'z' || k === 'y') {\r\n if (k === 'y' || e.shiftKey) editing.redo();\r\n else editing.undo();\r\n e.preventDefault();\r\n }\r\n return;\r\n }\r\n if (e.key === 'Escape' && dragRef.current) {\r\n endDrag();\r\n announce('Drag cancelled');\r\n e.preventDefault();\r\n return;\r\n }\r\n if (keyboardEdit(e, selected)) {\r\n e.preventDefault();\r\n return;\r\n }\r\n if (e.key === 'Insert' && can('create')) {\r\n createAfterSelected();\r\n e.preventDefault();\r\n return;\r\n }\r\n if (e.altKey || e.ctrlKey || e.metaKey) return;\r\n switch (e.key) {\r\n case 'ArrowLeft':\r\n case 'ArrowRight':\r\n case 'ArrowUp':\r\n case 'ArrowDown': {\r\n // In RTL, ← and → still follow time (which runs left to right).\r\n select(neighbour(rows, selected, e.key), true);\r\n break;\r\n }\r\n case 'Home': {\r\n const first = rows.find((r): r is ResourceRow => r.kind === 'resource' && r.items.length > 0);\r\n select(first?.items[0]?.id ?? null, true);\r\n break;\r\n }\r\n case 'Enter': {\r\n const it = selected ? byId.get(selected) : undefined;\r\n if (it) props.onOpen?.(it.booking);\r\n break;\r\n }\r\n case 'Escape':\r\n if (!selected) return;\r\n select(null);\r\n break;\r\n default:\r\n return;\r\n }\r\n e.preventDefault();\r\n };\r\n\r\n const toggleGroup = (g: string) =>\r\n setCollapsed((prev) => {\r\n const next = new Set(prev);\r\n if (next.has(g)) next.delete(g);\r\n else next.add(g);\r\n return next;\r\n });\r\n\r\n /* ---------- Render ---------- */\r\n\r\n const style = {\r\n ...resolveTheme(props.theme, props.accent),\r\n ...textVars(textScale, props.textSizes),\r\n '--nf-head': `${headH}px`,\r\n '--nf-res': `${resW}px`,\r\n '--nf-lane': `${laneH}px`,\r\n '--nf-group': `${scaleWithText(GROUP_H, textScale)}px`,\r\n // The Unassigned panel grows with the text, so its filter box and cards don't clip.\r\n '--nf-unassigned-w': `${Math.round(280 * textScale)}px`,\r\n height: props.height ?? '100%'\r\n } as CSSProperties;\r\n const nowX = now >= scale.start && now < scale.end ? scale.x(now) : null;\r\n const left0 = scroll.left - 200;\r\n const right0 = scroll.left + size.width + 200;\r\n const showHours = view === 'day';\r\n // Hour labels thin out with the text size and the board width, so they never run together.\r\n const captionPx = textSize('caption', textScale, props.textSizes);\r\n const hourEvery = hourLabelStep(pxPerHour, captionPx);\r\n const selectedRowKey = selected ? rows.find((r) => r.kind === 'resource' && r.items.some((i) => i.id === selected))?.key : undefined;\r\n const title = props.title ?? labels.bookings;\r\n const showToolbar = props.showToolbar !== false;\r\n\r\n const renderBooking = (it: Item, row: ResourceRow) => {\r\n const x1 = scale.x(Math.max(it.start, scale.start));\r\n const x2 = scale.x(Math.min(it.end, scale.end));\r\n if (x2 < left0 - resW || x1 > right0) return null; // off-screen horizontally\r\n const width = Math.max(6, x2 - x1 - 2);\r\n const look = colours.get(it.id)!;\r\n const conflict = row.conflicts.has(it.id);\r\n // Rooms: a meeting with more people than seats is flagged on the board, not only when it's\r\n // edited (QA-2026-10-31-10).\r\n const seats = row.resource.seats;\r\n const people = it.booking.attendees;\r\n const overSeat = !!seats && !!people && people > seats;\r\n const seatWords = overSeat ? `, ${people} people for ${seats} seats` : '';\r\n const isSel = selected === it.id;\r\n const showTime = view === 'day' || width > 140;\r\n const top = ROW_PAD + (row.lane.get(it.id) ?? 0) * laneH;\r\n const movable = can('move', it.booking) || can('assign', it.booking);\r\n const resizable = can('resize', it.booking) && width > 24;\r\n const isPreview = preview?.id === it.id;\r\n return (\r\n <React.Fragment key={it.id}>\r\n <div\r\n id={`${uid}-b-${it.id}`}\r\n role=\"gridcell\"\r\n className=\"nf-s-bk\"\r\n aria-selected={isSel}\r\n data-style={look.style}\r\n data-conflict={conflict ? '' : undefined}\r\n data-overseat={overSeat ? '' : undefined}\r\n data-editable={movable ? '' : undefined}\r\n data-preview={isPreview ? '' : undefined}\r\n style={{ left: x1 + 1, width, top, '--bk': look.color, '--bk-text': look.text } as CSSProperties}\r\n onPointerDown={(e) => onBookingPointerDown(e, it)}\r\n onClick={() => scrollRef.current?.focus({ preventScroll: true })}\r\n onDoubleClick={() => props.onOpen?.(it.booking)}\r\n title={isPreview ? undefined : bookingWords(it) + seatWords}\r\n >\r\n {/* Screen readers get the full sentence; the visible bar is a compact summary of it. */}\r\n <span className=\"nf-s-sr\">{bookingWords(it) + seatWords + (conflict ? ', overlaps another booking' : '')}</span>\r\n {overSeat && width > 40 ? (\r\n <span className=\"nf-s-seats\" aria-hidden=\"true\">\r\n {people}/{seats}\r\n </span>\r\n ) : null}\r\n <b aria-hidden=\"true\">{it.booking.name}</b>\r\n {width > 90 ? (\r\n <span aria-hidden=\"true\">\r\n {showTime && !it.booking.allDay ? barTimeLabel(it, zone) : ''}\r\n {showTime && !it.booking.allDay && it.booking.subtitle ? ' · ' : ''}\r\n {it.booking.subtitle ?? ''}\r\n </span>\r\n ) : null}\r\n {resizable ? (\r\n <>\r\n <span className=\"nf-s-h nf-s-h-l\" data-edge=\"start\" aria-hidden=\"true\" />\r\n <span className=\"nf-s-h nf-s-h-r\" data-edge=\"end\" aria-hidden=\"true\" />\r\n </>\r\n ) : null}\r\n </div>\r\n {isPreview && dragging ? (\r\n <div className=\"nf-s-draglabel\" aria-hidden=\"true\" style={{ left: x1, top: Math.max(0, top - 24) }}>\r\n {whenLabel(it.start, it.end, zone)}\r\n {it.resourceId && it.resourceId !== baseById.get(it.id)?.resourceId ? ` · ${resourceById.get(it.resourceId)?.name ?? ''}` : ''}\r\n </div>\r\n ) : null}\r\n </React.Fragment>\r\n );\r\n };\r\n\r\n /* ---------- Print and PNG ---------- */\r\n\r\n const [exportOpen, setExportOpen] = useState(false);\r\n const exportRef = useRef<HTMLDivElement>(null);\r\n useEffect(() => {\r\n if (!exportOpen) return;\r\n exportRef.current?.querySelector<HTMLButtonElement>('[role=\"menuitem\"]')?.focus();\r\n const away = (e: PointerEvent) => {\r\n if (!exportRef.current?.contains(e.target as Node)) setExportOpen(false);\r\n };\r\n document.addEventListener('pointerdown', away, true);\r\n return () => document.removeEventListener('pointerdown', away, true);\r\n }, [exportOpen]);\r\n const exportMenuKeys = (e: KeyboardEvent<HTMLDivElement>) => {\r\n const items = Array.from(exportRef.current?.querySelectorAll<HTMLButtonElement>('[role=\"menuitem\"]') ?? []);\r\n const i = items.indexOf(document.activeElement as HTMLButtonElement);\r\n if (e.key === 'ArrowDown') items[(i + 1) % items.length]?.focus();\r\n else if (e.key === 'ArrowUp') items[(i - 1 + items.length) % items.length]?.focus();\r\n else if (e.key === 'Escape') {\r\n setExportOpen(false);\r\n exportRef.current?.querySelector<HTMLButtonElement>('[aria-haspopup]')?.focus();\r\n } else return;\r\n e.preventDefault();\r\n };\r\n const ownerDoc = () => rootRef.current?.ownerDocument ?? document;\r\n const printedOn = `${dayLabelIn(now, zone)} ${parts(instantToDay(now, zone)).year}`;\r\n const boardExport = (forPrint: boolean): BoardExport => ({\r\n title,\r\n range: rangeLabel(startDay, days, view),\r\n printedOn,\r\n // Print fits the range to the page; PNG keeps the screen's scale.\r\n scale: forPrint\r\n ? createInstantScale({\r\n from: dayToInstant(startDay, zone),\r\n to: dayToInstant(startDay + days, zone) - 1,\r\n zone,\r\n pxPerHour: PRINT_TIMELINE_W / (shownDays * hoursPerDay),\r\n workStart: hours.start,\r\n workEnd: hours.end,\r\n workingWeek: view === 'day' ? 'all' : workWeek,\r\n hideNonWorking: dayHidden\r\n })\r\n : scale,\r\n rows,\r\n zone,\r\n labels,\r\n theme: palette(props.theme, props.accent, forPrint),\r\n look: colours,\r\n capacity: (row) => capacityText(row)?.text ?? null,\r\n queue: unassigned(items, scale.start, scale.end),\r\n now,\r\n showHours: view === 'day'\r\n });\r\n // Copy diagnostics: starts inside the click (browsers need the gesture), then closes the menu.\r\n const copySupport = () => {\r\n const host = (props.diagnostics || undefined)?.() ?? {};\r\n void copyDiagnostics({\r\n product: 'Plan',\r\n control: 'Scheduler',\r\n version: VERSION,\r\n licence: license,\r\n rows: props.bookings.length,\r\n ...host, extra: { Resources: props.resources.length, ...host.extra }\r\n }).then((r) => editing.toast({ text: r.message, tone: r.ok ? 'info' : 'error' }));\r\n setExportOpen(false);\r\n };\r\n const printBoard = () => {\r\n setExportOpen(false);\r\n const html =\r\n view === 'agenda'\r\n ? agendaPrintHtml({\r\n title,\r\n range: rangeLabel(startDay, days, view),\r\n printedOn,\r\n resources: resources,\r\n items,\r\n startDay,\r\n days,\r\n zone,\r\n filter,\r\n labels,\r\n theme: palette(props.theme, props.accent, true)\r\n })\r\n : boardPrintHtml(boardExport(true));\r\n printDocument(html, ownerDoc());\r\n announce('Print dialog opened');\r\n };\r\n const downloadPng = () => {\r\n setExportOpen(false);\r\n const ok = exportBoardPng(boardExport(false), exportFileName(title, todayIn(zone, now), 'png', 'schedule'), ownerDoc());\r\n if (ok) announce('Image downloaded');\r\n else editing.toast({ text: 'Too big for one image. Filter or collapse groups first, or use Print.', tone: 'info' });\r\n };\r\n\r\n return (\r\n <div\r\n ref={rootRef}\r\n dir=\"ltr\"\r\n data-rtl={rtl ? '' : undefined}\r\n data-narrow={narrow ? '' : undefined}\r\n className={`nf-sched${settled ? ' nf-s-settled' : ''}${dragging ? ' nf-s-dragging' : ''}${props.className ? ' ' + props.className : ''}`}\r\n style={style}\r\n >\r\n {/* Raw CSS: a text child gets HTML-escaped when server-rendered (quotes become &quot;). */}\r\n <style dangerouslySetInnerHTML={{ __html: css }} />\r\n\r\n {showToolbar ? (\r\n <div className=\"nf-s-toolbar\" dir={dirAttr}>\r\n <div className=\"nf-s-title\">{title}</div>\r\n <div className=\"nf-s-seg\" role=\"group\" aria-label=\"View\" ref={segRef}>\r\n {VIEWS.map((v) => (\r\n <button key={v} type=\"button\" className=\"nf-s-btn\" aria-pressed={v === view} onClick={() => changeView(v)}>\r\n {VIEW_LABEL[v]}\r\n </button>\r\n ))}\r\n </div>\r\n <button type=\"button\" className=\"nf-s-btn\" onClick={() => go(todayDay)}>\r\n Today\r\n </button>\r\n <button type=\"button\" className=\"nf-s-btn nf-s-icon\" aria-label={`Previous ${VIEW_STEP[view]}`} onClick={() => step(-1)}>\r\n {rtl ? '›' : '‹'}\r\n </button>\r\n <button type=\"button\" className=\"nf-s-btn nf-s-icon\" aria-label={`Next ${VIEW_STEP[view]}`} onClick={() => step(1)}>\r\n {rtl ? '‹' : '›'}\r\n </button>\r\n <div className=\"nf-s-range\" aria-hidden=\"true\">\r\n {rangeLabel(startDay, days, view)}\r\n </div>\r\n <input\r\n className=\"nf-s-filter\"\r\n type=\"date\"\r\n aria-label=\"Go to date\"\r\n value={date}\r\n onChange={(e) => {\r\n if (/^\\d{4}-\\d{2}-\\d{2}$/.test(e.target.value)) go(toDay(e.target.value));\r\n }}\r\n style={{ width: Math.round(150 * textScale) }}\r\n />\r\n {view !== 'agenda' ? (\r\n <button type=\"button\" className=\"nf-s-switch\" aria-pressed={hideOff} onClick={() => setHideOff(!hideOff)}>\r\n <span className=\"nf-s-switch-track\" aria-hidden=\"true\" />\r\n Working hours only\r\n </button>\r\n ) : null}\r\n <div className=\"nf-s-spacer\" />\r\n <input\r\n className=\"nf-s-filter\"\r\n type=\"search\"\r\n placeholder={`Filter ${labels.resources.toLowerCase()}`}\r\n aria-label={`Filter ${labels.resources.toLowerCase()} by name, group or subtitle`}\r\n value={filter}\r\n onChange={(e) => setFilter(e.target.value)}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Escape' && filter) {\r\n setFilter('');\r\n e.preventDefault();\r\n }\r\n }}\r\n />\r\n {hasGroups && view !== 'agenda' ? (\r\n <label className=\"nf-s-pick\">\r\n Group by\r\n <select value={groupBy ? 'group' : 'none'} onChange={(e) => setGroupBy(e.target.value === 'group')}>\r\n <option value=\"group\">{labels.group}</option>\r\n <option value=\"none\">None</option>\r\n </select>\r\n </label>\r\n ) : null}\r\n <button type=\"button\" className=\"nf-s-btn\" aria-pressed={panelOpen} aria-controls={panelOpen ? `${uid}-unassigned` : undefined} onClick={() => setPanelOpen(!panelOpen)}>\r\n Unassigned<span className=\"nf-s-count\">{queue.length}</span>\r\n </button>\r\n {props.exports !== false ? (\r\n <div className=\"nf-s-menuwrap\" ref={exportRef} onKeyDown={exportMenuKeys}>\r\n <button type=\"button\" className=\"nf-s-btn\" aria-haspopup=\"menu\" aria-expanded={exportOpen} onClick={() => setExportOpen(!exportOpen)}>\r\n Export\r\n </button>\r\n {exportOpen ? (\r\n <div className=\"nf-s-menu\" role=\"menu\" aria-label=\"Export\">\r\n <button type=\"button\" role=\"menuitem\" tabIndex={-1} onClick={printBoard}>\r\n Print…\r\n </button>\r\n {view !== 'agenda' ? (\r\n <button type=\"button\" role=\"menuitem\" tabIndex={-1} onClick={downloadPng}>\r\n Download PNG\r\n </button>\r\n ) : null}\r\n {props.diagnostics !== false ? (\r\n <button type=\"button\" role=\"menuitem\" tabIndex={-1} onClick={copySupport}>\r\n Copy diagnostics\r\n </button>\r\n ) : null}\r\n </div>\r\n ) : null}\r\n </div>\r\n ) : null}\r\n </div>\r\n ) : null}\r\n\r\n <div className=\"nf-s-main\">\r\n {view === 'agenda' ? (\r\n <Agenda\r\n uid={uid}\r\n label={`${title}, ${rangeLabel(startDay, days, view)}`}\r\n resources={resources}\r\n items={items}\r\n startDay={startDay}\r\n days={days}\r\n zone={zone}\r\n todayDay={todayDay}\r\n filter={filter}\r\n labels={labels}\r\n selected={selected}\r\n appearance={colours}\r\n describe={bookingWords}\r\n capacityOf={(row, day) => {\r\n const w = workWindow(row);\r\n return dailyUtilisation(row.items, [day], w.ws, w.we, w.perDay, workWeek, zone)[0];\r\n }}\r\n onSelect={(id) => select(id)}\r\n onOpen={props.onOpen}\r\n dir={dirAttr}\r\n />\r\n ) : (\r\n <div\r\n ref={scrollRef}\r\n className=\"nf-s-scroll\"\r\n role=\"grid\"\r\n aria-busy={props.loading ? true : undefined}\r\n aria-label={`${title}, ${rangeLabel(startDay, days, view)}`}\r\n aria-rowcount={rows.filter((r) => r.kind === 'resource').length + 1}\r\n aria-activedescendant={selected && byId.get(selected)?.resourceId ? `${uid}-b-${selected}` : undefined}\r\n aria-describedby={`${uid}-help`}\r\n tabIndex={0}\r\n onKeyDown={onKeyDown}\r\n onScroll={onScroll}\r\n >\r\n <div className=\"nf-s-head\" role=\"row\" aria-rowindex={1} style={{ width: resW + scale.width }}>\r\n <div className=\"nf-s-corner\" role=\"columnheader\" dir={dirAttr}>\r\n {labels.resources}\r\n </div>\r\n <div className=\"nf-s-axis\" role=\"columnheader\" aria-label={rangeLabel(startDay, days, view)} style={{ width: scale.width }}>\r\n {scale.days.map((d) => (\r\n <div key={d.day} className=\"nf-s-day\" data-today={d.day === todayDay ? '' : undefined} style={{ left: d.x, width: d.width }}>\r\n {view === 'month' ? (\r\n <>\r\n {compactDay(d.day)}\r\n {d.day === todayDay ? <span className=\"nf-s-sr\">, today</span> : null}\r\n </>\r\n ) : (\r\n <>\r\n {dayHeadLabel(d.label, compactDay(d.day), d.day === todayDay, d.width, textSize('label', textScale, props.textSizes))}\r\n {d.day === todayDay && !fitsDay(`${d.label} · Today`, d.width, textSize('label', textScale, props.textSizes)) ? (\r\n <span className=\"nf-s-sr\">, today</span>\r\n ) : null}\r\n </>\r\n )}\r\n </div>\r\n ))}\r\n {showHours\r\n ? scale.hours\r\n .filter((h, i) => i % hourEvery === 0 && hourLabelFits(h.x, scale.width, captionPx))\r\n .map((h) => (\r\n <div key={h.ms} className=\"nf-s-hour\" style={{ left: h.x }}>\r\n {h.label}\r\n </div>\r\n ))\r\n : null}\r\n </div>\r\n </div>\r\n\r\n <div className=\"nf-s-body\" style={{ height: Math.max(bodyHeight, 120), width: resW + scale.width }}>\r\n <div className=\"nf-s-layer\" aria-hidden=\"true\" style={{ left: resW, width: scale.width }}>\r\n {scale.nonWorking.map((b, i) => (\r\n <div key={`o${i}`} className=\"nf-s-off\" style={{ left: b.x, width: b.width }} />\r\n ))}\r\n {showHours ? scale.hours.map((h) => <div key={`h${h.ms}`} className=\"nf-s-vline\" style={{ left: h.x }} />) : null}\r\n {scale.days.map((d) => (\r\n <div key={`d${d.day}`} className=\"nf-s-vline\" data-day=\"\" style={{ left: d.x }} />\r\n ))}\r\n {nowX !== null ? <div className=\"nf-s-nowline\" style={{ left: nowX }} /> : null}\r\n </div>\r\n\r\n {shownRows.map((row) =>\r\n row.kind === 'group' ? (\r\n <div key={row.key} className=\"nf-s-group\" role=\"row\" style={{ top: row.top, width: resW + scale.width }}>\r\n <div role=\"rowheader\" className=\"nf-s-groupcell\">\r\n <button\r\n type=\"button\"\r\n className=\"nf-s-grouphead\"\r\n aria-expanded={!row.collapsed}\r\n tabIndex={-1}\r\n onClick={() => toggleGroup(row.label === 'Other' ? '' : row.label)}\r\n dir={dirAttr}\r\n >\r\n <Chevron />\r\n {row.label}\r\n <span className=\"nf-s-groupcount\">{row.count}</span>\r\n </button>\r\n </div>\r\n </div>\r\n ) : (\r\n <div\r\n key={row.key}\r\n className=\"nf-s-row\"\r\n role=\"row\"\r\n aria-rowindex={row.index + 2}\r\n aria-selected={selectedRowKey === row.key}\r\n style={{ top: row.top, height: row.height }}\r\n >\r\n <div className=\"nf-s-res\" role=\"rowheader\" dir={dirAttr} style={{ height: row.height }}>\r\n <span className=\"nf-s-avatar\" aria-hidden=\"true\" style={row.resource.color ? { boxShadow: `inset 0 0 0 2px ${row.resource.color}` } : undefined}>\r\n {row.resource.image ? <img src={row.resource.image} alt=\"\" /> : initials(row.resource.name)}\r\n </span>\r\n <span className=\"nf-s-who\">\r\n <span className=\"nf-s-name\">{row.resource.name}</span>\r\n {row.resource.subtitle ? <span className=\"nf-s-sub\">{row.resource.subtitle}</span> : null}\r\n </span>\r\n {(() => {\r\n const cap = capacityText(row);\r\n const overDays = utilOf(row).filter((u) => u.level === 'over');\r\n return cap ? (\r\n <span className=\"nf-s-cap\" data-level={cap.level === 'over' || overDays.length ? 'over' : 'ok'}>\r\n {cap.text}\r\n {cap.level === 'over' ? <span className=\"nf-s-sr\">, over capacity</span> : null}\r\n {overDays.length ? (\r\n <span className=\"nf-s-sr\">\r\n , over on {overDays.map((u) => `${dayLabelIn(dayToInstant(u.day, zone), zone)} (${u.booked} of ${u.capacity} h)`).join(', ')}\r\n </span>\r\n ) : null}\r\n </span>\r\n ) : null;\r\n })()}\r\n </div>\r\n <div\r\n className=\"nf-s-lanes\"\r\n data-create={can('create') ? '' : undefined}\r\n style={{ width: scale.width, height: row.height }}\r\n onPointerDown={(e) => onLanePointerDown(e, row)}\r\n >\r\n {utilOf(row).map((u) => {\r\n const t = dayTicks.get(u.day);\r\n if (!t) return null;\r\n return (\r\n <React.Fragment key={`u${u.day}`}>\r\n {u.level === 'over' ? <div className=\"nf-s-overday\" aria-hidden=\"true\" style={{ left: t.x, width: t.width }} /> : null}\r\n <div className=\"nf-s-util\" aria-hidden=\"true\" style={{ left: t.x + 4, width: Math.max(0, t.width - 8) }}>\r\n <i data-level={u.level} style={{ width: `${Math.min(1, u.ratio) * 100}%` }} />\r\n </div>\r\n </React.Fragment>\r\n );\r\n })}\r\n {row.items.map((it) => renderBooking(it, row))}\r\n {ghost && ghost.resourceId === row.resource.id ? (\r\n <div\r\n className=\"nf-s-ghost\"\r\n aria-hidden=\"true\"\r\n style={{ left: scale.x(ghost.start), width: Math.max(6, scale.x(ghost.end) - scale.x(ghost.start)), top: ROW_PAD }}\r\n >\r\n {whenLabel(ghost.start, ghost.end, zone)}\r\n </div>\r\n ) : null}\r\n </div>\r\n </div>\r\n )\r\n )}\r\n </div>\r\n </div>\r\n )}\r\n {/* Outside the grid, which may only own rows (axe aria-required-children, QA-2026-10-31-9);\r\n placed over the body, just below the sticky header. */}\r\n {view !== 'agenda' && rows.length === 0 ? (\r\n <div className=\"nf-s-empty\" role=\"status\" aria-busy={props.loading ? true : undefined} style={{ top: headH + 40, right: panelOpen ? Math.round(280 * textScale) : 0 }}>\r\n {props.loading ? `Loading ${labels.bookings.toLowerCase()}…` : filter ? `No ${labels.resources.toLowerCase()} match “${filter}”.` : `No ${labels.resources.toLowerCase()} yet.`}\r\n {props.emptyAction && !props.loading && !filter ? <EmptyActionView action={props.emptyAction} prefix=\"nf-s\" buttonClass=\"nf-s-btn\" /> : null}\r\n </div>\r\n ) : null}\r\n\r\n {panelOpen ? (\r\n <aside className=\"nf-s-unassigned\" id={`${uid}-unassigned`} aria-label={`Unassigned ${labels.bookings.toLowerCase()}`} dir={dirAttr}>\r\n <div className=\"nf-s-un-head\">\r\n Unassigned\r\n <span className=\"nf-s-count\">{queueFilter ? `${queueShown.length} of ${queue.length}` : queue.length}</span>\r\n </div>\r\n {queue.length ? (\r\n <div className=\"nf-s-un-tools\">\r\n <input\r\n className=\"nf-s-filter\"\r\n type=\"search\"\r\n placeholder={`Filter ${labels.bookings.toLowerCase()}`}\r\n aria-label={`Filter unassigned ${labels.bookings.toLowerCase()} by name, location or status`}\r\n value={queueFilter}\r\n onChange={(e) => setQueueFilter(e.target.value)}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Escape' && queueFilter) {\r\n setQueueFilter('');\r\n e.preventDefault();\r\n }\r\n }}\r\n />\r\n <label className=\"nf-s-pick\">\r\n <span className=\"nf-s-sr\">Sort unassigned by</span>\r\n <select value={queueSort} onChange={(e) => setQueueSort(e.target.value as QueueSort)}>\r\n {(Object.keys(QUEUE_SORT_LABEL) as QueueSort[]).map((k) => (\r\n <option key={k} value={k}>\r\n {QUEUE_SORT_LABEL[k]}\r\n </option>\r\n ))}\r\n </select>\r\n </label>\r\n </div>\r\n ) : null}\r\n {queueShown.length ? (\r\n <ul className=\"nf-s-un-list\">\r\n {queueShown.map((it, i) => {\r\n const d = instantToDay(it.start, zone);\r\n const newDay = byDay && (i === 0 || instantToDay(queueShown[i - 1]!.start, zone) !== d);\r\n return (\r\n <li key={it.id}>\r\n {newDay ? <div className=\"nf-s-un-day\">{dayLabelIn(it.start, zone)}</div> : null}\r\n <button\r\n type=\"button\"\r\n className=\"nf-s-un-item\"\r\n aria-pressed={selected === it.id}\r\n aria-describedby={can('assign', it.booking) ? `${uid}-queue-help` : undefined}\r\n data-editable={can('assign', it.booking) ? '' : undefined}\r\n onPointerDown={(e) => onQueuePointerDown(e, it)}\r\n onClick={() => select(it.id)}\r\n onDoubleClick={() => props.onOpen?.(it.booking)}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter') {\r\n e.preventDefault();\r\n props.onOpen?.(it.booking);\r\n return;\r\n }\r\n // Alt+↓ assigns to the first row (keeping its time); then Alt+↑/↓ on the board moves it.\r\n if (keyboardEdit(e, it.id)) {\r\n e.preventDefault();\r\n select(it.id);\r\n }\r\n }}\r\n >\r\n <span className=\"nf-s-sr\">{bookingWords(it)}</span>\r\n <span aria-hidden=\"true\">{it.booking.name}</span>\r\n <small aria-hidden=\"true\">\r\n {byDay ? '' : `${dayLabelIn(it.start, zone)} · `}\r\n <span className=\"nf-s-ltr\">{it.booking.allDay ? 'All day' : `${timeLabel(it.start, zone)}–${timeLabel(it.end, zone)}`}</span>\r\n {it.booking.location ? ` · ${it.booking.location}` : ''}\r\n </small>\r\n </button>\r\n </li>\r\n );\r\n })}\r\n </ul>\r\n ) : (\r\n <div className=\"nf-s-un-empty\">{queue.length ? `Nothing matches “${queueFilter}”.` : 'Nothing to schedule.'}</div>\r\n )}\r\n </aside>\r\n ) : null}\r\n </div>\r\n\r\n <div id={`${uid}-help`} className=\"nf-s-sr\">\r\n Arrow keys move between {labels.bookings.toLowerCase()}: left and right along a row, up and down to the next {labels.resource.toLowerCase()}. Enter opens.\r\n {props.onBookingChange\r\n ? ` Alt plus left or right moves the selected ${labels.booking.toLowerCase()} in time, Alt plus Shift plus left or right changes its end, Alt plus up or down moves it to another ${labels.resource.toLowerCase()}. Control plus Z undoes.`\r\n : ''}\r\n {props.onCreate ? ` Insert adds a ${labels.booking.toLowerCase()} after the selected one.` : ''}\r\n </div>\r\n <div id={`${uid}-queue-help`} className=\"nf-s-sr\">\r\n Alt plus down arrow assigns it to the first {labels.resource.toLowerCase()}, keeping its time. Or drag it onto a row.\r\n </div>\r\n <Toasts prefix=\"nf-s\" items={editing.toasts} announcement={editing.announcement} onDismiss={editing.dismiss} />\r\n\r\n {license && !license.licensed && props.watermark !== false ? (\r\n <Watermark prefix=\"nf-s\" title={licenseMessage(license)} markHtml={markSvg({ size: 16 })} variant={props.watermarkStyle} onClick={props.onWatermarkClick} />\r\n ) : null}\r\n {editing.lockNote ? (\r\n <p className=\"nf-s-locknote\" role=\"status\">\r\n {editing.lockNote}\r\n </p>\r\n ) : null}\r\n {renewal ? (\r\n <a className=\"nf-s-renew\" href={renewal.href} target=\"_blank\" rel=\"noreferrer\" title={renewal.detail}>\r\n {renewal.text}\r\n </a>\r\n ) : null}\r\n </div>\r\n );\r\n}\r\n\r\nexport type { SchedBooking };\r\n","/** This package's version, for Copy diagnostics. `version.test.ts` keeps it equal to package.json. */\r\nexport const VERSION = '1.0.0-rc.1';\r\n","/**\r\n * \"Copy diagnostics\": one click copies what support needs to reproduce a problem, and nothing\r\n * else. Product and control version, licence state (never the key), host, browser, locale, time\r\n * zone, the mapped property names and the row count. No data values and no personal data: the\r\n * input types only take names and counts, and every string is trimmed to one short line.\r\n *\r\n * Framework-free. The drop-in menu item is `CopyDiagnosticsItem` in `@ninthforge/core/react`.\r\n */\r\n\r\n/** Where the control runs. PCF wrappers and Power BI visuals pass it when they know. */\r\nexport type DiagnosticsHost =\r\n | 'model-driven'\r\n | 'canvas'\r\n | 'canvas (Studio)'\r\n | 'custom page'\r\n | 'Power BI'\r\n | 'code app'\r\n | 'web page';\r\n\r\n/**\r\n * The licence as diagnostics reads it: a `LicenseResult` from `@ninthforge/license` fits. Only\r\n * `status`, `payload.tier`, `payload.org` and `payload.exp` are read; the key itself never\r\n * reaches this function, and the tenant ID and key id are left out.\r\n */\r\nexport interface DiagnosticsLicence {\r\n status: string;\r\n payload?: { tier?: string; org?: string; exp?: string } | null;\r\n}\r\n\r\nexport interface DiagnosticsInput {\r\n /** The product, e.g. `Plan`, `Charts`. */\r\n product: string;\r\n /** The control, e.g. `Gantt` or `nf_NinthForge.PlanGantt`. */\r\n control: string;\r\n /** The control or package version, e.g. `0.4.4`. */\r\n version: string;\r\n licence?: DiagnosticsLicence | null;\r\n /** Overrides detection (Power BI visuals, code apps, or a PCF that knows its host). */\r\n host?: DiagnosticsHost;\r\n /**\r\n * Mapped properties: `{ taskStart: 'nf_start' }` gives `taskStart → nf_start`, and a list\r\n * gives the names alone. Names only, never values.\r\n */\r\n properties?: Readonly<Record<string, string | null | undefined>> | readonly string[];\r\n /** Rows the control received. */\r\n rows?: number;\r\n /**\r\n * A few product-specific settings, e.g. `{ 'Save mode': 'direct' }`. Settings only: never\r\n * record values, user names or e-mail addresses.\r\n */\r\n extra?: Readonly<Record<string, string | number | boolean | null | undefined>>;\r\n now?: Date;\r\n}\r\n\r\n/**\r\n * What a host adds to a component's own diagnostics (the component fills product, version,\r\n * licence and its row count): a PCF passes its control name, mapped properties and settings.\r\n */\r\nexport type DiagnosticsHostInfo = Partial<Pick<DiagnosticsInput, 'control' | 'version' | 'host' | 'properties' | 'rows' | 'extra'>>;\r\n\r\nexport interface Diagnostics {\r\n product: string;\r\n control: string;\r\n version: string;\r\n licence: { state: string; org?: string; expires?: string };\r\n host: DiagnosticsHost;\r\n browser: string;\r\n locale: string;\r\n timeZone: string;\r\n properties: string[];\r\n rows?: number;\r\n extra: [string, string][];\r\n /** When it was copied, UTC ISO. */\r\n at: string;\r\n}\r\n\r\n/** The parts of `window` diagnostics reads; tests pass a fake. */\r\nexport interface DiagnosticsEnv {\r\n navigator?: {\r\n userAgent?: string;\r\n language?: string;\r\n languages?: readonly string[];\r\n userAgentData?: { brands?: { brand: string; version: string }[]; platform?: string };\r\n };\r\n location?: { hostname?: string; ancestorOrigins?: ArrayLike<string> };\r\n Xrm?: unknown;\r\n}\r\n\r\nconst MAX = 80;\r\n\r\n/** One short line: no control characters or new lines, at most 80 characters. */\r\nfunction clean(value: unknown): string {\r\n const s = String(value ?? '')\r\n .replace(/[\\u0000-\\u001f\\u007f]+/g, ' ')\r\n .trim();\r\n return s.length > MAX ? `${s.slice(0, MAX - 1)}…` : s;\r\n}\r\n\r\nfunction envOf(win?: DiagnosticsEnv): DiagnosticsEnv {\r\n if (win) return win;\r\n return typeof window === 'undefined' ? {} : (window as unknown as DiagnosticsEnv);\r\n}\r\n\r\nfunction ancestors(env: DiagnosticsEnv): string[] {\r\n try {\r\n const list = env.location?.ancestorOrigins;\r\n return list ? Array.from(list, String) : [];\r\n } catch {\r\n return [];\r\n }\r\n}\r\n\r\nfunction hostnameOf(origin: string): string {\r\n return origin.replace(/^[a-z]+:\\/\\//i, '').replace(/[:/].*$/, '').toLowerCase();\r\n}\r\n\r\nconst endsWith = (host: string, suffix: string) => host === suffix || host.endsWith(`.${suffix}`);\r\n\r\n/**\r\n * Best-effort host detection, for when the wrapper doesn't say. Model-driven pages have `Xrm`\r\n * (a custom page says `pageType: 'custom'`). A canvas player inside a Dynamics page is a custom\r\n * page, and inside Power BI it's the Power Apps visual. Studio runs on `authoring.*.powerapps.com`.\r\n */\r\nexport function detectHost(win?: DiagnosticsEnv): DiagnosticsHost {\r\n const env = envOf(win);\r\n const xrm = env.Xrm as { Utility?: { getPageContext?: () => { input?: { pageType?: string } } } } | undefined;\r\n if (xrm) {\r\n try {\r\n if (xrm.Utility?.getPageContext?.()?.input?.pageType === 'custom') return 'custom page';\r\n } catch {\r\n /* not a page context we know */\r\n }\r\n return 'model-driven';\r\n }\r\n const up = ancestors(env).map(hostnameOf);\r\n if (up.some((h) => endsWith(h, 'powerbi.com') || endsWith(h, 'fabric.microsoft.com'))) return 'Power BI';\r\n const host = (env.location?.hostname ?? '').toLowerCase();\r\n if (endsWith(host, 'powerbi.com')) return 'Power BI';\r\n if (up.some((h) => endsWith(h, 'dynamics.com'))) return 'custom page';\r\n if (host.startsWith('authoring.') && endsWith(host, 'powerapps.com')) return 'canvas (Studio)';\r\n if (endsWith(host, 'powerapps.com') || endsWith(host, 'powerplatformusercontent.com')) return 'canvas';\r\n return 'web page';\r\n}\r\n\r\nconst BRANDS: [RegExp, string][] = [\r\n [/Edg(?:e|A|iOS)?\\/(\\d+)/, 'Edge'],\r\n [/OPR\\/(\\d+)/, 'Opera'],\r\n [/Firefox\\/(\\d+)/, 'Firefox'],\r\n [/FxiOS\\/(\\d+)/, 'Firefox'],\r\n [/CriOS\\/(\\d+)/, 'Chrome'],\r\n [/Chrome\\/(\\d+)/, 'Chrome'],\r\n [/Version\\/(\\d+)[.\\d]* (?:Mobile\\/\\S+ )?Safari\\//, 'Safari']\r\n];\r\n\r\nconst PLATFORMS: [RegExp, string][] = [\r\n [/iPhone|iPad|iPod/, 'iOS'],\r\n [/Android/, 'Android'],\r\n [/Windows/, 'Windows'],\r\n [/Mac OS X|Macintosh/, 'macOS'],\r\n [/CrOS/, 'ChromeOS'],\r\n [/Linux/, 'Linux']\r\n];\r\n\r\n/** \"Edge 131 on Windows\": brand, major version and platform only, not the full user agent. */\r\nexport function describeBrowser(win?: DiagnosticsEnv): string {\r\n const nav = envOf(win).navigator;\r\n const ua = nav?.userAgent ?? '';\r\n let name = '';\r\n for (const [re, brand] of BRANDS) {\r\n const m = re.exec(ua);\r\n if (m) {\r\n name = `${brand} ${m[1]}`;\r\n break;\r\n }\r\n }\r\n if (!name) {\r\n const brands = nav?.userAgentData?.brands ?? [];\r\n const b = brands.find((x) => !/not.?a.?brand|chromium/i.test(x.brand)) ?? brands[0];\r\n name = b ? `${b.brand.replace('Microsoft ', '')} ${b.version}` : 'Unknown browser';\r\n }\r\n const platform = PLATFORMS.find(([re]) => re.test(ua))?.[1] ?? nav?.userAgentData?.platform;\r\n return clean(platform ? `${name} on ${platform}` : name);\r\n}\r\n\r\nfunction offsetLabel(minutes: number): string {\r\n const sign = minutes < 0 ? '−' : '+';\r\n const abs = Math.abs(minutes);\r\n return `UTC${sign}${String(Math.floor(abs / 60)).padStart(2, '0')}:${String(abs % 60).padStart(2, '0')}`;\r\n}\r\n\r\nfunction licenceOf(l: DiagnosticsLicence | null | undefined): Diagnostics['licence'] {\r\n if (!l) return { state: 'not checked' };\r\n const p = l.payload ?? undefined;\r\n let state = clean(l.status) || 'unknown';\r\n if ((state === 'valid' || state === 'unverifiable') && p?.tier === 'trial') state = 'trial';\r\n else if (state === 'unverifiable') state = 'valid (browser cannot verify)';\r\n const out: Diagnostics['licence'] = { state };\r\n if (p?.org) out.org = clean(p.org);\r\n if (p?.exp && /^\\d{4}-\\d{2}-\\d{2}$/.test(p.exp)) out.expires = p.exp;\r\n return out;\r\n}\r\n\r\nfunction propertiesOf(p: DiagnosticsInput['properties']): string[] {\r\n if (!p) return [];\r\n if (Array.isArray(p)) return (p as readonly string[]).map(clean).filter(Boolean);\r\n return Object.keys(p)\r\n .map((name) => {\r\n const column = (p as Record<string, string | null | undefined>)[name];\r\n return column ? `${clean(name)} → ${clean(column)}` : `${clean(name)} (not mapped)`;\r\n })\r\n .filter(Boolean);\r\n}\r\n\r\n/** Gather the diagnostics. Never throws. */\r\nexport function collectDiagnostics(input: DiagnosticsInput, win?: DiagnosticsEnv): Diagnostics {\r\n const env = envOf(win);\r\n const now = input.now ?? new Date();\r\n let locale = clean(env.navigator?.language);\r\n let zone = '';\r\n try {\r\n const opts = Intl.DateTimeFormat().resolvedOptions();\r\n if (!locale) locale = opts.locale;\r\n else if (opts.locale && opts.locale !== locale) locale = `${locale} (formats ${opts.locale})`;\r\n zone = opts.timeZone ?? '';\r\n } catch {\r\n /* Intl missing: leave blank */\r\n }\r\n // The offset itself, not a date: the one place a local-time getter is what we want.\r\n const offset = offsetLabel(-now.getTimezoneOffset());\r\n const extra: [string, string][] = Object.keys(input.extra ?? {})\r\n .filter((k) => input.extra![k] !== undefined && input.extra![k] !== null)\r\n .map((k) => [clean(k), clean(input.extra![k])]);\r\n const d: Diagnostics = {\r\n product: clean(input.product),\r\n control: clean(input.control),\r\n version: clean(input.version),\r\n licence: licenceOf(input.licence),\r\n host: input.host ?? detectHost(env),\r\n browser: describeBrowser(env),\r\n locale: locale || 'unknown',\r\n timeZone: zone ? `${clean(zone)} (${offset})` : offset,\r\n properties: propertiesOf(input.properties),\r\n extra,\r\n at: now.toISOString().replace(/\\.\\d{3}Z$/, 'Z')\r\n };\r\n if (typeof input.rows === 'number' && Number.isFinite(input.rows)) d.rows = Math.max(0, Math.round(input.rows));\r\n return d;\r\n}\r\n\r\n/** Plain text for an e-mail to support. */\r\nexport function formatDiagnostics(d: Diagnostics): string {\r\n const lic = [d.licence.state, d.licence.org, d.licence.expires && `expires ${d.licence.expires}`]\r\n .filter(Boolean)\r\n .join(' · ');\r\n const lines = [\r\n 'Ninth Forge diagnostics',\r\n `Product: ${d.product} · ${d.control} ${d.version}`,\r\n `Licence: ${lic}`,\r\n `Host: ${d.host}`,\r\n `Browser: ${d.browser}`,\r\n `Locale: ${d.locale}`,\r\n `Time zone: ${d.timeZone}`,\r\n `Properties: ${d.properties.length ? d.properties.join(', ') : 'none mapped'}`\r\n ];\r\n if (d.rows !== undefined) lines.push(`Rows: ${d.rows}`);\r\n for (const [k, v] of d.extra) lines.push(`${k}: ${v}`);\r\n lines.push(`Copied: ${d.at}`);\r\n return lines.join('\\n');\r\n}\r\n\r\n/** The parts of `window` copying needs; tests pass a fake. */\r\nexport interface ClipboardEnv {\r\n navigator?: { clipboard?: { writeText?: (text: string) => Promise<void> } };\r\n document?: Document;\r\n}\r\n\r\n/**\r\n * Copy text to the clipboard. Tries the Clipboard API, then the `execCommand('copy')` fallback\r\n * (canvas apps and Power BI run controls in iframes where the Clipboard API is often blocked).\r\n * Call it straight from a click: both need the user's gesture. Focus goes back where it was.\r\n */\r\nexport async function copyText(text: string, win?: ClipboardEnv): Promise<boolean> {\r\n const env: ClipboardEnv = win ?? (typeof window === 'undefined' ? {} : (window as unknown as ClipboardEnv));\r\n const fallback = (): boolean => {\r\n const doc = env.document;\r\n if (!doc?.body) return false;\r\n const active = doc.activeElement as HTMLElement | null;\r\n const area = doc.createElement('textarea');\r\n area.value = text;\r\n area.setAttribute('readonly', '');\r\n area.setAttribute('aria-hidden', 'true');\r\n area.style.cssText = 'position:fixed;top:0;left:0;width:1px;height:1px;opacity:0;pointer-events:none';\r\n doc.body.appendChild(area);\r\n let ok = false;\r\n try {\r\n area.select();\r\n ok = doc.execCommand('copy');\r\n } catch {\r\n ok = false;\r\n }\r\n area.remove();\r\n active?.focus?.();\r\n return ok;\r\n };\r\n const write = env.navigator?.clipboard?.writeText;\r\n if (write) {\r\n try {\r\n // Started synchronously, inside the click.\r\n await write.call(env.navigator!.clipboard, text);\r\n return true;\r\n } catch {\r\n return fallback();\r\n }\r\n }\r\n return fallback();\r\n}\r\n\r\nexport interface CopyDiagnosticsResult {\r\n ok: boolean;\r\n /** The copied text, so a product can show it to copy by hand if the clipboard refused. */\r\n text: string;\r\n /** \"Diagnostics copied\", or what to do instead. For a toast or a live region. */\r\n message: string;\r\n}\r\n\r\nexport const DIAGNOSTICS_COPIED = 'Diagnostics copied. Paste them into your e-mail to support.';\r\nexport const DIAGNOSTICS_NOT_COPIED = \"Couldn't copy diagnostics. Your browser blocked the clipboard.\";\r\n\r\n/** Collect, format and copy. Call it straight from a click. */\r\nexport async function copyDiagnostics(\r\n input: DiagnosticsInput,\r\n win?: DiagnosticsEnv & ClipboardEnv\r\n): Promise<CopyDiagnosticsResult> {\r\n const text = formatDiagnostics(collectDiagnostics(input, win));\r\n const ok = await copyText(text, win);\r\n return { ok, text, message: ok ? DIAGNOSTICS_COPIED : DIAGNOSTICS_NOT_COPIED };\r\n}\r\n","/**\n * Dates are ISO `YYYY-MM-DD` strings, handled as whole UTC days.\n * Internally a date is a day number: days since 1970-01-01.\n * Never use local-time getters on these — they shift a day west of Greenwich.\n */\n\nconst DAY_MS = 86_400_000;\nconst ISO = /^(\\d{4})-(\\d{2})-(\\d{2})/;\n\nexport const MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] as const;\nexport const MONTHS_LONG = [\n 'January', 'February', 'March', 'April', 'May', 'June',\n 'July', 'August', 'September', 'October', 'November', 'December'\n] as const;\nexport const WEEKDAYS = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] as const;\n\n/** ISO date (or datetime — the time is ignored) → day number. NaN when invalid. */\nexport function toDay(iso: string | null | undefined): number {\n if (!iso) return NaN;\n const m = ISO.exec(iso);\n if (!m) return NaN;\n const y = Number(m[1]);\n const mo = Number(m[2]);\n const d = Number(m[3]);\n if (mo < 1 || mo > 12 || d < 1 || d > 31) return NaN;\n return Math.round(Date.UTC(y, mo - 1, d) / DAY_MS);\n}\n\n/** Day number → ISO date. */\nexport function fromDay(day: number): string {\n return new Date(day * DAY_MS).toISOString().slice(0, 10);\n}\n\nexport function addDays(iso: string, n: number): string {\n return fromDay(toDay(iso) + n);\n}\n\n/** Inclusive length in days of a start/end pair (a one-day task is 1). */\nexport function spanDays(startIso: string, endIso: string): number {\n return toDay(endIso) - toDay(startIso) + 1;\n}\n\n/** 0 = Monday … 6 = Sunday. 1970-01-01 was a Thursday. */\nexport function weekday(day: number): number {\n return (((day + 3) % 7) + 7) % 7;\n}\n\nexport function parts(day: number): { year: number; month: number; date: number } {\n const d = new Date(day * DAY_MS);\n return { year: d.getUTCFullYear(), month: d.getUTCMonth(), date: d.getUTCDate() };\n}\n\nexport function dayOf(year: number, month: number, date = 1): number {\n return Math.round(Date.UTC(year, month, date) / DAY_MS);\n}\n\nexport function startOfWeek(day: number): number {\n return day - weekday(day);\n}\n\nexport function startOfMonth(day: number): number {\n const p = parts(day);\n return dayOf(p.year, p.month, 1);\n}\n\nexport function addMonths(day: number, n: number): number {\n const p = parts(day);\n return dayOf(p.year, p.month + n, 1);\n}\n\nexport function startOfQuarter(day: number): number {\n const p = parts(day);\n return dayOf(p.year, Math.floor(p.month / 3) * 3, 1);\n}\n\n/** The viewer's local \"today\" as a day number — the one place local time is right. */\nexport function today(now: Date = new Date()): number {\n return dayOf(now.getFullYear(), now.getMonth(), now.getDate());\n}\n\n/** \"Oct 12\" */\nexport function formatShort(day: number): string {\n const p = parts(day);\n return `${MONTHS[p.month]} ${p.date}`;\n}\n\n/** \"12 Oct 2026\" */\nexport function formatLong(day: number): string {\n const p = parts(day);\n return `${p.date} ${MONTHS[p.month]} ${p.year}`;\n}\n\n/* ---------- Working days ---------- */\n\n/** Which days count as working days. `mon-fri` skips Saturdays and Sundays; `all` counts every day. */\nexport type WorkingWeek = 'mon-fri' | 'all';\n\nexport function isWorkingDay(day: number, week: WorkingWeek = 'mon-fri'): boolean {\n return week === 'all' || weekday(day) < 5;\n}\n\n/** Working days from `start` to `end`, both inclusive. 0 when the span is all weekend or reversed. */\nexport function workingDaysBetween(start: number, end: number, week: WorkingWeek = 'mon-fri'): number {\n if (Number.isNaN(start) || Number.isNaN(end) || end < start) return 0;\n const total = end - start + 1;\n if (week === 'all') return total;\n const full = Math.floor(total / 7);\n let count = full * 5;\n for (let d = start + full * 7; d <= end; d++) if (isWorkingDay(d, week)) count++;\n return count;\n}\n\n/** `day` if it's a working day, else the nearest working day forward (dir ≥ 0) or backward (dir < 0). */\nexport function snapToWorkingDay(day: number, dir: number, week: WorkingWeek = 'mon-fri'): number {\n if (Number.isNaN(day)) return day;\n const step = dir < 0 ? -1 : 1;\n let d = day;\n for (let i = 0; i < 7 && !isWorkingDay(d, week); i++) d += step;\n return d;\n}\n\n/** The day `n` working days after `day` (n ≥ 0), counting from the first working day at or after `day`. */\nexport function addWorkingDays(day: number, n: number, week: WorkingWeek = 'mon-fri'): number {\n let d = snapToWorkingDay(day, 1, week);\n if (week === 'all') return d + n;\n d += Math.floor(n / 5) * 7;\n for (let left = n % 5; left > 0; ) {\n d++;\n if (isWorkingDay(d, week)) left--;\n }\n return d;\n}\n","import {\n addMonths,\n MONTHS,\n MONTHS_LONG,\n parts,\n startOfMonth,\n startOfQuarter,\n startOfWeek,\n weekday\n} from './date';\n\nexport type Zoom = 'week' | 'month' | 'quarter';\n\nexport const ZOOMS: readonly Zoom[] = ['week', 'month', 'quarter'];\n\n/** Pixels per day at each zoom level. */\nexport const DAY_WIDTH: Record<Zoom, number> = { week: 36, month: 9, quarter: 3 };\n\nexport interface Tick {\n day: number;\n x: number;\n width: number;\n label: string;\n}\n\nexport interface TimeScale {\n zoom: Zoom;\n startDay: number;\n /** Exclusive. */\n endDay: number;\n dayWidth: number;\n width: number;\n x(day: number): number;\n /** Day at a pixel offset (for drag, later). */\n dayAt(x: number): number;\n major: Tick[];\n minor: Tick[];\n /** Weekend bands, only at week zoom. */\n weekends: { x: number; width: number }[];\n}\n\nexport interface ScaleInput {\n /** First and last day that must be visible (inclusive). */\n from: number;\n to: number;\n zoom: Zoom;\n /** Extra breathing room after `to`, in days. */\n padDays?: number;\n /** Extend the end until the scale is at least this wide (fills the viewport). */\n minWidth?: number;\n}\n\nfunction snapStart(day: number, zoom: Zoom): number {\n if (zoom === 'week') return startOfWeek(day);\n if (zoom === 'month') return startOfMonth(day);\n return startOfQuarter(day);\n}\n\nfunction snapEnd(day: number, zoom: Zoom): number {\n // Exclusive end on the next boundary after `day`.\n if (zoom === 'week') return startOfWeek(day) + 7;\n if (zoom === 'month') return addMonths(day, 1);\n return addMonths(startOfQuarter(day), 3);\n}\n\nexport function createTimeScale({ from, to, zoom, padDays, minWidth = 0 }: ScaleInput): TimeScale {\n const pad = padDays ?? (zoom === 'week' ? 3 : zoom === 'month' ? 10 : 30);\n const startDay = snapStart(from, zoom);\n const dayWidth = DAY_WIDTH[zoom];\n let endDay = snapEnd(Math.max(to, from) + pad, zoom);\n for (let guard = 0; (endDay - startDay) * dayWidth < minWidth && guard < 120; guard++) {\n endDay = snapEnd(endDay, zoom);\n }\n const x = (day: number) => (day - startDay) * dayWidth;\n\n const major: Tick[] = [];\n const minor: Tick[] = [];\n const weekends: { x: number; width: number }[] = [];\n\n if (zoom === 'quarter') {\n for (let d = startDay; d < endDay; d = addMonths(d, 3)) {\n const p = parts(d);\n const next = addMonths(d, 3);\n major.push({ day: d, x: x(d), width: (next - d) * dayWidth, label: `Q${Math.floor(p.month / 3) + 1} ${p.year}` });\n }\n for (let d = startDay; d < endDay; d = addMonths(d, 1)) {\n const next = addMonths(d, 1);\n minor.push({ day: d, x: x(d), width: (next - d) * dayWidth, label: MONTHS[parts(d).month] ?? '' });\n }\n } else {\n let first = true;\n for (let d = startOfMonth(startDay); d < endDay; d = addMonths(d, 1)) {\n const next = addMonths(d, 1);\n const left = Math.max(d, startDay);\n const p = parts(d);\n const label = first || p.month === 0 ? `${MONTHS_LONG[p.month]} ${p.year}` : `${MONTHS_LONG[p.month]}`;\n major.push({ day: left, x: x(left), width: (Math.min(next, endDay) - left) * dayWidth, label });\n first = false;\n }\n if (zoom === 'week') {\n for (let d = startDay; d < endDay; d++) {\n minor.push({ day: d, x: x(d), width: dayWidth, label: String(parts(d).date) });\n if (weekday(d) === 5) weekends.push({ x: x(d), width: dayWidth * 2 });\n }\n } else {\n for (let d = startOfWeek(startDay); d < endDay; d += 7) {\n if (d < startDay) continue;\n minor.push({ day: d, x: x(d), width: 7 * dayWidth, label: String(parts(d).date) });\n }\n }\n }\n\n return {\n zoom,\n startDay,\n endDay,\n dayWidth,\n width: (endDay - startDay) * dayWidth,\n x,\n dayAt: (px: number) => startDay + Math.floor(px / dayWidth),\n major,\n minor,\n weekends\n };\n}\n","/**\n * Time zones for instant (UTC millisecond) maths.\n *\n * A `Zone` answers one question: at this instant, how many minutes **east of UTC** is the\n * wall clock? Perth is +480, New York −300 (winter) / −240 (summer). Everything that turns\n * instants into dates, hours or pixels goes through a Zone — never through the machine's\n * local getters, which are wrong whenever the user's Dataverse zone differs from the browser's.\n */\nexport type Zone = (utcMs: number) => number;\n\nconst MINUTE = 60_000;\n\n/** No offset, ever. Also the right zone for time-zone-independent (\"floating\") values. */\nexport const UTC: Zone = () => 0;\n\nexport function fixedZone(minutesEast: number): Zone {\n return () => minutesEast;\n}\n\n/** The browser's own zone. Only for demos; in Power Apps use `pcfZone`. */\nexport function browserZone(): Zone {\n return (ms) => -new Date(ms).getTimezoneOffset() || 0;\n}\n\nconst formatters = new Map<string, Intl.DateTimeFormat>();\n\n/** An IANA zone such as `Europe/London`, via `Intl`. Used by the demo and the tests. */\nexport function ianaZone(name: string): Zone {\n let fmt = formatters.get(name);\n if (!fmt) {\n fmt = new Intl.DateTimeFormat('en-US', {\n timeZone: name,\n hourCycle: 'h23',\n year: 'numeric',\n month: 'numeric',\n day: 'numeric',\n hour: 'numeric',\n minute: 'numeric',\n second: 'numeric'\n });\n formatters.set(name, fmt);\n }\n const f = fmt;\n return (ms) => {\n const p: Record<string, number> = {};\n for (const part of f.formatToParts(new Date(ms))) if (part.type !== 'literal') p[part.type] = Number(part.value);\n const wall = Date.UTC(p.year!, p.month! - 1, p.day!, p.hour === 24 ? 0 : p.hour!, p.minute!, p.second!);\n return Math.round((wall - Math.floor(ms / 1000) * 1000) / MINUTE);\n };\n}\n\n/**\n * The Power Apps user's zone, from `context.userSettings.getTimeZoneOffsetMinutes(date)`.\n *\n * Measured in Dev (DECISIONS 2026-10-25): it returns minutes **east** of UTC (Perth +480,\n * Pacific −480 in January, −420 in July), so `sign` defaults to +1. It must always be given a\n * date: without one the PCF API returned the standard offset during daylight time.\n */\nexport function pcfZone(getOffset: (date: Date) => number, sign: 1 | -1 = 1): Zone {\n return (ms) => sign * getOffset(new Date(ms)) || 0;\n}\n","import { MONTHS, WEEKDAYS, weekday } from './date';\nimport type { Zone } from './zone';\n\n/*\n * Instant maths for the Scheduler. An instant is UTC milliseconds. Wall-clock values (a date\n * and time as the user reads it) only exist relative to a Zone.\n *\n * The Gantt's day numbers (date.ts) count calendar days; they meet instants only through\n * `dayToInstant` and `instantToDay`, never by multiplying by 86,400,000 — DST days are 23 or\n * 25 hours long.\n */\n\nconst MINUTE = 60_000;\nconst HOUR = 3_600_000;\nconst DAY = 86_400_000;\n\n/** A wall-clock time. `m` is 1–12. */\nexport interface Wall {\n y: number;\n m: number;\n d: number;\n h?: number;\n min?: number;\n s?: number;\n}\n\n/** The wall clock in `zone` at an instant. */\nexport function instantToWall(ms: number, zone: Zone): Required<Wall> {\n const t = new Date(ms + zone(ms) * MINUTE);\n return { y: t.getUTCFullYear(), m: t.getUTCMonth() + 1, d: t.getUTCDate(), h: t.getUTCHours(), min: t.getUTCMinutes(), s: t.getUTCSeconds() };\n}\n\n/**\n * The instant a wall-clock time happens in `zone`.\n *\n * - **Gap** (spring forward: 02:30 doesn't exist in New York on 8 Mar 2026): moves forward by\n * the length of the gap, so 02:30 → 03:30 daylight time.\n * - **Overlap** (fall back: 01:30 happens twice on 1 Nov 2026): the first occurrence (daylight).\n *\n * Assumes at most one offset change within a day either side, which holds for every real zone.\n */\nexport function wallToInstant(w: Wall, zone: Zone): number {\n const local = Date.UTC(w.y, w.m - 1, w.d, w.h ?? 0, w.min ?? 0, w.s ?? 0);\n const before = zone(local - DAY);\n const after = zone(local + DAY);\n const tBefore = local - before * MINUTE;\n if (before === after) return tBefore;\n const tAfter = local - after * MINUTE;\n const okBefore = zone(tBefore) === before;\n const okAfter = zone(tAfter) === after;\n if (okBefore && okAfter) return Math.min(tBefore, tAfter); // overlap → first occurrence\n if (okBefore) return tBefore;\n if (okAfter) return tAfter;\n return tBefore; // gap → forward by the gap's length\n}\n\n/** Calendar day number (as in date.ts) of the wall date at an instant. */\nexport function instantToDay(ms: number, zone: Zone): number {\n const w = instantToWall(ms, zone);\n return Math.round(Date.UTC(w.y, w.m - 1, w.d) / DAY);\n}\n\n/** The instant a calendar day starts in `zone` (local midnight, or the first instant after a midnight gap). */\nexport function dayToInstant(day: number, zone: Zone): number {\n const t = new Date(day * DAY);\n return wallToInstant({ y: t.getUTCFullYear(), m: t.getUTCMonth() + 1, d: t.getUTCDate() }, zone);\n}\n\n/** Start of the wall day containing `ms`. */\nexport function startOfDayIn(ms: number, zone: Zone): number {\n return dayToInstant(instantToDay(ms, zone), zone);\n}\n\n/** Same wall time `n` calendar days later (or earlier). */\nexport function addDaysIn(ms: number, n: number, zone: Zone): number {\n const w = instantToWall(ms, zone);\n const t = new Date(Date.UTC(w.y, w.m - 1, w.d + n));\n return wallToInstant({ y: t.getUTCFullYear(), m: t.getUTCMonth() + 1, d: t.getUTCDate(), h: w.h, min: w.min, s: w.s }, zone);\n}\n\n/** Calendar days from `a` to `b` in `zone` (whole days, however long each one is). */\nexport function daysBetween(a: number, b: number, zone: Zone): number {\n return instantToDay(b, zone) - instantToDay(a, zone);\n}\n\n/** Real length of a calendar day in ms: 24 h, or 23 / 25 h on DST changes. */\nexport function dayLength(day: number, zone: Zone): number {\n return dayToInstant(day + 1, zone) - dayToInstant(day, zone);\n}\n\n/** A wall time `hours` after the day's midnight, e.g. 9.5 → 09:30. */\nexport function atHour(day: number, hours: number, zone: Zone): number {\n const t = new Date(day * DAY);\n const whole = Math.floor(hours);\n const min = Math.round((hours - whole) * 60);\n return wallToInstant({ y: t.getUTCFullYear(), m: t.getUTCMonth() + 1, d: t.getUTCDate(), h: whole, min }, zone);\n}\n\n/* ---------- Snapping ---------- */\n\nexport type SnapDirection = 'nearest' | 'floor' | 'ceil';\n\n/**\n * Snap to a multiple of `minutes` on the **wall clock** (15-minute marks are :00/:15/:30/:45\n * local, including in +5:30 or +9:30 zones). Across a DST gap the result follows `wallToInstant`.\n */\nexport function snap(ms: number, minutes: number, zone: Zone, dir: SnapDirection = 'nearest'): number {\n if (minutes <= 0) return ms;\n const w = instantToWall(ms, zone);\n const ofDay = w.h * 60 + w.min + w.s / 60 + (ms % 1000) / 60_000;\n const steps = ofDay / minutes;\n const n = dir === 'floor' ? Math.floor(steps) : dir === 'ceil' ? Math.ceil(steps) : Math.round(steps);\n const target = n * minutes;\n const t = new Date(Date.UTC(w.y, w.m - 1, w.d, 0, target));\n return wallToInstant({ y: t.getUTCFullYear(), m: t.getUTCMonth() + 1, d: t.getUTCDate(), h: t.getUTCHours(), min: t.getUTCMinutes() }, zone);\n}\n\n/* ---------- Labels ---------- */\n\n/** \"09:30\" */\nexport function timeLabel(ms: number, zone: Zone): string {\n const w = instantToWall(ms, zone);\n return `${String(w.h).padStart(2, '0')}:${String(w.min).padStart(2, '0')}`;\n}\n\n/** \"Mon 26 Oct\" */\nexport function dayLabelIn(ms: number, zone: Zone): string {\n const day = instantToDay(ms, zone);\n const w = instantToWall(ms, zone);\n return `${WEEKDAYS[weekday(day)]} ${w.d} ${MONTHS[w.m - 1]}`;\n}\n\n/* ---------- Instant scale ---------- */\n\nexport interface InstantTick {\n ms: number;\n x: number;\n width: number;\n label: string;\n}\n\nexport interface InstantDayTick extends InstantTick {\n day: number;\n /** Real hours in this day (23, 24 or 25). */\n hours: number;\n working: boolean;\n}\n\nexport interface InstantScale {\n zone: Zone;\n /** First visible instant (start of the first day). */\n start: number;\n /** Exclusive end (start of the day after the last). */\n end: number;\n startDay: number;\n /** Exclusive. */\n endDay: number;\n pxPerHour: number;\n width: number;\n /** Instant → px. Instants in hidden (non-working) time map to the next visible edge. */\n x(ms: number): number;\n /** px → instant. */\n msAt(px: number): number;\n days: InstantDayTick[];\n hours: InstantTick[];\n /** Non-working time to tint (before/after hours, and whole non-working days). Empty when hidden. */\n nonWorking: { x: number; width: number }[];\n}\n\nexport interface InstantScaleInput {\n /** Any instants inside the first and last day to show. */\n from: number;\n to: number;\n zone: Zone;\n pxPerHour: number;\n /** Working hours as decimal wall hours, e.g. 9 and 17.5. */\n workStart?: number;\n workEnd?: number;\n /** Mon–Fri (default) or every day. */\n workingWeek?: 'mon-fri' | 'all';\n /** Show only working hours (and only working days with `mon-fri`). */\n hideNonWorking?: boolean;\n /** Hour tick spacing (default 1). */\n hourStep?: number;\n}\n\ninterface Segment {\n start: number;\n end: number;\n x0: number;\n}\n\nexport function createInstantScale(input: InstantScaleInput): InstantScale {\n const { zone, pxPerHour, workStart = 9, workEnd = 17, workingWeek = 'mon-fri', hideNonWorking = false, hourStep = 1 } = input;\n const startDay = instantToDay(Math.min(input.from, input.to), zone);\n const endDay = instantToDay(Math.max(input.from, input.to), zone) + 1;\n const pxPerMs = pxPerHour / HOUR;\n const isWorkingDay = (day: number) => workingWeek === 'all' || weekday(day) < 5;\n\n // Visible stretches of time, laid end to end.\n const segments: Segment[] = [];\n let x0 = 0;\n const days: InstantDayTick[] = [];\n const nonWorking: { x: number; width: number }[] = [];\n for (let day = startDay; day < endDay; day++) {\n const dayStart = dayToInstant(day, zone);\n const dayEnd = dayToInstant(day + 1, zone);\n const working = isWorkingDay(day);\n const ws = Math.max(dayStart, Math.min(dayEnd, atHour(day, workStart, zone)));\n const we = Math.max(ws, Math.min(dayEnd, atHour(day, workEnd, zone)));\n let segStart = dayStart;\n let segEnd = dayEnd;\n if (hideNonWorking) {\n if (!working) continue;\n segStart = ws;\n segEnd = we;\n }\n const width = (segEnd - segStart) * pxPerMs;\n segments.push({ start: segStart, end: segEnd, x0 });\n days.push({ day, ms: segStart, x: x0, width, label: dayLabelIn(dayStart, zone), hours: (dayEnd - dayStart) / HOUR, working });\n if (!hideNonWorking) {\n if (!working) nonWorking.push({ x: x0, width });\n else {\n if (ws > dayStart) nonWorking.push({ x: x0, width: (ws - dayStart) * pxPerMs });\n if (we < dayEnd) nonWorking.push({ x: x0 + (we - dayStart) * pxPerMs, width: (dayEnd - we) * pxPerMs });\n }\n }\n x0 += width;\n }\n const width = x0;\n const start = segments[0]?.start ?? dayToInstant(startDay, zone);\n const end = segments[segments.length - 1]?.end ?? dayToInstant(endDay, zone);\n\n const x = (ms: number): number => {\n if (segments.length === 0) return 0;\n let lo = 0;\n let hi = segments.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n if (segments[mid]!.start <= ms) lo = mid;\n else hi = mid - 1;\n }\n const s = segments[lo]!;\n if (ms <= s.start) return s.x0;\n if (ms >= s.end) return s.x0 + (s.end - s.start) * pxPerMs; // hidden gap → next visible edge\n return s.x0 + (ms - s.start) * pxPerMs;\n };\n\n const msAt = (px: number): number => {\n if (segments.length === 0) return start;\n let lo = 0;\n let hi = segments.length - 1;\n while (lo < hi) {\n const mid = (lo + hi + 1) >> 1;\n if (segments[mid]!.x0 <= px) lo = mid;\n else hi = mid - 1;\n }\n const s = segments[lo]!;\n return Math.min(s.end, Math.max(s.start, s.start + (px - s.x0) / pxPerMs));\n };\n\n // Hour ticks walk real instants, so a 25-hour day shows 01:00 twice and a 23-hour day skips 02:00.\n const hours: InstantTick[] = [];\n for (const s of segments) {\n let t = snap(s.start, hourStep * 60, zone, 'ceil');\n while (t < s.end) {\n const next = Math.min(s.end, t + hourStep * HOUR);\n hours.push({ ms: t, x: x(t), width: (next - t) * pxPerMs, label: timeLabel(t, zone) });\n t += hourStep * HOUR;\n }\n }\n\n return { zone, start, end, startDay, endDay, pxPerHour, width, x, msAt, days, hours, nonWorking };\n}\n","import { atHour, dayToInstant } from './instant';\nimport type { Zone } from './zone';\n\n/* Working hours and capacity for the Scheduler. Hours are decimal wall hours (9.5 = 09:30). */\n\nconst HOUR = 3_600_000;\n\n/** \"09:30\" or 9.5 → 9.5. Invalid input → NaN. */\nexport function parseHour(value: string | number): number {\n if (typeof value === 'number') return value >= 0 && value <= 24 ? value : NaN;\n const m = /^\\s*(\\d{1,2})(?::(\\d{2}))?\\s*$/.exec(value);\n if (!m) return NaN;\n const h = Number(m[1]);\n const min = m[2] ? Number(m[2]) : 0;\n return h <= 24 && min < 60 && (h < 24 || min === 0) ? h + min / 60 : NaN;\n}\n\n/** The working window of a calendar day in `zone`, as instants. End never precedes start. */\nexport function workingWindow(day: number, start: string | number, end: string | number, zone: Zone): { start: number; end: number } {\n const s = atHour(day, parseHour(start), zone);\n const e = atHour(day, parseHour(end), zone);\n return { start: s, end: Math.max(s, e) };\n}\n\nexport interface TimedItem {\n start: number;\n end: number;\n /** Unavailable blocks (leave, maintenance) count against capacity like bookings. */\n kind?: 'booking' | 'unavailable';\n}\n\n/**\n * Hours booked on a calendar day: each item clipped to the day's real start and end (so an\n * overnight booking splits across days, and a DST night counts its real 23 or 25 hours).\n * Overlapping items both count — that is what over-capacity means.\n */\nexport function bookedHours(items: readonly TimedItem[], day: number, zone: Zone): number {\n const dayStart = dayToInstant(day, zone);\n const dayEnd = dayToInstant(day + 1, zone);\n let ms = 0;\n for (const it of items) {\n const s = Math.max(it.start, dayStart);\n const e = Math.min(it.end, dayEnd);\n if (e > s) ms += e - s;\n }\n return ms / HOUR;\n}\n\nexport type UtilisationLevel = 'none' | 'under' | 'near' | 'over';\n\n/** Booked ÷ capacity, with a level for colouring: under < 85% ≤ near ≤ 100% < over. */\nexport function utilisation(booked: number, capacity: number): { ratio: number; level: UtilisationLevel } {\n if (!(capacity > 0)) return { ratio: booked > 0 ? Infinity : 0, level: booked > 0 ? 'over' : 'none' };\n const ratio = booked / capacity;\n const level: UtilisationLevel = booked <= 0 ? 'none' : ratio > 1 + 1e-9 ? 'over' : ratio >= 0.85 ? 'near' : 'under';\n return { ratio, level };\n}\n","/**\r\n * Number formatting on Intl.NumberFormat, so every locale and currency is right.\r\n * Compact notation follows the locale: en \"4.2M\", de \"4,2 Mio.\", ja \"420万\".\r\n * Shared by Charts, Scheduler and Driver Tree (moved from @ninthforge/charts).\r\n */\r\n\r\nexport type NumberStyle = 'auto' | 'number' | 'currency' | 'percent' | 'compact';\r\n\r\nexport interface NumberFormatOptions {\r\n style?: NumberStyle;\r\n /** ISO 4217 code, e.g. AUD. Used by `currency`, and by `compact`/`auto` when set. */\r\n currency?: string;\r\n /** Fixed decimal places. Leave out to choose automatically. */\r\n decimals?: number;\r\n /** BCP-47 tag. Leave out for the runtime default. */\r\n locale?: string;\r\n /**\r\n * When to show a sign: `auto` (negatives only, the default), `always` (+5, 0 shows +0),\r\n * `exceptZero` (+5, 0, −5: for changes and variances) or `never`.\r\n */\r\n signDisplay?: SignDisplay;\r\n /**\r\n * A unit for `number` and `compact` (and `auto` without a currency): an Intl unit\r\n * identifier such as `hour`, `day`, `kilometer`, `kilogram` or `kilometer-per-hour`.\r\n * Shown short (\"8 hr\", \"1.2K km\") in the locale's own words. Ignored for currency and\r\n * percent; an identifier Intl doesn't know is dropped rather than thrown.\r\n */\r\n unit?: string;\r\n}\r\n\r\nexport type SignDisplay = 'auto' | 'always' | 'exceptZero' | 'never';\r\n\r\n/** Decimal places a step needs (0.25 → 2, 5 → 0). */\r\nexport function stepDecimals(step: number): number {\r\n if (!(step > 0) || !Number.isFinite(step)) return 0;\r\n let d = 0;\r\n while (d < 12 && Math.abs(Math.round(step * 10 ** d) - step * 10 ** d) > 1e-9 * 10 ** d) d++;\r\n return d;\r\n}\r\n\r\n/** Values at or above this (absolute) use compact notation in `auto`. */\r\nexport const AUTO_COMPACT_FROM = 10_000;\r\n\r\nconst cache = new Map<string, Intl.NumberFormat>();\r\n\r\nfunction nf(locale: string | undefined, o: Intl.NumberFormatOptions): Intl.NumberFormat {\r\n const key = (locale ?? '') + JSON.stringify(o);\r\n let f = cache.get(key);\r\n if (!f) {\r\n try {\r\n f = new Intl.NumberFormat(locale, o);\r\n } catch {\r\n // Unknown locale or currency code: fall back rather than throw inside a render.\r\n const safe: Intl.NumberFormatOptions = { ...o };\r\n if (safe.style === 'currency' || safe.style === 'unit') {\r\n delete safe.style;\r\n delete safe.currency;\r\n delete safe.unit;\r\n delete safe.unitDisplay;\r\n }\r\n try {\r\n f = new Intl.NumberFormat(locale, safe);\r\n } catch {\r\n f = new Intl.NumberFormat(undefined, safe);\r\n }\r\n }\r\n cache.set(key, f);\r\n }\r\n return f;\r\n}\r\n\r\ntype Style = Exclude<NumberStyle, 'auto'>;\r\n\r\nfunction intlOptions(style: Style, opts: NumberFormatOptions, min: number | undefined, max: number | undefined): Intl.NumberFormatOptions {\r\n const { currency, unit, signDisplay } = opts;\r\n const o: Intl.NumberFormatOptions = {};\r\n if (currency && (style === 'currency' || style === 'compact')) {\r\n o.style = 'currency';\r\n o.currency = currency.toUpperCase();\r\n } else if (unit && (style === 'number' || style === 'compact')) {\r\n o.style = 'unit';\r\n o.unit = unit;\r\n o.unitDisplay = 'short';\r\n }\r\n if (signDisplay && signDisplay !== 'auto') o.signDisplay = signDisplay;\r\n if (style === 'percent') o.style = 'percent';\r\n if (style === 'compact') o.notation = 'compact';\r\n if (max !== undefined) o.maximumFractionDigits = max;\r\n if (min !== undefined) o.minimumFractionDigits = Math.min(min, max ?? min);\r\n // Compact currency would otherwise inherit the currency's minimum digits on some ICU versions\r\n // (Node 22 printed \"$450.0K\" where Node 24 prints \"$450K\").\r\n else if (style === 'compact') o.minimumFractionDigits = 0;\r\n return o;\r\n}\r\n\r\n/** Resolves `auto` from the largest magnitude on show: compact from 10,000, else number or currency. */\r\nexport function resolveStyle(style: NumberStyle | undefined, magnitude: number, currency?: string): Style {\r\n if (style && style !== 'auto') return style;\r\n if (Math.abs(magnitude) >= AUTO_COMPACT_FROM) return 'compact';\r\n return currency ? 'currency' : 'number';\r\n}\r\n\r\n/**\r\n * Formats one value for a tooltip, label or table. Non-finite values give \"\".\r\n * Percent takes a fraction: 0.125 → \"12.5%\".\r\n */\r\nexport function formatNumber(v: number, o: NumberFormatOptions = {}): string {\r\n if (!Number.isFinite(v)) return '';\r\n const style = resolveStyle(o.style, v, o.currency);\r\n let max = o.decimals;\r\n const min = o.decimals;\r\n if (max === undefined) {\r\n if (style === 'compact' || style === 'percent') max = 1;\r\n else if (style === 'number') max = Math.abs(v) >= 100 ? 0 : 2;\r\n // currency: leave undefined so the currency's own digits apply (2 for AUD, 0 for JPY).\r\n }\r\n return nf(o.locale, intlOptions(style, o, min, max)).format(v);\r\n}\r\n\r\n/**\r\n * A formatter for axis ticks. Decimals come from the tick step, and it adds\r\n * decimals until no two ticks share a label (so an axis never says 1K, 1K, 2K).\r\n */\r\nexport function tickFormatter(ticks: number[], o: NumberFormatOptions = {}): (v: number) => string {\r\n const finite = ticks.filter(Number.isFinite);\r\n const magnitude = finite.reduce((m, v) => Math.max(m, Math.abs(v)), 0);\r\n const style = resolveStyle(o.style, magnitude, o.currency);\r\n const step = finite.length > 1 ? Math.abs((finite[1] as number) - (finite[0] as number)) : 0;\r\n const wrap = (f: Intl.NumberFormat) => (v: number) => (Number.isFinite(v) ? f.format(v) : '');\r\n // Others keep a fixed width (0.0, 0.5, 1.0).\r\n const make = (d: number): ((v: number) => string) => wrap(nf(o.locale, intlOptions(style, o, d, d)));\r\n if (o.decimals !== undefined) return make(o.decimals);\r\n const unique = (fmt: (v: number) => string): boolean => new Set(finite.map(fmt)).size === finite.length;\r\n if (style === 'compact') {\r\n // Compact units differ by locale (K, Mio., 万), so precision is in significant\r\n // digits: enough to show every tick exactly (2.5K, 10.2K, 1.25万), no trailing zeros.\r\n const digits = step > 0 && magnitude > 0 ? Math.floor(Math.log10(magnitude)) - Math.floor(Math.log10(step)) + 1 : 1;\r\n for (let sig = Math.max(1, Math.min(21, digits)); sig <= 21; sig++) {\r\n const fmt = wrap(nf(o.locale, { ...intlOptions(style, o, undefined, undefined), maximumSignificantDigits: sig }));\r\n if (unique(fmt)) return fmt;\r\n }\r\n }\r\n const start = style === 'percent' ? Math.max(0, stepDecimals(step) - 2) : stepDecimals(step);\r\n for (let d = start; d <= 6; d++) {\r\n const fmt = make(d);\r\n if (unique(fmt)) return fmt;\r\n }\r\n return make(6);\r\n}\r\n","/* Print and image export helpers shared by the components. No network: printing happens in a\n same-origin about:blank iframe and images download through a local blob URL. */\n\nexport function escapeHtml(s: string): string {\n return s.replace(/[&<>\"']/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' })[c]!);\n}\n\n/** \"Capital works program\" + \"2026-10-21\" → \"capital-works-program-2026-10-21.png\" */\nexport function exportFileName(title: string, isoDay: string, ext = 'png', fallback = 'plan'): string {\n const slug = title.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || fallback;\n return `${slug}-${isoDay}.${ext}`;\n}\n\n/** Browsers cap canvases around 16k px a side (and ~268M px in area); exports stay under both. */\nexport const MAX_CANVAS_SIDE = 16_000;\nexport const MAX_CANVAS_AREA = 120_000_000;\n\n/** Device-pixel ratio for a `width` × `height` export: `preferred`, lowered to fit the caps; null below 0.5. */\nexport function exportRatio(width: number, height: number, preferred = 2): number | null {\n const ratio = Math.min(preferred, MAX_CANVAS_SIDE / width, MAX_CANVAS_SIDE / height, Math.sqrt(MAX_CANVAS_AREA / (width * height)));\n return ratio >= 0.5 ? ratio : null;\n}\n\n/** Any CSS colour → a canvas-safe string (resolves names, rgb() and color-mix() where supported). */\nexport function canvasColor(ctx: CanvasRenderingContext2D, color: string | undefined, fallback: string): string {\n ctx.fillStyle = fallback;\n if (color) ctx.fillStyle = color;\n return String(ctx.fillStyle);\n}\n\n/** Save a canvas as a PNG through a local blob URL. */\nexport function downloadCanvas(canvas: HTMLCanvasElement, fileName: string, doc: Document = document): void {\n canvas.toBlob((blob) => {\n if (!blob) return;\n const url = URL.createObjectURL(blob);\n const a = doc.createElement('a');\n a.href = url;\n a.download = fileName;\n a.style.display = 'none';\n doc.body.appendChild(a);\n a.click();\n a.remove();\n setTimeout(() => URL.revokeObjectURL(url), 10_000);\n }, 'image/png');\n}\n\n/**\n * Print `html` from a hidden same-origin iframe, so the host page (the whole Power Apps shell)\n * is never printed or re-laid out. The iframe is removed after the print dialog closes, or after\n * a minute for browsers that never fire afterprint.\n */\nexport function printDocument(html: string, doc: Document = document): void {\n const frame = doc.createElement('iframe');\n frame.setAttribute('aria-hidden', 'true');\n frame.setAttribute('tabindex', '-1');\n frame.style.cssText = 'position:fixed;right:0;bottom:0;width:0;height:0;border:0;visibility:hidden';\n doc.body.appendChild(frame);\n const win = frame.contentWindow;\n const fdoc = frame.contentDocument;\n if (!win || !fdoc) {\n frame.remove();\n return;\n }\n fdoc.open();\n fdoc.write(html);\n fdoc.close();\n let done = false;\n const cleanup = () => {\n if (done) return;\n done = true;\n frame.remove();\n };\n win.addEventListener('afterprint', () => setTimeout(cleanup, 0));\n setTimeout(cleanup, 60_000);\n // Let the iframe lay out before the dialog snapshots it.\n setTimeout(() => {\n win.focus();\n win.print();\n }, 50);\n}\n","import { dayOf, MONTHS, parts } from './date';\nimport type { Tick } from './scale';\n\n/*\n * The Timeline's axis: months, quarters or half-years, with fiscal-year labels. Separate from\n * createTimeScale (the Gantt's week/month/quarter), so the Gantt's zooms are untouched.\n * Day numbers in UTC, like the rest of the date maths.\n */\n\nexport type RoadmapZoom = 'month' | 'quarter' | 'half';\n\nexport const ROADMAP_ZOOMS: readonly RoadmapZoom[] = ['month', 'quarter', 'half'];\n\n/** Pixels per day: about 120 px a month, 140 px a quarter and 150 px a half-year. */\nexport const ROADMAP_DAY_WIDTH: Record<RoadmapZoom, number> = { month: 4, quarter: 1.5, half: 0.8 };\n\nexport interface RoadmapScaleInput {\n /** First and last day that must be visible (inclusive). */\n from: number;\n to: number;\n zoom: RoadmapZoom;\n /** First month of the fiscal year, 1–12 (default 1: calendar years). */\n fiscalYearStart?: number;\n /** Extend the end until the scale is at least this wide. */\n minWidth?: number;\n}\n\nexport interface RoadmapScale {\n zoom: RoadmapZoom;\n fiscalYearStart: number;\n startDay: number;\n /** Exclusive. */\n endDay: number;\n dayWidth: number;\n width: number;\n x(day: number): number;\n dayAt(px: number): number;\n /** Years (or fiscal years) on top. */\n major: Tick[];\n /** Months, quarters or halves below. */\n minor: Tick[];\n /** Quarter zoom only: the months under each quarter (empty at other zooms). */\n months: Tick[];\n}\n\nconst clampMonth = (m: number | undefined) => (Number.isInteger(m) && m! >= 1 && m! <= 12 ? m! : 1);\n\n/** Months since the fiscal year began (0–11) for a 0-based calendar month. */\nconst fyMonth = (month: number, fyStart: number) => (((month - (fyStart - 1)) % 12) + 12) % 12;\n\n/**\n * The fiscal year a day falls in, named by the year it ends in (a July 2026 start is FY27).\n * With a January start it is the calendar year.\n */\nexport function fiscalYear(day: number, fiscalYearStart = 1): number {\n const fs = clampMonth(fiscalYearStart);\n const p = parts(day);\n return fs === 1 || p.month < fs - 1 ? p.year : p.year + 1;\n}\n\n/** First day of the period (`months` long: 1, 3 or 6) that `day` is in, aligned to the fiscal year. */\nexport function periodStart(day: number, months: 1 | 3 | 6, fiscalYearStart = 1): number {\n const fs = clampMonth(fiscalYearStart);\n const p = parts(day);\n const into = fyMonth(p.month, fs) % months;\n return dayOf(p.year, p.month - into, 1);\n}\n\n/** The first day `n` months after the start of `day`'s month. */\nfunction addMonthsFrom(day: number, n: number): number {\n const p = parts(day);\n return dayOf(p.year, p.month + n, 1);\n}\n\n/** \"Q1 FY27\" / \"Q1 2027\", \"H2 FY27\" / \"H2 2027\", \"FY27\" / \"2027\". */\nexport function periodLabel(day: number, kind: 'quarter' | 'half' | 'year', fiscalYearStart = 1): string {\n const fs = clampMonth(fiscalYearStart);\n const fy = fiscalYear(day, fs);\n const year = fs === 1 ? String(fy) : `FY${String(fy % 100).padStart(2, '0')}`;\n if (kind === 'year') return year;\n const m = fyMonth(parts(day).month, fs);\n return kind === 'quarter' ? `Q${Math.floor(m / 3) + 1} ${year}` : `H${m < 6 ? 1 : 2} ${year}`;\n}\n\nexport function createRoadmapScale({ from, to, zoom, fiscalYearStart, minWidth = 0 }: RoadmapScaleInput): RoadmapScale {\n const fs = clampMonth(fiscalYearStart);\n const step = zoom === 'month' ? 1 : zoom === 'quarter' ? 3 : 6;\n const startDay = periodStart(Math.min(from, to), step, fs);\n let endDay = addMonthsFrom(periodStart(Math.max(from, to), step, fs), step);\n const dayWidth = ROADMAP_DAY_WIDTH[zoom];\n for (let guard = 0; (endDay - startDay) * dayWidth < minWidth && guard < 240; guard++) endDay = addMonthsFrom(endDay, step);\n const x = (day: number) => Math.round((day - startDay) * dayWidth * 10) / 10;\n\n const minor: Tick[] = [];\n for (let d = startDay; d < endDay; d = addMonthsFrom(d, step)) {\n const next = addMonthsFrom(d, step);\n const label = zoom === 'month' ? (MONTHS[parts(d).month] ?? '') : periodLabel(d, zoom, fs);\n minor.push({ day: d, x: x(d), width: x(next) - x(d), label });\n }\n const months: Tick[] = [];\n if (zoom === 'quarter') {\n for (let d = startDay; d < endDay; d = addMonthsFrom(d, 1)) {\n const next = addMonthsFrom(d, 1);\n months.push({ day: d, x: x(d), width: x(next) - x(d), label: MONTHS[parts(d).month] ?? '' });\n }\n }\n const major: Tick[] = [];\n for (let yStart = yearStart(startDay, fs); yStart < endDay; ) {\n const yEnd = addMonthsFrom(yStart, 12);\n const left = Math.max(yStart, startDay);\n const right = Math.min(yEnd, endDay);\n major.push({ day: left, x: x(left), width: x(right) - x(left), label: periodLabel(yStart, 'year', fs) });\n yStart = yEnd;\n }\n\n return {\n zoom,\n fiscalYearStart: fs,\n startDay,\n endDay,\n dayWidth,\n width: x(endDay),\n x,\n dayAt: (px: number) => startDay + Math.floor(px / dayWidth),\n major,\n minor,\n months\n };\n}\n\n/** First day of the (fiscal) year `day` is in. */\nexport function yearStart(day: number, fiscalYearStart = 1): number {\n const fs = clampMonth(fiscalYearStart);\n const p = parts(day);\n const y = p.month >= fs - 1 ? p.year : p.year - 1;\n return dayOf(y, fs - 1, 1);\n}\n","/**\n * Host hooks shared by the Plan components (Gantt, Timeline, capacity heatmap) when a host such\n * as a Power BI visual wraps them. Same shapes as `@ninthforge/charts`' hooks. All optional:\n * Power Apps passes none of them.\n */\n\n/** Passed with a selection: `multi` is true when Ctrl or Cmd was held (add to the selection). */\nexport interface SelectInfo {\n multi: boolean;\n}\n\n/** Where the pointer (or, from the keyboard, the item's anchor) is, in client coordinates. */\nexport interface PointerInfo extends SelectInfo {\n clientX: number;\n clientY: number;\n}\n\n/** A host formatter, e.g. one built from a Power BI format string. `C` says what is formatted. */\nexport type HostFormat<C> = (value: number, ctx: C) => string;\n\n/** Separates the parts of a composite key (category and series, resource and week). */\nexport const KEY_SEPARATOR = '\\u001f';\n\n/** True when the event asks to add to the selection (Ctrl on Windows, Cmd on a Mac). */\nexport function isMultiSelect(e: { ctrlKey?: boolean; metaKey?: boolean }): boolean {\n return !!(e.ctrlKey || e.metaKey);\n}\n\n/** Opacity for items outside `highlighted` (the Charts' DIM). */\nexport const HOST_DIM = 0.3;\n\n/**\n * Whether an item stays at full strength. With no highlight (undefined or empty), every item\n * does; otherwise only the listed ids.\n */\nexport function isHighlighted(id: string, highlighted?: readonly string[] | ReadonlySet<string>): boolean {\n if (!highlighted) return true;\n if (isSet(highlighted)) return highlighted.size === 0 || highlighted.has(id);\n return highlighted.length === 0 || highlighted.indexOf(id) !== -1;\n}\n\nfunction isSet(v: readonly string[] | ReadonlySet<string>): v is ReadonlySet<string> {\n return typeof (v as ReadonlySet<string>).has === 'function' && !Array.isArray(v);\n}\n\n/** The keyboard way to open a context menu: the Menu key or Shift+F10. */\nexport function isContextMenuKey(e: { key: string; shiftKey?: boolean }): boolean {\n return e.key === 'ContextMenu' || (e.key === 'F10' && !!e.shiftKey);\n}\n","/**\r\n * Pointer maths that ignores CSS scaling around a component.\r\n *\r\n * Layout maths work in the component's own CSS pixels (`offsetWidth`, `clientWidth`). Pointer\r\n * events and `getBoundingClientRect()` report screen pixels, which differ when something around\r\n * the component scales it: Power BI's focus mode and report zoom, a canvas app's scale-to-fit,\r\n * a wrapper's `transform: scale()`. Browser zoom scales both alike, so it needs nothing. Divide\r\n * pointer movement by `cssScale()`, and turn client points into local ones with `localPoint()`.\r\n */\r\n\r\n/** The parts of an element these helpers read; tests pass a fake. */\r\nexport interface ScalableElement {\r\n offsetWidth: number;\r\n offsetHeight: number;\r\n getBoundingClientRect(): { left: number; top: number; width: number; height: number };\r\n}\r\n\r\nexport interface CssScale {\r\n x: number;\r\n y: number;\r\n}\r\n\r\nconst ratio = (drawn: number, layout: number): number =>\r\n layout > 0 && drawn > 0 && Number.isFinite(drawn / layout) ? drawn / layout : 1;\r\n\r\n/**\r\n * How much the element is drawn bigger (>1) or smaller (<1) than its layout size, from\r\n * transforms around it. 1 when unknown (no element, not laid out, or server render).\r\n */\r\nexport function cssScale(el: ScalableElement | null | undefined): CssScale {\r\n if (!el) return { x: 1, y: 1 };\r\n const r = el.getBoundingClientRect();\r\n return { x: ratio(r.width, el.offsetWidth), y: ratio(r.height, el.offsetHeight) };\r\n}\r\n\r\n/**\r\n * A client point (a pointer event's `clientX`/`clientY`) as an offset from the element's\r\n * top-left corner, in the element's own CSS pixels. Add `scrollLeft`/`scrollTop` yourself for\r\n * content coordinates.\r\n */\r\nexport function localPoint(el: ScalableElement | null | undefined, clientX: number, clientY: number): { x: number; y: number } {\r\n if (!el) return { x: 0, y: 0 };\r\n const r = el.getBoundingClientRect();\r\n return { x: (clientX - r.left) / ratio(r.width, el.offsetWidth), y: (clientY - r.top) / ratio(r.height, el.offsetHeight) };\r\n}\r\n","/**\r\n * Text size: type roles and one scale for every component (the same roles in Power Apps and Power BI).\r\n *\r\n * Components size text by role, never by a pixel number, so one setting (\"Text size\", 75–200 %)\r\n * makes every label, value and title bigger or smaller together. The same numbers reach CSS\r\n * (`--nf-text-<role>`) and layout maths (`textSize(role, scale)`), so label fitting and row\r\n * heights match what the browser draws.\r\n */\r\n\r\n/** What the text is for, not how big it is. */\r\nexport type TextRole = 'caption' | 'body' | 'label' | 'value' | 'callout' | 'header' | 'title' | 'hero';\r\n\r\n/** Every role, in the spec's order. */\r\nexport const TEXT_ROLES: readonly TextRole[] = ['caption', 'body', 'label', 'value', 'callout', 'header', 'title', 'hero'];\r\n\r\n/**\r\n * Sizes at 100 %, in CSS pixels.\r\n * - `caption`: legends, footnotes, axis titles, \"+N\"\r\n * - `body`: grid and table cells, task and card text, node and person names (dense UI text)\r\n * - `label`: chart axis ticks and category labels\r\n * - `value`: data labels, values in cards and cells\r\n * - `callout`: Δ callouts, annotations, badges\r\n * - `header`: table, column, lane and group headers (semibold)\r\n * - `title`: in-component titles and section headings\r\n * - `hero`: the big number on KPI cards and gauges\r\n */\r\nexport const TEXT_SIZES: Readonly<Record<TextRole, number>> = {\r\n caption: 11,\r\n body: 13,\r\n label: 12,\r\n value: 12,\r\n callout: 12,\r\n header: 13,\r\n title: 16,\r\n hero: 28\r\n};\r\n\r\n/** Per-role sizes at 100 %, in CSS pixels, that replace the defaults. The scale still applies. */\r\nexport type TextSizes = Partial<Record<TextRole, number>>;\r\n\r\n/** The smallest and largest text scale (75 % and 200 %). */\r\nexport const TEXT_SCALE_MIN = 0.75;\r\nexport const TEXT_SCALE_MAX = 2;\r\n\r\n/** A usable scale: 0.75–2, and 1 for anything that isn't a positive number. */\r\nexport function clampTextScale(scale: number | null | undefined): number {\r\n if (typeof scale !== 'number' || !Number.isFinite(scale) || scale <= 0) return 1;\r\n return Math.min(TEXT_SCALE_MAX, Math.max(TEXT_SCALE_MIN, scale));\r\n}\r\n\r\n/**\r\n * A scale from a percent, as the PCF \"Text size\" input and the Power BI format pane give it\r\n * (75–200, default 100). Empty, zero or garbage gives 1.\r\n */\r\nexport function textScaleFromPercent(percent: number | string | null | undefined): number {\r\n const n = typeof percent === 'string' ? Number(percent.trim().replace(/%$/, '')) : percent;\r\n if (typeof n !== 'number' || !Number.isFinite(n) || n <= 0) return 1;\r\n return clampTextScale(n / 100);\r\n}\r\n\r\n/**\r\n * Per-role sizes from a maker's JSON (the PCF's optional override input), e.g.\r\n * `{\"label\": 13, \"title\": 18}`. Unknown roles and sizes outside 6–96 px are ignored, and bad\r\n * JSON gives `{}`: a typo never breaks the control.\r\n */\r\nexport function parseTextSizes(json: string | null | undefined): TextSizes {\r\n if (!json || !json.trim()) return {};\r\n let raw: unknown;\r\n try {\r\n raw = JSON.parse(json);\r\n } catch {\r\n return {};\r\n }\r\n const out: TextSizes = {};\r\n if (!raw || typeof raw !== 'object' || Array.isArray(raw)) return out;\r\n for (const role of TEXT_ROLES) {\r\n const v = (raw as Record<string, unknown>)[role];\r\n if (typeof v === 'number' && Number.isFinite(v) && v >= 6 && v <= 96) out[role] = v;\r\n }\r\n return out;\r\n}\r\n\r\n/** Rounds to the nearest half pixel, which browsers draw crisply. */\r\nconst half = (px: number): number => Math.round(px * 2) / 2;\r\n\r\n/** A role's size in CSS pixels at a scale (1 = 100 %), for layout maths and canvas text. */\r\nexport function textSize(role: TextRole, scale = 1, sizes?: TextSizes): number {\r\n const base = sizes?.[role] ?? TEXT_SIZES[role];\r\n return half(base * clampTextScale(scale));\r\n}\r\n\r\n/** The `--nf-text-<role>` CSS variables for a scale, e.g. `{ '--nf-text-label': '12px' }`. */\r\nexport function textVars(scale = 1, sizes?: TextSizes): Record<string, string> {\r\n const out: Record<string, string> = {};\r\n for (const role of TEXT_ROLES) out[`--nf-text-${role}`] = `${textSize(role, scale, sizes)}px`;\r\n return out;\r\n}\r\n\r\n/** `var(--nf-text-<role>)`, for inline styles and CSS strings, with the 100 % size as fallback. */\r\nexport function textVar(role: TextRole): string {\r\n return `var(--nf-text-${role},${TEXT_SIZES[role]}px)`;\r\n}\r\n\r\n/**\r\n * A text-dependent length (row height, card height, padding) at a scale. At 100 % it is the\r\n * design value unchanged; at any other scale it grows or shrinks with the text and rounds up\r\n * to the 8 px grid, so rows stay aligned and text never clips.\r\n */\r\nexport function scaleWithText(px: number, scale = 1): number {\r\n const s = clampTextScale(scale);\r\n if (s === 1) return px;\r\n return Math.ceil((px * s) / 8) * 8;\r\n}\r\n","/**\r\n * Ninth Forge design tokens.\r\n * Brand colours carry the website and marketing; component themes are what\r\n * customers see inside their apps and can override with CSS variables.\r\n */\r\n\r\nexport const brand = {\r\n ink: '#0E1116',\r\n ember: '#D9480F',\r\n paper: '#F6F6F3',\r\n line: '#E4E5E9',\r\n muted: '#5D6472',\r\n cobalt: '#2E54FF'\r\n} as const;\r\n\r\nexport const semantic = {\r\n milestone: '#E8930C',\r\n positive: '#138A52',\r\n negative: '#D93F2B'\r\n} as const;\r\n\r\nexport const font = {\r\n sans: \"'Geist', 'Segoe UI', system-ui, -apple-system, sans-serif\",\r\n mono: \"'Geist Mono', 'Cascadia Mono', ui-monospace, monospace\",\r\n /** Components inherit the host app's font unless a theme says otherwise. */\r\n inherit: 'inherit'\r\n} as const;\r\n\r\nexport const radius = { bar: 6, control: 9, card: 16 } as const;\r\nexport const space = (n: number): number => n * 8;\r\n\r\nexport const motion = {\r\n fast: '150ms',\r\n base: '220ms',\r\n ease: 'cubic-bezier(0.2, 0.8, 0.2, 1)'\r\n} as const;\r\n\r\n/** A component theme. Every key becomes a `--nf-<kebab-key>` CSS variable. */\r\nexport interface ComponentTheme {\r\n accent: string;\r\n accentTint: string;\r\n accentText: string;\r\n surface: string;\r\n surfaceAlt: string;\r\n ink: string;\r\n muted: string;\r\n line: string;\r\n lineSoft: string;\r\n phase: string;\r\n milestone: string;\r\n baseline: string;\r\n selected: string;\r\n focus: string;\r\n positive: string;\r\n negative: string;\r\n tooltipBg: string;\r\n tooltipInk: string;\r\n font: string;\r\n monoFont: string;\r\n}\r\n\r\nexport const lightTheme: ComponentTheme = {\r\n accent: brand.cobalt,\r\n accentTint: '#DCE3FF',\r\n accentText: '#FFFFFF',\r\n surface: '#FFFFFF',\r\n surfaceAlt: '#F6F6F3',\r\n ink: brand.ink,\r\n muted: brand.muted,\r\n line: brand.line,\r\n lineSoft: '#F2F3F5',\r\n phase: brand.ink,\r\n milestone: semantic.milestone,\r\n baseline: '#B9BDC7',\r\n selected: '#F4F6FF',\r\n focus: brand.cobalt,\r\n positive: semantic.positive,\r\n negative: semantic.negative,\r\n tooltipBg: brand.ink,\r\n tooltipInk: '#FFFFFF',\r\n font: font.inherit,\r\n monoFont: font.mono\r\n};\r\n\r\nexport const darkTheme: ComponentTheme = {\r\n accent: '#6F8BFF',\r\n accentTint: '#28305A',\r\n accentText: '#0E1116',\r\n surface: '#14171D',\r\n surfaceAlt: '#0E1116',\r\n ink: '#F2F3F5',\r\n muted: '#A3A9B6',\r\n line: '#2A2F3A',\r\n lineSoft: '#1D2129',\r\n phase: '#E4E5E9',\r\n milestone: '#F2A53A',\r\n baseline: '#5D6472',\r\n selected: '#1C2238',\r\n focus: '#8FA6FF',\r\n positive: '#3FBF7F',\r\n negative: '#F06A58',\r\n tooltipBg: '#F2F3F5',\r\n tooltipInk: '#0E1116',\r\n font: font.inherit,\r\n monoFont: font.mono\r\n};\r\n\r\nconst kebab = (s: string): string => s.replace(/[A-Z]/g, (m) => '-' + m.toLowerCase());\r\n\r\n/** Theme → `{ '--nf-accent': '#2E54FF', ... }`, ready for a React `style` prop. */\r\nexport function themeVars(theme: Partial<ComponentTheme> = {}, base: ComponentTheme = lightTheme): Record<string, string> {\r\n const merged = { ...base, ...theme } as ComponentTheme;\r\n const out: Record<string, string> = {};\r\n (Object.keys(merged) as (keyof ComponentTheme)[]).forEach((k) => {\r\n out[`--nf-${kebab(k)}`] = merged[k];\r\n });\r\n return out;\r\n}\r\n\r\n/** The IX mark as an SVG string. `ember` colours the glowing stroke tip. */\r\nexport interface MarkOptions {\r\n size?: number;\r\n tile?: boolean;\r\n ink?: string;\r\n ember?: string;\r\n}\r\n\r\nexport function markSvg({ size = 40, tile = true, ink = brand.ink as string, ember = brand.ember as string }: MarkOptions = {}): string {\r\n const stroke = tile ? '#FFFFFF' : ink;\r\n return (\r\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${size}\" height=\"${size}\" viewBox=\"0 0 40 40\" role=\"img\" aria-label=\"Ninth Forge\">` +\r\n (tile ? `<rect width=\"40\" height=\"40\" rx=\"10\" fill=\"${ink}\"/>` : '') +\r\n `<rect x=\"8.5\" y=\"10\" width=\"4.6\" height=\"20\" fill=\"${stroke}\"/>` +\r\n `<path d=\"M17 10 L31.5 30\" stroke=\"${stroke}\" stroke-width=\"4.6\"/>` +\r\n `<path d=\"M31.5 10 L17 30\" stroke=\"${stroke}\" stroke-width=\"4.6\"/>` +\r\n `<path d=\"M31.5 10 L27.6 15.4\" stroke=\"${ember}\" stroke-width=\"4.6\"/>` +\r\n `</svg>`\r\n );\r\n}\r\n\r\n/* ---------- Contrast ---------- */\r\n\r\nfunction luminance(hex: string): number | null {\r\n const m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex.trim());\r\n if (!m) return null;\r\n const h = m[1]!.length === 3 ? m[1]!.replace(/./g, (c) => c + c) : m[1]!;\r\n const ch = [0, 2, 4].map((i) => {\r\n const v = parseInt(h.slice(i, i + 2), 16) / 255;\r\n return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;\r\n });\r\n return 0.2126 * ch[0]! + 0.7152 * ch[1]! + 0.0722 * ch[2]!;\r\n}\r\n\r\n/** WCAG contrast ratio between two hex colours (1–21), or null if either isn't a hex colour. */\r\nexport function contrastRatio(a: string, b: string): number | null {\r\n const la = luminance(a);\r\n const lb = luminance(b);\r\n if (la === null || lb === null) return null;\r\n const [hi, lo] = la > lb ? [la, lb] : [lb, la];\r\n return (hi + 0.05) / (lo + 0.05);\r\n}\r\n\r\n/**\r\n * Text colour for use on `background`: white or brand ink, whichever contrasts more.\r\n * Use it for text on a customer's accent (badges, tags, primary buttons). Non-hex input\r\n * returns white.\r\n */\r\nexport function readableTextOn(background: string, light = '#FFFFFF', dark: string = brand.ink): string {\r\n const onLight = contrastRatio(background, light);\r\n const onDark = contrastRatio(background, dark);\r\n if (onLight === null || onDark === null) return light;\r\n return onLight >= onDark ? light : dark;\r\n}\r\n\r\nfunction mixHex(a: string, b: string, t: number): string {\r\n const p = (h: string) => {\r\n const s = h.replace('#', '');\r\n const f = s.length === 3 ? s.replace(/./g, (c) => c + c) : s;\r\n return [0, 2, 4].map((i) => parseInt(f.slice(i, i + 2), 16));\r\n };\r\n const [x, y] = [p(a), p(b)];\r\n return '#' + x.map((v, i) => Math.round(v + (y[i]! - v) * t).toString(16).padStart(2, '0')).join('').toUpperCase();\r\n}\r\n\r\n/**\r\n * A background and text colour for small text on an accent (tags, badges, primary buttons)\r\n * that meets 4.5:1. Keeps the accent when it already passes; otherwise shades it — darker\r\n * under white text, lighter under ink — by the smallest step that passes. Non-hex accents\r\n * are returned unchanged with white text.\r\n */\r\nexport function accentForText(accent: string, light = '#FFFFFF', dark: string = brand.ink): { background: string; text: string } {\r\n const text = readableTextOn(accent, light, dark);\r\n if ((contrastRatio(accent, text) ?? 21) >= 4.5) return { background: accent, text };\r\n const toward = text === light ? '#000000' : '#FFFFFF';\r\n for (let t = 0.05; t <= 1.0001; t += 0.05) {\r\n const bg = mixHex(accent, toward, t);\r\n if ((contrastRatio(bg, text) ?? 0) >= 4.5) return { background: bg, text };\r\n }\r\n return { background: toward === '#000000' ? '#000000' : '#FFFFFF', text };\r\n}\r\n\r\n/**\r\n * A colour for a mark (a stripe, a dot, a bar) that must stay visible on `background`: `color`\r\n * itself when it reaches `ratio` (default 3:1, WCAG non-text), otherwise mixed toward black (or\r\n * white on a dark background) in 10% steps until it does. For data colours makers choose, such as\r\n * a Dataverse choice's colour. Non-hex input is returned unchanged.\r\n */\r\nexport function visibleOn(color: string, background: string, ratio = 3): string {\r\n const own = contrastRatio(color, background);\r\n if (own === null || own >= ratio) return color;\r\n const dark = (luminance(background) ?? 1) < 0.18;\r\n const toward = dark ? '#FFFFFF' : '#000000';\r\n for (let t = 0.1; t <= 1.0001; t += 0.1) {\r\n const mixed = mixHex(color, toward, t);\r\n if ((contrastRatio(mixed, background) ?? 0) >= ratio) return mixed;\r\n }\r\n return toward;\r\n}\r\n\r\nexport * from './text';\r\n","import type { ConflictMode, SchedulerLabels, SchedulerPreset, SchedulerView } from './types';\n\nexport interface PresetDefaults {\n labels: SchedulerLabels;\n view: SchedulerView;\n snapMinutes: number;\n conflictMode: ConflictMode;\n /** Decimal wall hours. */\n workStart: number;\n workEnd: number;\n /** What capacity means in the row header: hours booked, or seats (rooms). */\n capacity: 'hours' | 'seats';\n capacityHours: number;\n /** Unassigned panel open by default. */\n showUnassigned: boolean;\n}\n\n/**\n * One engine, five scenarios (docs/specs/scheduler.md, \"Scenarios\", Matt 2026-10-25).\n * A preset only sets defaults; every prop still overrides it.\n */\nexport const PRESETS: Record<SchedulerPreset, PresetDefaults> = {\n people: {\n labels: { resource: 'Person', resources: 'People', booking: 'Shift', bookings: 'Shifts', group: 'Team' },\n view: 'week',\n snapMinutes: 30,\n conflictMode: 'warn',\n workStart: 9,\n workEnd: 17,\n capacity: 'hours',\n capacityHours: 8,\n showUnassigned: false\n },\n rooms: {\n labels: { resource: 'Room', resources: 'Rooms', booking: 'Booking', bookings: 'Bookings', group: 'Building' },\n view: 'day',\n snapMinutes: 15,\n conflictMode: 'block',\n workStart: 8,\n workEnd: 18,\n capacity: 'seats',\n capacityHours: 10,\n showUnassigned: false\n },\n vehicles: {\n labels: { resource: 'Vehicle', resources: 'Vehicles', booking: 'Hire', bookings: 'Hires', group: 'Depot' },\n view: 'twoWeeks',\n snapMinutes: 60,\n conflictMode: 'block',\n workStart: 7,\n workEnd: 17,\n capacity: 'hours',\n capacityHours: 10,\n showUnassigned: false\n },\n equipment: {\n labels: { resource: 'Asset', resources: 'Equipment', booking: 'Hire', bookings: 'Hires', group: 'Category' },\n view: 'twoWeeks',\n snapMinutes: 60,\n conflictMode: 'block',\n workStart: 7,\n workEnd: 17,\n capacity: 'hours',\n capacityHours: 10,\n showUnassigned: false\n },\n jobs: {\n labels: { resource: 'Technician', resources: 'Technicians', booking: 'Job', bookings: 'Jobs', group: 'Area' },\n view: 'day',\n snapMinutes: 15,\n conflictMode: 'warn',\n workStart: 7.5,\n workEnd: 16,\n capacity: 'hours',\n capacityHours: 8,\n showUnassigned: true\n },\n custom: {\n labels: { resource: 'Resource', resources: 'Resources', booking: 'Booking', bookings: 'Bookings', group: 'Group' },\n view: 'week',\n snapMinutes: 15,\n conflictMode: 'allow',\n workStart: 9,\n workEnd: 17,\n capacity: 'hours',\n capacityHours: 8,\n showUnassigned: false\n }\n};\n\n/** Days per view. Month varies (28–31); use `viewRange` for the real span. */\nexport const VIEW_DAYS: Record<SchedulerView, number> = { day: 1, week: 7, twoWeeks: 14, month: 31, agenda: 7 };\n\nexport const VIEW_LABEL: Record<SchedulerView, string> = { day: 'Day', week: 'Week', twoWeeks: 'Two weeks', month: 'Month', agenda: 'Agenda' };\n\n/** What one step back or forward moves, for button labels (\"Previous month\"). */\nexport const VIEW_STEP: Record<SchedulerView, string> = { day: 'day', week: 'week', twoWeeks: 'two weeks', month: 'month', agenda: '7 days' };\n","import { atHour, dayOf, dayToInstant, instantToDay, MONTHS, parseHour, parts, startOfMonth, timeLabel, toDay, weekday, type WorkingWeek, type Zone } from '@ninthforge/core';\r\nimport { accentForText, scaleWithText } from '@ninthforge/tokens';\r\nimport { VIEW_DAYS } from './presets';\r\nimport type { SchedBooking, SchedResource, SchedulerView } from './types';\r\n\r\n/* Pure layout for the Scheduler: no React, so node:test covers it. Times are UTC ms. */\r\n\r\nexport const HEADER_H = 56;\r\nexport const GROUP_H = 32;\r\nexport const LANE_H = 30;\r\nexport const ROW_PAD = 7;\r\nexport const ROW_MIN_H = 52;\r\nexport const RESOURCE_W = 232;\r\n\r\nconst MINUTE = 60_000;\r\n\r\n/**\r\n * How many hours apart the Day view's hour labels go, so they never touch: the smallest of 1, 2, 3,\r\n * 4, 6 or 12 hours whose width holds one label (\"08:00\": five monospace characters at the caption\r\n * size, its 4px inset and an 8px gap). Bigger text or a narrower board means fewer labels.\r\n */\r\nexport function hourLabelStep(pxPerHour: number, captionPx: number): number {\r\n const need = 5 * 0.62 * captionPx + 4 + 8;\r\n for (const step of [1, 2, 3, 4, 6, 12]) if (pxPerHour * step >= need) return step;\r\n return 24;\r\n}\r\n\r\n/** Whether an hour label starting at `x` fits before the board's right edge (else it would be cut off). */\r\nexport function hourLabelFits(x: number, boardWidth: number, captionPx: number): boolean {\r\n return x + 4 + 5 * 0.62 * captionPx <= boardWidth;\r\n}\r\n\r\n/** An instant from ms or an ISO date-time; NaN when unusable. */\r\nexport function toMs(v: number | string | null | undefined): number {\r\n if (typeof v === 'number') return Number.isFinite(v) ? v : NaN;\r\n if (typeof v !== 'string' || !v) return NaN;\r\n return Date.parse(v);\r\n}\r\n\r\nexport interface Item {\r\n booking: SchedBooking;\r\n id: string;\r\n start: number;\r\n /** Never before start; zero-length bookings get 15 minutes so they can be seen and grabbed. */\r\n end: number;\r\n resourceId: string | null;\r\n kind: 'booking' | 'unavailable';\r\n cancelled: boolean;\r\n}\r\n\r\n/** Usable bookings, with instants resolved. Bookings without a valid start are dropped. */\r\nconst TEXT_FIELDS = ['name', 'status', 'location', 'subtitle', 'color'] as const;\r\nconst text = (v: unknown): string | undefined => (v === null || v === undefined ? undefined : typeof v === 'string' ? v : String(v));\r\n\r\n/**\r\n * A booking with every text field really text (hosts outside Power Apps can pass anything).\r\n * Returns the same object when it's already clean, so hosts get their own booking back.\r\n */\r\nexport function cleanBooking(b: SchedBooking): SchedBooking {\r\n const raw = b as unknown as Record<string, unknown>;\r\n const dirty =\r\n typeof raw.id !== 'string' ||\r\n TEXT_FIELDS.some((k) => raw[k] !== undefined && typeof raw[k] !== 'string') ||\r\n (raw.attendees !== undefined && typeof raw.attendees !== 'number');\r\n if (!dirty) return b;\r\n const fixed: Record<string, unknown> = { ...raw, id: String(raw.id ?? '') };\r\n for (const k of TEXT_FIELDS) fixed[k] = text(raw[k]);\r\n fixed.name = text(raw.name) ?? '';\r\n const n = Number(raw.attendees);\r\n fixed.attendees = raw.attendees === undefined || !Number.isFinite(n) ? undefined : n;\r\n return fixed as unknown as SchedBooking;\r\n}\r\n\r\n/** Resources with text fields as text, numbers as finite numbers, and unique ids (first wins). */\r\nexport function cleanResources(resources: readonly SchedResource[]): SchedResource[] {\r\n const seen = new Set<string>();\r\n const out: SchedResource[] = [];\r\n for (const r of resources) {\r\n const raw = r as unknown as Record<string, unknown>;\r\n const id = text(raw.id);\r\n if (!id || seen.has(id)) continue;\r\n seen.add(id);\r\n const num = (v: unknown) => (typeof v === 'number' && Number.isFinite(v) && v >= 0 ? v : undefined);\r\n const clean =\r\n typeof raw.name === 'string' &&\r\n ['group', 'subtitle', 'color', 'image'].every((k) => raw[k] === undefined || typeof raw[k] === 'string') &&\r\n ['capacityHours', 'seats', 'sort'].every((k) => raw[k] === undefined || num(raw[k]) !== undefined);\r\n if (clean) {\r\n out.push(r);\r\n continue;\r\n }\r\n out.push({\r\n ...r,\r\n id,\r\n name: text(raw.name) ?? '',\r\n group: text(raw.group),\r\n subtitle: text(raw.subtitle),\r\n color: text(raw.color),\r\n image: text(raw.image),\r\n capacityHours: num(raw.capacityHours),\r\n seats: num(raw.seats),\r\n sort: num(raw.sort)\r\n });\r\n }\r\n return out;\r\n}\r\n\r\nexport function normalise(bookings: readonly SchedBooking[]): Item[] {\r\n const out: Item[] = [];\r\n for (const raw of bookings) {\r\n if (!raw || typeof raw !== 'object') continue;\r\n const b = cleanBooking(raw);\r\n const start = toMs(b.start);\r\n let end = toMs(b.end);\r\n if (Number.isNaN(start)) continue;\r\n if (Number.isNaN(end) || end <= start) end = start + 15 * MINUTE;\r\n out.push({\r\n booking: b,\r\n id: b.id,\r\n start,\r\n end,\r\n resourceId: b.resourceId || null,\r\n kind: b.kind === 'unavailable' ? 'unavailable' : 'booking',\r\n cancelled: /^cancel/i.test(b.status ?? '')\r\n });\r\n }\r\n return out;\r\n}\r\n\r\n/* ---------- Range ---------- */\r\n\r\n/** First day shown and how many: Day = that day; Week / Two weeks start on the Monday. */\r\nexport function viewRange(date: string, view: SchedulerView): { startDay: number; days: number } {\r\n const day = toDay(date);\r\n if (view === 'month') {\r\n const p = parts(day);\r\n const first = startOfMonth(day);\r\n return { startDay: first, days: dayOf(p.year, p.month + 1, 1) - first };\r\n }\r\n // Agenda runs from the chosen date (upcoming work), not from Monday.\r\n const startDay = view === 'day' || view === 'agenda' ? day : day - weekday(day);\r\n return { startDay, days: VIEW_DAYS[view] };\r\n}\r\n\r\n/**\r\n * The date one step back or forward: a day, a week, two weeks, 7 days (agenda), or a calendar\r\n * month, keeping the day of the month where it exists (31 Jan → 28 Feb).\r\n */\r\nexport function stepDate(date: string, view: SchedulerView, dir: 1 | -1): number {\r\n const day = toDay(date);\r\n if (view === 'month') {\r\n const p = parts(day);\r\n const last = dayOf(p.year, p.month + dir + 1, 1) - 1;\r\n return Math.min(dayOf(p.year, p.month + dir, p.date), last);\r\n }\r\n return day + dir * VIEW_DAYS[view];\r\n}\r\n\r\n/**\r\n * When a booking falls on one agenda day: \"09:00–13:00\", \"from 22:00\", \"until 06:00\" or\r\n * \"All day\" (all-day bookings, and ones that cover the whole day).\r\n */\r\nexport function agendaTime(it: Pick<Item, 'start' | 'end' | 'booking'>, day: number, zone: Zone): string {\r\n const ds = dayToInstant(day, zone);\r\n const de = dayToInstant(day + 1, zone);\r\n if (it.booking.allDay || (it.start <= ds && it.end >= de)) return 'All day';\r\n if (it.start < ds) return `until ${timeLabel(it.end, zone)}`;\r\n if (it.end > de) return `from ${timeLabel(it.start, zone)}`;\r\n return `${timeLabel(it.start, zone)}–${timeLabel(it.end, zone)}`;\r\n}\r\n\r\n/** The instant range a view covers, as [start, end) in `zone`. */\r\nexport function rangeInstants(startDay: number, days: number, zone: Zone): { start: number; end: number } {\r\n return { start: dayToInstant(startDay, zone), end: dayToInstant(startDay + days, zone) };\r\n}\r\n\r\n/* ---------- Lanes and conflicts ---------- */\r\n\r\n/**\r\n * Stack overlapping items into lanes (greedy, by start then longest first): each item gets the\r\n * first lane free at its start. Returns lane per item id and how many lanes the row needs.\r\n */\r\nexport function assignLanes(items: readonly Item[]): { lane: Map<string, number>; lanes: number } {\r\n const sorted = [...items].sort((a, b) => a.start - b.start || b.end - b.start - (a.end - a.start) || a.id.localeCompare(b.id));\r\n const ends: number[] = [];\r\n const lane = new Map<string, number>();\r\n for (const it of sorted) {\r\n let i = ends.findIndex((e) => e <= it.start);\r\n if (i < 0) {\r\n i = ends.length;\r\n ends.push(it.end);\r\n } else ends[i] = it.end;\r\n lane.set(it.id, i);\r\n }\r\n return { lane, lanes: Math.max(1, ends.length) };\r\n}\r\n\r\n/** Ids of items that overlap another item on the same resource. Cancelled bookings never conflict. */\r\nexport function conflicts(items: readonly Item[]): Set<string> {\r\n const out = new Set<string>();\r\n const live = items.filter((i) => !i.cancelled).sort((a, b) => a.start - b.start);\r\n // Sorted by start, so each item only needs checking against the ones that start before it ends.\r\n for (let i = 0; i < live.length; i++)\r\n for (let j = i + 1; j < live.length && live[j]!.start < live[i]!.end; j++) {\r\n out.add(live[i]!.id);\r\n out.add(live[j]!.id);\r\n }\r\n return out;\r\n}\r\n\r\n/* ---------- Rows ---------- */\r\n\r\nexport interface GroupRow {\r\n kind: 'group';\r\n key: string;\r\n label: string;\r\n count: number;\r\n collapsed: boolean;\r\n top: number;\r\n height: number;\r\n}\r\n\r\nexport interface ResourceRow {\r\n kind: 'resource';\r\n key: string;\r\n resource: SchedResource;\r\n /** Items overlapping the visible range, with their lanes. */\r\n items: Item[];\r\n lane: Map<string, number>;\r\n lanes: number;\r\n conflicts: Set<string>;\r\n top: number;\r\n height: number;\r\n /** Position among resource rows (for aria-rowindex and keyboard). */\r\n index: number;\r\n}\r\n\r\nexport type Row = GroupRow | ResourceRow;\r\n\r\nexport function sortResources(resources: readonly SchedResource[]): SchedResource[] {\r\n return [...resources].sort(\r\n (a, b) =>\r\n (a.group ?? '').localeCompare(b.group ?? '') ||\r\n (a.sort ?? Number.MAX_SAFE_INTEGER) - (b.sort ?? Number.MAX_SAFE_INTEGER) ||\r\n a.name.localeCompare(b.name)\r\n );\r\n}\r\n\r\n/** A resource row's height; lanes and the minimum grow with the text scale (8 px grid). */\r\nexport function rowHeight(lanes: number, textScale = 1): number {\r\n return Math.max(scaleWithText(ROW_MIN_H, textScale), ROW_PAD * 2 + lanes * scaleWithText(LANE_H, textScale));\r\n}\r\n\r\n/**\r\n * Rows for the board: optional group headers, then resources with their bookings in the\r\n * range [from, to). Heights grow with the number of overlapping lanes.\r\n */\r\nexport function buildRows(\r\n resources: readonly SchedResource[],\r\n items: readonly Item[],\r\n opts: { from: number; to: number; groupBy: boolean; collapsed?: ReadonlySet<string>; filter?: string; textScale?: number }\r\n): Row[] {\r\n const q = (opts.filter ?? '').trim().toLowerCase();\r\n const byResource = new Map<string, Item[]>();\r\n for (const it of items) {\r\n if (!it.resourceId || it.end <= opts.from || it.start >= opts.to) continue;\r\n const list = byResource.get(it.resourceId) ?? [];\r\n list.push(it);\r\n byResource.set(it.resourceId, list);\r\n }\r\n const visible = sortResources(resources).filter(\r\n (r) => !q || r.name.toLowerCase().includes(q) || (r.subtitle ?? '').toLowerCase().includes(q) || (r.group ?? '').toLowerCase().includes(q)\r\n );\r\n const rows: Row[] = [];\r\n let top = 0;\r\n let index = 0;\r\n let currentGroup: string | undefined;\r\n for (const r of visible) {\r\n const g = r.group ?? '';\r\n if (opts.groupBy && g !== currentGroup) {\r\n currentGroup = g;\r\n const collapsed = !!opts.collapsed?.has(g);\r\n rows.push({ kind: 'group', key: `g:${g}`, label: g || 'Other', count: visible.filter((x) => (x.group ?? '') === g).length, collapsed, top, height: scaleWithText(GROUP_H, opts.textScale) });\r\n top += scaleWithText(GROUP_H, opts.textScale);\r\n }\r\n if (opts.groupBy && opts.collapsed?.has(g)) continue;\r\n const its = (byResource.get(r.id) ?? []).sort((a, b) => a.start - b.start);\r\n const { lane, lanes } = assignLanes(its);\r\n const height = rowHeight(lanes, opts.textScale);\r\n rows.push({ kind: 'resource', key: `r:${r.id}`, resource: r, items: its, lane, lanes, conflicts: conflicts(its), top, height, index: index++ });\r\n top += height;\r\n }\r\n return rows;\r\n}\r\n\r\nexport function totalHeight(rows: readonly Row[]): number {\r\n const last = rows[rows.length - 1];\r\n return last ? last.top + last.height : 0;\r\n}\r\n\r\n/** Rows overlapping the viewport (plus `overscan` px), by binary search on `top`. */\r\nexport function visibleRows(rows: readonly Row[], scrollTop: number, viewportHeight: number, overscan = 400): Row[] {\r\n if (!rows.length) return [];\r\n const from = Math.max(0, scrollTop - overscan);\r\n const to = scrollTop + viewportHeight + overscan;\r\n let lo = 0;\r\n let hi = rows.length - 1;\r\n while (lo < hi) {\r\n const mid = (lo + hi) >> 1;\r\n if (rows[mid]!.top + rows[mid]!.height <= from) lo = mid + 1;\r\n else hi = mid;\r\n }\r\n const out: Row[] = [];\r\n for (let i = lo; i < rows.length && rows[i]!.top < to; i++) out.push(rows[i]!);\r\n return out;\r\n}\r\n\r\n/* ---------- Unassigned ---------- */\r\n\r\n/** Unassigned items overlapping [from, to), earliest first. */\r\nexport function unassigned(items: readonly Item[], from: number, to: number): Item[] {\r\n return items.filter((i) => !i.resourceId && i.end > from && i.start < to).sort((a, b) => a.start - b.start);\r\n}\r\n\r\n/** How the Unassigned panel orders its queue. */\r\nexport type QueueSort = 'soonest' | 'latest' | 'name' | 'location';\r\n\r\nexport const QUEUE_SORT_LABEL: Record<QueueSort, string> = { soonest: 'Soonest first', latest: 'Latest first', name: 'Name', location: 'Location' };\r\n\r\nconst byText = (a: string | undefined, b: string | undefined) => {\r\n // Blanks last, then a natural, case-insensitive order (\"Job 2\" before \"Job 10\").\r\n if (!a && !b) return 0;\r\n if (!a) return 1;\r\n if (!b) return -1;\r\n return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'base' });\r\n};\r\n\r\n/**\r\n * The panel's queue: unassigned items matching `query` (name, location, status or subtitle,\r\n * case-insensitive), in the chosen order. Ties fall back to soonest first.\r\n */\r\nexport function queueView(items: readonly Item[], query: string, sort: QueueSort): Item[] {\r\n const q = query.trim().toLowerCase();\r\n const hit = (it: Item) =>\r\n !q || [it.booking.name, it.booking.location, it.booking.status, it.booking.subtitle].some((v) => (v ?? '').toLowerCase().includes(q));\r\n const soonest = (a: Item, b: Item) => a.start - b.start || byText(a.booking.name, b.booking.name);\r\n const order: Record<QueueSort, (a: Item, b: Item) => number> = {\r\n soonest,\r\n latest: (a, b) => b.start - a.start || byText(a.booking.name, b.booking.name),\r\n name: (a, b) => byText(a.booking.name, b.booking.name) || soonest(a, b),\r\n location: (a, b) => byText(a.booking.location, b.booking.location) || soonest(a, b)\r\n };\r\n return items.filter((i) => !i.resourceId && hit(i)).sort(order[sort]);\r\n}\r\n\r\n/* ---------- Colour ---------- */\r\n\r\n/** Validated palette for statuses without a colour (distinct in lightness as well as hue). */\r\nexport const STATUS_PALETTE = ['#2E54FF', '#0E8C7F', '#6B4EFF', '#B54708', '#0B6E99', '#9E1F63'];\r\n\r\nconst KNOWN: Record<string, 'accent' | 'tentative' | 'done' | 'cancelled'> = {\r\n confirmed: 'accent',\r\n booked: 'accent',\r\n scheduled: 'accent',\r\n tentative: 'tentative',\r\n proposed: 'tentative',\r\n done: 'done',\r\n completed: 'done',\r\n cancelled: 'cancelled',\r\n canceled: 'cancelled'\r\n};\r\n\r\nexport type BarStyle = 'solid' | 'tentative' | 'done' | 'cancelled' | 'unavailable';\r\n\r\n/**\r\n * Colour and style for a booking: its own colour, else the status map, else a known status\r\n * style on the accent, else palette colours in order of first appearance.\r\n */\r\nexport function bookingAppearance(\r\n b: SchedBooking,\r\n accent: string,\r\n statusColors: Readonly<Record<string, string>> = {},\r\n seen: Map<string, string> = new Map()\r\n): { color: string; text: string; style: BarStyle } {\r\n if (b.kind === 'unavailable') return { color: '#7A808C', text: '#FFFFFF', style: 'unavailable' };\r\n const status = (b.status ?? '').trim();\r\n const known = KNOWN[status.toLowerCase()];\r\n const style: BarStyle = known === 'tentative' ? 'tentative' : known === 'done' ? 'done' : known === 'cancelled' ? 'cancelled' : 'solid';\r\n let color = b.color || statusColors[status];\r\n if (!color) {\r\n if (!status || known) color = accent;\r\n else {\r\n color = seen.get(status) ?? '';\r\n if (!color) {\r\n color = STATUS_PALETTE[seen.size % STATUS_PALETTE.length]!;\r\n seen.set(status, color);\r\n }\r\n }\r\n }\r\n const pair = accentForText(color);\r\n return { color: pair.background, text: pair.text, style };\r\n}\r\n\r\n/* ---------- Words ---------- */\r\n\r\nexport function initials(name: string): string {\r\n return name\r\n .split(/\\s+/)\r\n .filter(Boolean)\r\n .slice(0, 2)\r\n .map((w) => w[0]!.toUpperCase())\r\n .join('');\r\n}\r\n\r\n/** Working hours of the board: the earliest start and latest end across resources, else the defaults. */\r\nexport function boardHours(resources: readonly SchedResource[], start: number, end: number): { start: number; end: number } {\r\n let s = Infinity;\r\n let e = -Infinity;\r\n for (const r of resources) {\r\n const rs = r.workStart === undefined ? NaN : parseHour(r.workStart);\r\n const re = r.workEnd === undefined ? NaN : parseHour(r.workEnd);\r\n if (!Number.isNaN(rs)) s = Math.min(s, rs);\r\n if (!Number.isNaN(re)) e = Math.max(e, re);\r\n }\r\n return { start: Number.isFinite(s) ? s : start, end: Number.isFinite(e) && e > (Number.isFinite(s) ? s : start) ? e : end };\r\n}\r\n\r\n/* ---------- Keyboard ---------- */\r\n\r\n/**\r\n * The booking to move to from `id` with an arrow key: ←/→ the previous/next booking in the same\r\n * row (by start); ↑/↓ the booking in the nearest row above/below that starts closest in time.\r\n */\r\nexport function neighbour(rows: readonly Row[], id: string | null, key: 'ArrowLeft' | 'ArrowRight' | 'ArrowUp' | 'ArrowDown'): string | null {\r\n const resourceRows = rows.filter((r): r is ResourceRow => r.kind === 'resource');\r\n const firstWithItems = resourceRows.find((r) => r.items.length);\r\n if (!id) return firstWithItems?.items[0]?.id ?? null;\r\n const at = resourceRows.findIndex((r) => r.items.some((i) => i.id === id));\r\n if (at < 0) return firstWithItems?.items[0]?.id ?? null;\r\n const row = resourceRows[at]!;\r\n const pos = row.items.findIndex((i) => i.id === id);\r\n const current = row.items[pos]!;\r\n if (key === 'ArrowLeft') return row.items[pos - 1]?.id ?? id;\r\n if (key === 'ArrowRight') return row.items[pos + 1]?.id ?? id;\r\n const step = key === 'ArrowUp' ? -1 : 1;\r\n for (let i = at + step; i >= 0 && i < resourceRows.length; i += step) {\r\n const its = resourceRows[i]!.items;\r\n if (!its.length) continue;\r\n let best = its[0]!;\r\n for (const it of its) if (Math.abs(it.start - current.start) < Math.abs(best.start - current.start)) best = it;\r\n return best.id;\r\n }\r\n return id;\r\n}\r\n\r\n/** ISO `YYYY-MM-DD` of a day number. */\r\nexport function isoOfDay(day: number): string {\r\n return new Date(day * 86_400_000).toISOString().slice(0, 10);\r\n}\r\n\r\n/** Today's ISO date in a zone. */\r\nexport function todayIn(zone: Zone, now = Date.now()): string {\r\n return isoOfDay(instantToDay(now, zone));\r\n}\r\n\r\n/* ---------- Capacity ---------- */\r\n\r\n/**\r\n * Hours booked inside working time over a range of days: each booking is clipped to each\r\n * working day's window (so a multi-day hire counts its working hours, not its nights).\r\n * Cancelled bookings don't count; unavailable blocks do.\r\n */\r\nexport function bookedWorkingHours(\r\n items: readonly Item[],\r\n startDay: number,\r\n days: number,\r\n workStart: number,\r\n workEnd: number,\r\n week: WorkingWeek,\r\n zone: Zone,\r\n /** Daily capacity: unavailable time counts up to this much a day (a day off is full, not over). */\r\n capacityHours = Infinity\r\n): number {\r\n let ms = 0;\r\n for (let d = startDay; d < startDay + days; d++) {\r\n if (week === 'mon-fri' && weekday(d) >= 5) continue;\r\n ms += bookedMsOn(items, d, workStart, workEnd, zone, capacityHours);\r\n }\r\n return Math.round((ms / 3_600_000) * 10) / 10;\r\n}\r\n\r\n// Zone conversions (Intl for IANA zones) dominate capacity maths at scale: 300 resources × 14\r\n// days asks for the same few day/hour instants thousands of times. Cache them per zone.\r\nconst hourCache = new WeakMap<Zone, Map<number, number>>();\r\nfunction hourIn(day: number, hour: number, zone: Zone): number {\r\n let m = hourCache.get(zone);\r\n if (!m) hourCache.set(zone, (m = new Map()));\r\n const key = day * 100 + hour; // hour is 0–24, so this is unique per (day, hour)\r\n let v = m.get(key);\r\n if (v === undefined) {\r\n if (m.size > 20_000) m.clear();\r\n v = atHour(day, hour, zone);\r\n m.set(key, v);\r\n }\r\n return v;\r\n}\r\n\r\n/**\r\n * Booked time (ms) inside one day's working window. Cancelled bookings don't count. Unavailable\r\n * blocks (leave, maintenance) count, but only up to the day's capacity: a day off fills the day,\r\n * it doesn't put it over. Bookings on top of that do.\r\n */\r\nfunction bookedMsOn(items: readonly Item[], day: number, workStart: number, workEnd: number, zone: Zone, capacityHours = Infinity): number {\r\n const ws = hourIn(day, workStart, zone);\r\n const we = hourIn(day, workEnd, zone);\r\n let booked = 0;\r\n let off = 0;\r\n for (const it of items) {\r\n if (it.cancelled) continue;\r\n const s = Math.max(it.start, ws);\r\n const e = Math.min(it.end, we);\r\n if (e <= s) continue;\r\n if (it.kind === 'unavailable') off += e - s;\r\n else booked += e - s;\r\n }\r\n return booked + Math.min(off, capacityHours * 3_600_000);\r\n}\r\n\r\n/** Utilisation shade: under 75% of capacity, near (75–100%), or over. */\r\nexport type UtilLevel = 'under' | 'near' | 'over';\r\n\r\nexport interface DayUtil {\r\n day: number;\r\n /** Booked hours in the working window, one decimal. */\r\n booked: number;\r\n /** Capacity hours for the day. */\r\n capacity: number;\r\n /** booked ÷ capacity (Infinity when capacity is 0 and something is booked). */\r\n ratio: number;\r\n level: UtilLevel;\r\n}\r\n\r\nexport const NEAR_RATIO = 0.75;\r\n\r\nexport function utilLevel(booked: number, capacity: number): UtilLevel {\r\n if (capacity <= 0) return booked > 0 ? 'over' : 'under';\r\n const ratio = booked / capacity;\r\n if (ratio > 1 + 1e-9) return 'over';\r\n return ratio >= NEAR_RATIO ? 'near' : 'under';\r\n}\r\n\r\n/**\r\n * One resource's utilisation per working day: booked hours in its working window ÷ its daily\r\n * capacity. Non-working days (weekends with `mon-fri`) are left out, matching the row total.\r\n */\r\nexport function dailyUtilisation(\r\n items: readonly Item[],\r\n days: readonly number[],\r\n workStart: number,\r\n workEnd: number,\r\n capacityHours: number,\r\n week: WorkingWeek,\r\n zone: Zone\r\n): DayUtil[] {\r\n const out: DayUtil[] = [];\r\n for (const day of days) {\r\n if (week === 'mon-fri' && weekday(day) >= 5) continue;\r\n const booked = Math.round((bookedMsOn(items, day, workStart, workEnd, zone, capacityHours) / 3_600_000) * 10) / 10;\r\n const ratio = capacityHours > 0 ? booked / capacityHours : booked > 0 ? Infinity : 0;\r\n out.push({ day, booked, capacity: capacityHours, ratio, level: utilLevel(booked, capacityHours) });\r\n }\r\n return out;\r\n}\r\n\r\n/** Text on a bar: \"09:00–13:00\" for a same-day booking, \"26–28 Oct\" for one spanning days. */\r\nexport function barTimeLabel(it: Pick<Item, 'start' | 'end'>, zone: Zone): string {\r\n const d1 = instantToDay(it.start, zone);\r\n const d2 = instantToDay(it.end - 1, zone);\r\n if (d1 === d2) return `${timeLabel(it.start, zone)}–${timeLabel(it.end, zone)}`;\r\n const a = parts(d1);\r\n const b = parts(d2);\r\n return a.month === b.month ? `${a.date}–${b.date} ${MONTHS[b.month]}` : `${a.date} ${MONTHS[a.month]}–${b.date} ${MONTHS[b.month]}`;\r\n}\r\n","/**\n * Optimistic editing for Ninth Forge components — pure functions, no React.\n *\n * A change is `{ id, …fields, previous, source }`: the new values of some fields of one item,\n * the old values of the same fields, and what caused it. Components define which fields are\n * editable (`keys`), when two values count as the same (`equal`), and what \"no value\" is for\n * a field when undoing (`blank`). Shared by the Gantt and the Scheduler.\n */\n\n/** One edit to one item. `F` is the editable field set; `S` the sources a component uses. */\nexport type Change<F extends object, S extends string = string> = Partial<F> & {\n id: string;\n /** The same fields before the edit — for undo, and for hosts that diff. */\n previous: Partial<F>;\n source: S;\n};\n\nexport interface ChangeSpec<F extends object> {\n /** The editable fields, in a stable order. */\n keys: readonly (keyof F & string)[];\n /** Whether two values of a field are the same. Default: empty values (undefined/null/'') match, else `===`. */\n equal?: (key: keyof F & string, a: unknown, b: unknown) => boolean;\n /** The value that means \"none\" for a field, used when undoing a change whose old value was unset. */\n blank?: (key: keyof F & string) => unknown;\n}\n\nconst isEmpty = (v: unknown) => v === undefined || v === null || v === '';\n\nexport function defaultEqual(_key: string, a: unknown, b: unknown): boolean {\n if (isEmpty(a) && isEmpty(b)) return true;\n return a === b;\n}\n\n/** Set-like equality for string arrays (order doesn't matter; missing = empty). */\nexport function sameStringSet(a: unknown, b: unknown): boolean {\n const x = Array.isArray(a) ? [...(a as string[])].sort() : [];\n const y = Array.isArray(b) ? [...(b as string[])].sort() : [];\n return x.length === y.length && x.every((v, i) => v === y[i]);\n}\n\n/** The forward values of a change (everything except id/previous/source). */\nexport function fieldsOf<F extends object>(change: Change<F>, spec: ChangeSpec<F>): Partial<F> {\n const out: Partial<F> = {};\n for (const k of spec.keys) if (k in change) (out as Record<string, unknown>)[k] = (change as Record<string, unknown>)[k];\n return out;\n}\n\n/** The current values of exactly the fields `next` sets. */\nexport function previousOf<T extends object, F extends object>(item: T, next: Partial<F>, spec: ChangeSpec<F>): Partial<F> {\n const prev: Partial<F> = {};\n for (const k of spec.keys) if (k in next) (prev as Record<string, unknown>)[k] = (item as unknown as Record<string, unknown>)[k];\n return prev;\n}\n\n/** True when the change would not alter the item (e.g. a drag that snapped back to where it started). */\nexport function isNoop<T extends object, F extends object>(item: T, change: Change<F>, spec: ChangeSpec<F>): boolean {\n const eq = spec.equal ?? defaultEqual;\n const f = fieldsOf(change, spec) as Record<string, unknown>;\n return Object.keys(f).every((k) => eq(k as keyof F & string, (item as unknown as Record<string, unknown>)[k], f[k]));\n}\n\n/* ---------- Optimistic overrides ---------- */\n\nexport interface Override<F extends object> {\n patch: Partial<F>;\n /** Increments per commit; a stale save result can't touch a newer edit. */\n seq: number;\n}\n\nexport type Overrides<F extends object> = ReadonlyMap<string, Override<F>>;\n\n/** Items as the user sees them: host data with pending edits laid over it. */\nexport function applyOverrides<T extends { id: string }, F extends object>(items: T[], overrides: Overrides<F>): T[] {\n if (overrides.size === 0) return items;\n return items.map((t) => {\n const o = overrides.get(t.id);\n return o ? { ...t, ...o.patch } : t;\n });\n}\n\n/** Drop overrides that the host data now matches (the save landed and the data refreshed), or whose item is gone. */\nexport function reconcileOverrides<T extends { id: string }, F extends object>(\n items: T[],\n overrides: Overrides<F>,\n spec: Pick<ChangeSpec<F>, 'equal'> = {}\n): Map<string, Override<F>> {\n const eq = spec.equal ?? defaultEqual;\n const byId = new Map(items.map((t) => [t.id, t] as const));\n const next = new Map(overrides);\n for (const [id, o] of overrides) {\n const t = byId.get(id);\n if (!t) {\n next.delete(id);\n continue;\n }\n const matches = Object.keys(o.patch).every((k) =>\n eq(k as keyof F & string, (t as unknown as Record<string, unknown>)[k], (o.patch as Record<string, unknown>)[k])\n );\n if (matches) next.delete(id);\n }\n return next;\n}\n\n/* ---------- Undo / redo ---------- */\n\nexport const HISTORY_LIMIT = 20;\n\nexport interface History<C> {\n undo: C[];\n redo: C[];\n}\n\nexport function emptyHistory<C>(): History<C> {\n return { undo: [], redo: [] };\n}\n\n/** A new edit: push it, cap at 20, and clear redo. */\nexport function pushHistory<C>(h: History<C>, change: C): History<C> {\n return { undo: [...h.undo, change].slice(-HISTORY_LIMIT), redo: [] };\n}\n\n/** Remove a change from both stacks (its save was rejected). */\nexport function dropFromHistory<C>(h: History<C>, change: C): History<C> {\n return { undo: h.undo.filter((c) => c !== change), redo: h.redo.filter((c) => c !== change) };\n}\n\n/** The change that reverses `change`, ready to commit like any other edit. */\nexport function invertChange<F extends object, S extends string>(change: Change<F, S>, source: S, spec: ChangeSpec<F>): Change<F, S> {\n const forward = fieldsOf(change, spec);\n const back: Record<string, unknown> = {};\n for (const k of Object.keys(forward) as (keyof F & string)[]) {\n const v = (change.previous as Record<string, unknown>)[k];\n back[k] = v !== undefined ? v : spec.blank ? spec.blank(k) : undefined;\n }\n return { id: change.id, ...(back as Partial<F>), previous: forward, source } as Change<F, S>;\n}\n\nexport interface HistoryStep<C> {\n commit: C;\n entry: C;\n history: History<C>;\n}\n\n/** Undo: the change to commit, and the history with the entry moved to redo. */\nexport function undoStep<F extends object, S extends string>(\n h: History<Change<F, S>>,\n undoSource: S,\n spec: ChangeSpec<F>\n): HistoryStep<Change<F, S>> | null {\n const entry = h.undo[h.undo.length - 1];\n if (!entry) return null;\n return { commit: invertChange(entry, undoSource, spec), entry, history: { undo: h.undo.slice(0, -1), redo: [...h.redo, entry] } };\n}\n\n/** Redo: re-apply the most recently undone change. */\nexport function redoStep<F extends object, S extends string>(h: History<Change<F, S>>, redoSource: S): HistoryStep<Change<F, S>> | null {\n const entry = h.redo[h.redo.length - 1];\n if (!entry) return null;\n const commit = { ...entry, source: redoSource } as Change<F, S>;\n return { commit, entry, history: { undo: [...h.undo, entry].slice(-HISTORY_LIMIT), redo: h.redo.slice(0, -1) } };\n}\n\n/** A readable message from anything a save might throw or reject with. */\nexport function errorText(err: unknown, fallback = 'The change was not saved.'): string {\n if (err instanceof Error && err.message) return err.message;\n if (typeof err === 'string' && err) return err;\n const m = (err as { message?: unknown } | null)?.message;\n return typeof m === 'string' && m ? m : fallback;\n}\n","import { useCallback, useEffect, useMemo, useRef, useState } from 'react';\r\nimport {\r\n applyOverrides,\r\n dropFromHistory,\r\n emptyHistory,\r\n errorText,\r\n fieldsOf,\r\n pushHistory,\r\n reconcileOverrides,\r\n redoStep,\r\n undoStep,\r\n type Change,\r\n type ChangeSpec,\r\n type History,\r\n type Override\r\n} from './changes';\r\nimport type { ToastItem } from './Toasts';\r\n\r\nconst INFO_MS = 5000;\r\nconst ERROR_MS = 9000;\r\n\r\nexport interface EditingSpec<T extends { id: string }, F extends object> extends ChangeSpec<F> {\r\n /** Screen-reader and toast text for a committed change (\"'Pour slab' moved to …\"). */\r\n describe: (change: Change<F>, nameOf: (id: string) => string) => string;\r\n /** The item's display name, for messages. Default: its `name`, else \"item\". */\r\n name?: (item: T) => string;\r\n /** What to call an item in messages when it can't be found (\"task\", \"booking\"). */\r\n noun?: string;\r\n /**\r\n * View-only: no key that unlocks Pro actions (`unlocksPro` in @ninthforge/license). A locked\r\n * commit is never applied, saved or raised, so the item snaps back. Leave `lockedMessage` empty\r\n * while the licence check runs, so nothing about it shows yet.\r\n */\r\n locked?: boolean;\r\n /** Said when a locked action is tried (`lockedMessage('Plan')`). Toasted once per page, then announced. */\r\n lockedMessage?: string;\r\n /** Where the locked toast's \"Start a free trial\" button goes (`trialUrl('gantt')`). No button without it. */\r\n lockedTrialUrl?: string;\r\n /** That button's text. Default \"Start a free trial\". */\r\n lockedTrialLabel?: string;\r\n}\r\n\r\n/** The free trial page, tagged with where the visitor came from (`trialUrl('gantt')`). */\r\nexport function trialUrl(ref: string): string {\r\n return `https://ninthforge.com/trial?ref=${encodeURIComponent(ref)}`;\r\n}\r\n\r\n/** The trial message for a locked action (Matt, 2026-11-08). */\r\nexport function lockedMessage(product: string): string {\r\n return `Saving changes needs a Ninth Forge ${product} key. Start a free 30-day trial at ninthforge.com/trial.`;\r\n}\r\n\r\n/**\r\n * The editing lock for a licence check. `license` is the result (`null` while checking) and\r\n * `unlocked` is `unlocksPro(license)` from @ninthforge/license. Locked unless the key unlocks Pro\r\n * actions; while the check runs it's locked with no message, so nothing about it shows yet.\r\n */\r\nexport function lockFor(\r\n license: object | null | undefined,\r\n unlocked: boolean,\r\n product: string,\r\n /** Adds the toast's \"Start a free trial\" button, to `trialUrl(trialRef)` (e.g. 'gantt'). */\r\n trialRef?: string\r\n): { locked: boolean; lockedMessage?: string; lockedTrialUrl?: string } {\r\n if (unlocked) return { locked: false };\r\n if (!license) return { locked: true };\r\n return trialRef ? { locked: true, lockedMessage: lockedMessage(product), lockedTrialUrl: trialUrl(trialRef) } : { locked: true, lockedMessage: lockedMessage(product) };\r\n}\r\n\r\n/** Locked messages already toasted on this page: one toast per session, then only the status line. */\r\nconst toastedLocks = new Set<string>();\r\n/** For tests. */\r\nexport function resetLockedToasts(): void {\r\n toastedLocks.clear();\r\n}\r\n\r\n/**\r\n * True the first time a locked message is shown on this page, and marks it shown: one toast per\r\n * page, shared by every Ninth Forge component (`useEditing`'s `refuse`, and components with their\r\n * own editing, such as Kanban Pro). Later refusals only announce and show the status line.\r\n */\r\nexport function firstLockToast(message: string): boolean {\r\n if (toastedLocks.has(message)) return false;\r\n toastedLocks.add(message);\r\n return true;\r\n}\r\n\r\n/** Opens the trial page in a new tab (an ordinary link the user chose; the component makes no request). */\r\nexport function openTrial(url: string): void {\r\n if (typeof window !== 'undefined') window.open(url, '_blank', 'noopener');\r\n}\r\n\r\n/**\r\n * Optimistic edits.\r\n *\r\n * - `overrides` hold each committed edit until the host's items show it (or the save rejects).\r\n * - Undo/redo keep the last 20 changes; undo commits the previous values as a new change,\r\n * so hosts save it like any other edit.\r\n * - A rejected save reverts its override, restores history, and shows an error toast.\r\n */\r\nexport function useEditing<T extends { id: string }, F extends object, C extends Change<F> = Change<F>>(\r\n items: T[],\r\n onChange: ((c: C) => void | Promise<void>) | undefined,\r\n spec: EditingSpec<T, F>\r\n) {\r\n const [overrides, setOverrides] = useState<Map<string, Override<F>>>(() => new Map());\r\n const historyRef = useRef<History<C>>(emptyHistory<C>());\r\n const [, setHistoryVersion] = useState(0);\r\n const setHistory = (fn: (h: History<C>) => History<C>) => {\r\n historyRef.current = fn(historyRef.current);\r\n setHistoryVersion((v) => v + 1);\r\n };\r\n const seqRef = useRef(0);\r\n const [toasts, setToasts] = useState<ToastItem[]>([]);\r\n const [announcement, setAnnouncement] = useState({ text: '', n: 0 });\r\n const timers = useRef(new Map<number, ReturnType<typeof setTimeout>>());\r\n\r\n const itemsRef = useRef(items);\r\n itemsRef.current = items;\r\n const handlerRef = useRef(onChange);\r\n handlerRef.current = onChange;\r\n const specRef = useRef(spec);\r\n specRef.current = spec;\r\n\r\n // Host data caught up → drop the matching overrides.\r\n useEffect(() => {\r\n setOverrides((o) => (o.size ? reconcileOverrides(items, o, specRef.current) : o));\r\n }, [items]);\r\n\r\n useEffect(() => {\r\n const map = timers.current;\r\n return () => map.forEach((t) => clearTimeout(t));\r\n }, []);\r\n\r\n const dismiss = useCallback((id: number) => {\r\n const t = timers.current.get(id);\r\n if (t) clearTimeout(t);\r\n timers.current.delete(id);\r\n setToasts((list) => list.filter((x) => x.id !== id));\r\n }, []);\r\n\r\n const toast = useCallback(\r\n (item: Omit<ToastItem, 'id'>) => {\r\n const id = ++seqRef.current;\r\n // Keep every error, but only the latest info toast.\r\n setToasts((list) => [...list.filter((x) => x.tone === 'error'), { ...item, id }].slice(-4));\r\n timers.current.set(\r\n id,\r\n setTimeout(() => dismiss(id), item.tone === 'error' ? ERROR_MS : INFO_MS)\r\n );\r\n },\r\n [dismiss]\r\n );\r\n\r\n const announce = useCallback((text: string) => setAnnouncement((a) => ({ text, n: a.n + 1 })), []);\r\n\r\n // View-only: say why the action didn't happen (a toast once per page, then the status line).\r\n const [lockNote, setLockNote] = useState<string | null>(null);\r\n const refuse = useCallback(() => {\r\n const msg = specRef.current.lockedMessage;\r\n if (!msg) return; // still checking the key: stay quiet\r\n announce(msg);\r\n setLockNote(msg);\r\n if (!firstLockToast(msg)) return;\r\n const url = specRef.current.lockedTrialUrl;\r\n toast({ text: msg, tone: 'info', action: url ? { label: specRef.current.lockedTrialLabel ?? 'Start a free trial', run: () => openTrial(url) } : undefined });\r\n }, [announce, toast]);\r\n\r\n const nameOf = (id: string) => {\r\n const item = itemsRef.current.find((t) => t.id === id);\r\n const s = specRef.current;\r\n if (!item) return s.noun ?? 'item';\r\n return s.name ? s.name(item) : ((item as unknown as { name?: string }).name ?? s.noun ?? 'item');\r\n };\r\n\r\n // `undo`/`redo` and `commit` refer to each other through refs so toasts can call them later.\r\n const undoRef = useRef<() => void>(() => undefined);\r\n const redoRef = useRef<() => void>(() => undefined);\r\n\r\n const commit = (change: C, mode: 'new' | 'undo' | 'redo', entry?: C) => {\r\n if (specRef.current.locked) return refuse();\r\n const seq = ++seqRef.current;\r\n setOverrides((m) => {\r\n const next = new Map(m);\r\n next.set(change.id, { patch: { ...m.get(change.id)?.patch, ...fieldsOf(change, specRef.current) }, seq });\r\n return next;\r\n });\r\n if (mode === 'new') setHistory((h) => pushHistory(h, change));\r\n\r\n const text = specRef.current.describe(change, nameOf);\r\n announce(text);\r\n toast({\r\n text,\r\n tone: 'info',\r\n action: mode === 'undo' ? { label: 'Redo', run: () => redoRef.current() } : { label: 'Undo', run: () => undoRef.current() }\r\n });\r\n\r\n let result: void | Promise<void>;\r\n try {\r\n result = handlerRef.current?.(change);\r\n } catch (err) {\r\n result = Promise.reject(err);\r\n }\r\n if (result && typeof (result as Promise<void>).then === 'function') {\r\n (result as Promise<void>).then(undefined, (err: unknown) => {\r\n setOverrides((m) => {\r\n const o = m.get(change.id);\r\n if (!o || o.seq !== seq) return m; // a newer edit owns this item now\r\n const next = new Map(m);\r\n next.delete(change.id);\r\n return next;\r\n });\r\n setHistory((h) => {\r\n if (mode === 'new') return dropFromHistory(h, change);\r\n if (!entry) return h;\r\n // Put the entry back where it was before the failed undo/redo.\r\n return mode === 'undo'\r\n ? { undo: [...h.undo, entry], redo: h.redo.filter((c) => c !== entry) }\r\n : { undo: h.undo.filter((c) => c !== entry), redo: [...h.redo, entry] };\r\n });\r\n const msg = errorText(err);\r\n toast({ text: `Couldn't save '${nameOf(change.id)}', so it was put back. ${msg}`, tone: 'error' });\r\n announce(`Not saved, put back. ${msg}`);\r\n });\r\n }\r\n };\r\n\r\n const undo = () => {\r\n const step = undoStep(historyRef.current as History<Change<F, string>>, 'undo', specRef.current);\r\n if (!step) return announce('Nothing to undo');\r\n setHistory(() => step.history as History<C>);\r\n commit(step.commit as C, 'undo', step.entry as C);\r\n };\r\n const redo = () => {\r\n const step = redoStep(historyRef.current as History<Change<F, string>>, 'redo');\r\n if (!step) return announce('Nothing to redo');\r\n setHistory(() => step.history as History<C>);\r\n commit(step.commit as C, 'redo', step.entry as C);\r\n };\r\n undoRef.current = undo;\r\n redoRef.current = redo;\r\n\r\n const shown = useMemo(() => applyOverrides(items, overrides), [items, overrides]);\r\n\r\n return {\r\n overrides,\r\n shown,\r\n commit: (change: C) => commit(change, 'new'),\r\n undo,\r\n redo,\r\n canUndo: historyRef.current.undo.length > 0,\r\n canRedo: historyRef.current.redo.length > 0,\r\n toasts,\r\n toast,\r\n dismiss,\r\n announcement,\r\n announce,\r\n /** View-only: Pro actions are refused (see `EditingSpec.locked`). */\r\n locked: !!spec.locked,\r\n /** Call instead of an action that bypasses `commit` (create, add) while `locked`. */\r\n refuse,\r\n /** The locked message once an action was refused, for a quiet status line. */\r\n lockNote: spec.locked ? lockNote : null\r\n };\r\n}\r\n","import * as React from 'react';\n\nexport interface ToastItem {\n id: number;\n text: string;\n tone: 'info' | 'error';\n action?: { label: string; run: () => void };\n}\n\n/**\n * Toasts plus a visually hidden live region. Error toasts use role=\"alert\";\n * every edit is also announced politely so screen-reader users hear the result.\n * Class names are `<prefix>-sr`, `<prefix>-toasts`, `<prefix>-toast…`; each component\n * styles them (the Gantt uses the `nf-g` prefix).\n */\nexport function Toasts({\n items,\n announcement,\n onDismiss,\n prefix = 'nf-g'\n}: {\n items: ToastItem[];\n announcement: { text: string; n: number };\n onDismiss: (id: number) => void;\n prefix?: string;\n}) {\n return (\n <>\n <div className={`${prefix}-sr`} aria-live=\"polite\" aria-atomic=\"true\">\n {/* The key forces a re-announcement when the same text repeats. */}\n <span key={announcement.n}>{announcement.text}</span>\n </div>\n {items.length ? (\n <div className={`${prefix}-toasts`}>\n {items.map((t) => (\n <div key={t.id} className={`${prefix}-toast`} data-tone={t.tone} role={t.tone === 'error' ? 'alert' : 'status'}>\n <span className={`${prefix}-toast-text`}>{t.text}</span>\n {t.action ? (\n <button\n type=\"button\"\n className={`${prefix}-toast-btn`}\n onClick={() => {\n t.action!.run();\n onDismiss(t.id);\n }}\n >\n {t.action.label}\n </button>\n ) : null}\n <button type=\"button\" className={`${prefix}-toast-x`} aria-label=\"Dismiss\" onClick={() => onDismiss(t.id)}>\n ×\n </button>\n </div>\n ))}\n </div>\n ) : null}\n </>\n );\n}\n","import * as React from 'react';\n\n/**\n * One action offered in a component's empty state, e.g. \"Load sample data\" on a Ninth Forge\n * sample table. The host decides when to offer it and runs it; the component only shows it.\n */\nexport interface EmptyAction {\n label: string;\n onClick: () => void;\n /** Running: the button gives way to `status` (\"Loading sample data… 120 of 380\"). */\n busy?: boolean;\n status?: string;\n /** Shown under the button, e.g. what the action does. */\n hint?: string;\n /** The last attempt failed (the button stays, so the user can retry). */\n error?: string;\n}\n\n/**\n * The action's button, progress and error. Class names are `<prefix>-emptyaction` and the\n * component's own button class, so each component styles it like its toolbar.\n */\nexport function EmptyActionView({ action, prefix, buttonClass }: { action: EmptyAction; prefix: string; buttonClass: string }) {\n return (\n <div className={`${prefix}-emptyaction`}>\n {action.busy ? (\n <span role=\"status\">{action.status ?? `${action.label}…`}</span>\n ) : (\n <button type=\"button\" className={buttonClass} onClick={action.onClick}>\n {action.label}\n </button>\n )}\n {action.hint && !action.busy ? <small>{action.hint}</small> : null}\n {action.error ? <span role=\"alert\">{action.error}</span> : null}\n </div>\n );\n}\n","import * as React from 'react';\r\n\r\n/**\r\n * A licence key field offered beside \"Load sample data\" on a Ninth Forge sample table. The host\r\n * decides when to offer it and saves the key; the component only shows it.\r\n */\r\nexport interface LicenceKeyField {\r\n onSave: (key: string) => void;\r\n busy?: boolean;\r\n /** Saved: what happens now. */\r\n message?: string;\r\n /** The key was refused or not saved (the field keeps the text, so the user can fix it). */\r\n error?: string;\r\n}\r\n\r\nlet nextId = 0;\r\n\r\n/**\r\n * The field, its Save button and the result. Class names are `<prefix>-keyfield` and the\r\n * component's own button class, so each component styles it like its toolbar.\r\n */\r\nexport function LicenceKeyFieldView({ field, prefix, buttonClass }: { field: LicenceKeyField; prefix: string; buttonClass: string }) {\r\n const [text, setText] = React.useState('');\r\n const id = React.useRef(`${prefix}-key-${++nextId}`).current;\r\n const save = () => field.onSave(text);\r\n return (\r\n <div className={`${prefix}-keyfield`}>\r\n <label htmlFor={id}>Licence key</label>\r\n <span>\r\n <input\r\n id={id}\r\n type=\"text\"\r\n value={text}\r\n placeholder=\"Paste your trial key\"\r\n autoComplete=\"off\"\r\n spellCheck={false}\r\n disabled={field.busy}\r\n aria-describedby={field.error || field.message ? `${id}-result` : undefined}\r\n onChange={(e) => setText(e.target.value)}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter') save();\r\n }}\r\n />\r\n <button type=\"button\" className={buttonClass} onClick={save} disabled={field.busy}>\r\n {field.busy ? 'Saving…' : 'Save'}\r\n </button>\r\n </span>\r\n {field.error ? (\r\n <span id={`${id}-result`} role=\"alert\">\r\n {field.error}\r\n </span>\r\n ) : field.message ? (\r\n <span id={`${id}-result`} role=\"status\">\r\n {field.message}\r\n </span>\r\n ) : null}\r\n </div>\r\n );\r\n}\r\n","import * as React from 'react';\r\n\r\n/** Where the Plan watermark links when the host doesn't handle the click. */\r\nexport const PLAN_WATERMARK_URL = 'https://ninthforge.com/plan?ref=watermark';\r\n\r\nexport interface WatermarkProps {\r\n /** Class prefix, e.g. `nf-g`: the element gets `${prefix}-mark`. */\r\n prefix: string;\r\n /** The licence message for makers (`licenseMessage(result)`), shown as the title. */\r\n title: string;\r\n /** The brand mark's SVG (`markSvg({ size: 16 })`). Not drawn with `variant: 'text'`. */\r\n markHtml: string;\r\n /** `text`: words only, no coloured mark (Power BI certification). Default `mark`. */\r\n variant?: 'mark' | 'text';\r\n /**\r\n * The host handles the click (Power BI opens links through its own API): a button instead\r\n * of a link.\r\n */\r\n onClick?: () => void;\r\n href?: string;\r\n}\r\n\r\n/** \"Ninth Forge · Unlicensed\", for Plan components without a licence. */\r\nexport function Watermark({ prefix, title, markHtml, variant = 'mark', onClick, href = PLAN_WATERMARK_URL }: WatermarkProps) {\r\n const mark =\r\n variant === 'text' ? null : <span aria-hidden=\"true\" style={{ display: 'inline-flex' }} dangerouslySetInnerHTML={{ __html: markHtml }} />;\r\n return onClick ? (\r\n <button type=\"button\" className={`${prefix}-mark`} title={title} onClick={onClick}>\r\n {mark}\r\n Ninth Forge · Unlicensed\r\n </button>\r\n ) : (\r\n <a className={`${prefix}-mark`} href={href} target=\"_blank\" rel=\"noreferrer\" title={title}>\r\n {mark}\r\n Ninth Forge · Unlicensed\r\n </a>\r\n );\r\n}\r\n\r\n/** The host props every Plan component takes for its watermark. */\r\nexport interface WatermarkHostProps {\r\n /**\r\n * `false` draws no watermark at all (no link, no space). The licence is still checked; the\r\n * host decides when, e.g. Power BI Desktop for authoring (DECISIONS 2026-10-31). Default true.\r\n */\r\n watermark?: boolean;\r\n /** `text`: words only, no coloured mark (Power BI certification). Default `mark`. */\r\n watermarkStyle?: 'mark' | 'text';\r\n /** The host opens the link itself (Power BI's `launchUrl`): the watermark becomes a button. */\r\n onWatermarkClick?: () => void;\r\n}\r\n\r\nlet next = 0;\r\n\r\n/**\r\n * A page-unique id prefix, for React 16 (no `useId`). A counter, not `Math.random`: Power BI\r\n * certification rejects any `Math.random`.\r\n */\r\nexport function nextUid(prefix: string): string {\r\n next += 1;\r\n return `${prefix}${next.toString(36)}`;\r\n}\r\n","export const PREFIX = 'NF1';\n\nexport function utf8(s: string): Uint8Array {\n return new TextEncoder().encode(s);\n}\n\nexport function b64urlEncode(bytes: Uint8Array): string {\n let bin = '';\n bytes.forEach((b) => (bin += String.fromCharCode(b)));\n return btoa(bin).replace(/\\+/g, '-').replace(/\\//g, '_').replace(/=+$/, '');\n}\n\nexport function b64urlDecode(s: string): Uint8Array {\n if (!/^[A-Za-z0-9_-]*$/.test(s)) throw new Error('Invalid base64url');\n const pad = s.length % 4 === 0 ? '' : '='.repeat(4 - (s.length % 4));\n const bin = atob(s.replace(/-/g, '+').replace(/_/g, '/') + pad);\n const out = new Uint8Array(bin.length);\n for (let i = 0; i < bin.length; i++) out[i] = bin.charCodeAt(i);\n return out;\n}\n\nexport function hexToBytes(hex: string): Uint8Array {\n if (!/^[0-9a-f]*$/i.test(hex) || hex.length % 2 !== 0) throw new Error('Invalid hex');\n const out = new Uint8Array(hex.length / 2);\n for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);\n return out;\n}\n\nexport function bytesToHex(bytes: Uint8Array): string {\n return Array.from(bytes, (b) => b.toString(16).padStart(2, '0')).join('');\n}\n\n/** WebCrypto, available in every modern browser, Node 20+, and Cloudflare Workers. */\nexport function subtle(): SubtleCrypto | undefined {\n const c = (globalThis as { crypto?: Crypto }).crypto;\n return c && c.subtle ? c.subtle : undefined;\n}\n\nexport const ED25519 = { name: 'Ed25519' } as const;\n","/** Lowercase a host name and drop any trailing dots (`WWW.Example.com.` → `www.example.com`). */\nexport function normaliseHost(host: string): string {\n return host.trim().toLowerCase().replace(/\\.+$/, '');\n}\n\n/**\n * The page's host name, normalised, or `undefined` outside a browser (SSR, tests, workers\n * without `location`). Components pass it as `host` to `verifyLicense`, so host-bound demo\n * keys work only on the hosts they name. Every component uses this one helper; none reads\n * `location` itself.\n */\nexport function browserHost(): string | undefined {\n const loc = (globalThis as { location?: { hostname?: unknown } }).location;\n const name = loc && typeof loc.hostname === 'string' ? normaliseHost(loc.hostname) : '';\n return name || undefined;\n}\n\n/**\n * Does `host` match a key's `hosts` entry? An exact entry matches only itself. `*.name`\n * matches any subdomain of `name` (`a.name`, `a.b.name`), but not `name` itself and never\n * `evilname`: the dot is part of the suffix.\n */\nexport function hostMatches(host: string, pattern: string): boolean {\n const h = normaliseHost(host);\n const p = normaliseHost(pattern);\n if (!h || !p) return false;\n if (p.startsWith('*.')) {\n const suffix = p.slice(1); // \".name\"\n return suffix.length > 1 && h.length > suffix.length && h.endsWith(suffix);\n }\n return h === p;\n}\n","import { b64urlDecode, ED25519, hexToBytes, PREFIX, subtle, utf8 } from './codec';\r\nimport { hostMatches, normaliseHost } from './browser';\r\nimport type { LicensePayload, LicenseResult, Product, Tier } from './types';\r\n\r\nexport type { LicensePayload, LicenseResult, LicenseStatus, Product, Tier } from './types';\r\nexport { browserHost, hostMatches, normaliseHost } from './browser';\r\n\r\n/** Days a key keeps working after `exp`, with a renewal notice to makers. */\r\nexport const GRACE_DAYS = 14;\r\n\r\n/**\r\n * Products a `suite` key unlocks: the Suite is Plan + Charts (licensing.md step 3, 2026-10-25).\r\n * It does not unlock Driver Tree or any future product; change this deliberately, with tests.\r\n */\r\nexport const SUITE_PRODUCTS: readonly Exclude<Product, 'suite'>[] = ['plan', 'charts'];\r\n\r\n/**\r\n * Production public keys by key id (hex, 32 bytes).\r\n * Generate with `pnpm --filter @ninthforge/license keygen <kid>` and paste the\r\n * PUBLIC key here. Private keys live only in the licence worker's secrets.\r\n */\r\nexport const PUBLIC_KEYS: Record<string, string> = {\r\n 'nf-2027-1': 'ad97c33249018681b3b315a303551442552d917d337690cac9b4783e931f88d1'\r\n};\r\n\r\nexport interface VerifyOptions {\r\n /** The product this component belongs to. */\r\n product: Exclude<Product, 'suite'>;\r\n /**\r\n * Host tenant ID; omit when unknown (code apps in local dev). `null` counts as unknown: the Power\r\n * Apps visual in Power BI passes `orgSettings.tenantId` as null (probe 2, 2026-11-02).\r\n */\r\n tenantId?: string | null;\r\n /**\r\n * The web host the component runs on (use `browserHost()`). Only host-bound demo keys\r\n * (v2) read it; v1 keys ignore it.\r\n */\r\n host?: string;\r\n now?: Date;\r\n /** Override trusted keys (tests, staging). Defaults to PUBLIC_KEYS. */\r\n publicKeys?: Record<string, string>;\r\n}\r\n\r\nconst DAY = 86_400_000;\r\n\r\nfunction isoToUtcMs(iso: string): number {\r\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(iso);\r\n if (!m) return NaN;\r\n return Date.UTC(Number(m[1]), Number(m[2]) - 1, Number(m[3]));\r\n}\r\n\r\nfunction parsePayload(segment: string): LicensePayload | undefined {\r\n try {\r\n const json = JSON.parse(new TextDecoder().decode(b64urlDecode(segment)));\r\n if (!json || typeof json.kid !== 'string' || typeof json.exp !== 'string') return undefined;\r\n if (json.v === 1) return json as LicensePayload;\r\n // v2: host-bound demo key; must name at least one host.\r\n if (\r\n json.v === 2 &&\r\n Array.isArray(json.hosts) &&\r\n json.hosts.length > 0 &&\r\n json.hosts.every((h: unknown) => typeof h === 'string' && h.trim() !== '')\r\n ) {\r\n return json as LicensePayload;\r\n }\r\n } catch {\r\n /* fall through */\r\n }\r\n return undefined;\r\n}\r\n\r\nasync function verifySignature(pubHex: string, message: Uint8Array, signature: Uint8Array): Promise<boolean | 'unsupported'> {\r\n const s = subtle();\r\n if (!s) return 'unsupported';\r\n let key: CryptoKey;\r\n try {\r\n key = await s.importKey('raw', hexToBytes(pubHex) as BufferSource, ED25519, false, ['verify']);\r\n } catch (err) {\r\n // NotSupportedError → the browser lacks Ed25519; anything else is a bad key.\r\n return (err as { name?: string })?.name === 'NotSupportedError' ? 'unsupported' : false;\r\n }\r\n try {\r\n return await s.verify(ED25519, key, signature as BufferSource, message as BufferSource);\r\n } catch {\r\n return false;\r\n }\r\n}\r\n\r\n/**\r\n * Verify a licence key offline. Never throws; never makes a network call.\r\n * Uses the platform's WebCrypto Ed25519, so it adds no bytes to the bundle.\r\n */\r\nexport async function verifyLicense(key: string | null | undefined, opts: VerifyOptions): Promise<LicenseResult> {\r\n const trimmed = (key ?? '').trim();\r\n if (!trimmed) return { status: 'missing', licensed: false };\r\n\r\n const parts = trimmed.split('.');\r\n if (parts.length !== 3 || parts[0] !== PREFIX) return { status: 'invalid', licensed: false };\r\n const [, payloadSeg, sigSeg] = parts as [string, string, string];\r\n\r\n const payload = parsePayload(payloadSeg);\r\n const keys = opts.publicKeys ?? PUBLIC_KEYS;\r\n const pubHex = payload ? keys[payload.kid] : undefined;\r\n if (!payload || !pubHex) return { status: 'invalid', licensed: false };\r\n\r\n let sig: Uint8Array;\r\n try {\r\n sig = b64urlDecode(sigSeg);\r\n } catch {\r\n return { status: 'invalid', licensed: false };\r\n }\r\n\r\n const ok = await verifySignature(pubHex, utf8(payloadSeg), sig);\r\n if (ok === false) return { status: 'invalid', licensed: false };\r\n\r\n // Host-bound demo keys (v2): only on the named hosts, and never where a tenant is known.\r\n // PCF wrappers always pass the Dataverse tenant, so a demo key can't unlock Power Apps.\r\n // Checked before the fail-open below, so an old browser doesn't widen a demo key either.\r\n if (payload.v === 2) {\r\n const host = opts.host === undefined ? '' : normaliseHost(opts.host);\r\n const onHost = host !== '' && (payload.hosts ?? []).some((p) => hostMatches(host, p));\r\n if ((opts.tenantId !== undefined && opts.tenantId !== null) || !onHost) return { status: 'wrong-host', licensed: false, payload };\r\n }\r\n\r\n if (ok === 'unsupported') return { status: 'unverifiable', licensed: true, payload };\r\n\r\n const suiteCovers = payload.pid === 'suite' && SUITE_PRODUCTS.includes(opts.product);\r\n if (payload.pid !== opts.product && !suiteCovers) {\r\n return { status: 'wrong-product', licensed: false, payload };\r\n }\r\n\r\n const tenant = typeof opts.tenantId === 'string' && opts.tenantId.trim() ? opts.tenantId : undefined;\r\n if (payload.v === 1 && payload.tid !== '*' && tenant !== undefined && payload.tid.toLowerCase() !== tenant.toLowerCase()) {\r\n return { status: 'wrong-tenant', licensed: false, payload };\r\n }\r\n\r\n const now = (opts.now ?? new Date()).getTime();\r\n const expMs = isoToUtcMs(payload.exp) + DAY; // inclusive expiry day\r\n if (Number.isNaN(expMs)) return { status: 'invalid', licensed: false };\r\n const daysLeft = Math.floor((expMs - now) / DAY);\r\n\r\n if (now > expMs + GRACE_DAYS * DAY) return { status: 'expired', licensed: false, payload, daysLeft };\r\n if (now > expMs) return { status: 'grace', licensed: true, payload, daysLeft };\r\n return { status: 'valid', licensed: true, payload, daysLeft };\r\n}\r\n\r\n/** How useful a failed result is to the maker: the key meant for this product says most. */\r\nconst FAILURE_RANK: Partial<Record<LicenseResult['status'], number>> = {\r\n expired: 0,\r\n 'wrong-tenant': 1,\r\n 'wrong-host': 2,\r\n 'wrong-product': 3,\r\n invalid: 4,\r\n missing: 5\r\n};\r\n\r\n/**\r\n * Verify one or more keys (separated by spaces, new lines, commas or semicolons), as an\r\n * environment variable holding a tenant's Plan and Charts keys does. Returns the first key\r\n * that licenses this product; otherwise the most telling failure (an expired key for this\r\n * product beats a key for another product). One key behaves exactly like `verifyLicense`.\r\n */\r\nexport async function verifyLicenses(keys: string | null | undefined, opts: VerifyOptions): Promise<LicenseResult> {\r\n const list = (keys ?? '').split(/[\\s,;]+/).filter(Boolean);\r\n if (list.length <= 1) return verifyLicense(list[0], opts);\r\n let best: LicenseResult | undefined;\r\n for (const key of list) {\r\n const r = await verifyLicense(key, opts);\r\n if (r.licensed) return r;\r\n if (!best || (FAILURE_RANK[r.status] ?? 9) < (FAILURE_RANK[best.status] ?? 9)) best = r;\r\n }\r\n return best ?? { status: 'missing', licensed: false };\r\n}\r\n\r\n/** Where a grace-period notice sends makers: the \"find my keys\" page, which renews too. */\r\nexport const RENEW_URL = 'https://ninthforge.com/licence?ref=renewal';\r\n\r\nexport interface RenewalNotice {\r\n /** Short text for a small notice, e.g. \"Licence expired · renew within 9 days\". */\r\n text: string;\r\n /** Full sentence for the notice's title and screen readers. */\r\n detail: string;\r\n href: string;\r\n /** Whole days until the watermark returns (1–14). */\r\n daysLeft: number;\r\n}\r\n\r\n/**\r\n * The renewal notice a component shows **to makers only** while a licence is in grace\r\n * (`docs/specs/licensing.md` step 5), or `null` in every other state. The licence check stays\r\n * offline: this only formats the result. Each component decides who counts as a maker (the PCF\r\n * wrappers pass it in; code apps choose with a prop).\r\n */\r\nexport function renewalNotice(result: LicenseResult | null | undefined, now: Date = new Date()): RenewalNotice | null {\r\n if (!result || result.status !== 'grace' || !result.payload) return null;\r\n const expMs = isoToUtcMs(result.payload.exp) + DAY;\r\n if (Number.isNaN(expMs)) return null;\r\n const daysLeft = Math.min(GRACE_DAYS, Math.max(1, Math.ceil((expMs + GRACE_DAYS * DAY - now.getTime()) / DAY)));\r\n const days = daysLeft === 1 ? '1 day' : `${daysLeft} days`;\r\n return {\r\n text: `Licence expired · renew within ${days}`,\r\n detail: `Your Ninth Forge licence expired on ${result.payload.exp}. Renew within ${days} to keep the watermark off. Only makers see this.`,\r\n href: RENEW_URL,\r\n daysLeft\r\n };\r\n}\r\n\r\n/** Short, human message for makers (never shown to end users). */\r\nexport function licenseMessage(result: LicenseResult): string {\r\n switch (result.status) {\r\n case 'valid':\r\n case 'unverifiable':\r\n return `Licensed to ${result.payload?.org ?? 'your organisation'}`;\r\n case 'grace':\r\n return 'Licence expired — renew within 14 days to keep the watermark off';\r\n case 'expired':\r\n return 'Licence expired';\r\n case 'missing':\r\n return 'Unlicensed — add a licence key in the control properties';\r\n case 'wrong-product':\r\n return 'This key is for a different Ninth Forge product';\r\n case 'wrong-tenant':\r\n return 'This key belongs to a different Microsoft tenant';\r\n case 'wrong-host':\r\n return `This demo key only works on ${result.payload?.hosts?.[0] ?? 'the Ninth Forge website'}`;\r\n default:\r\n return 'Licence key not recognised';\r\n }\r\n}\r\n\r\n/** Paid tiers keep Pro actions after they expire (\"nothing ever breaks\" for customers who lapse). */\r\nconst PAID_TIERS: readonly Tier[] = ['team', 'business', 'enterprise'];\r\n\r\n/**\r\n * True when Pro actions (editing, saving, what-if…) are allowed (Matt, 2026-11-08: \"see\r\n * everything free; a key unlocks the work\"). Valid, grace and unverifiable keys unlock. A paid\r\n * key that has expired still unlocks, with the watermark. Missing, invalid, wrong-product,\r\n * wrong-tenant and wrong-host keys don't, and neither do expired trial, developer or demo keys.\r\n * `null` (still checking) is locked: draw nothing about it until the result is in.\r\n * The watermark is unchanged: `licensed` still decides it.\r\n */\r\nexport function unlocksPro(r: LicenseResult | null | undefined): boolean {\r\n if (!r) return false;\r\n if (r.licensed) return true;\r\n return r.status === 'expired' && !!r.payload && PAID_TIERS.includes(r.payload.tier);\r\n}\r\n","import { useEffect, useState } from 'react';\nimport { browserHost, verifyLicenses, type LicenseResult, type VerifyOptions } from '@ninthforge/license';\n\n/**\n * The options a component verifies with: its product and tenant, plus the page's host from\n * the shared `browserHost()`, so host-bound website demo keys work only on their own hosts.\n */\nexport function licenseOptions(opts: Pick<VerifyOptions, 'product' | 'tenantId'>): VerifyOptions {\n return { product: opts.product, tenantId: opts.tenantId, host: browserHost() };\n}\n\n/**\n * Licence state for a component. `key` may hold several keys (the licence-key environment\n * variable holds a tenant's Plan and Charts keys together). Starts as `null` (checking) so the watermark\n * never flashes for paying customers; verification is offline and async.\n */\nexport function useLicense(key: string | PromiseLike<string | undefined> | undefined, opts: Pick<VerifyOptions, 'product' | 'tenantId'>): LicenseResult | null {\n const [result, setResult] = useState<LicenseResult | null>(null);\n const { product, tenantId } = opts;\n useEffect(() => {\n let live = true;\n // A pending key (PCF reading the licence-key environment variable) keeps the state at\n // \"checking\", so no watermark flashes while it arrives.\n Promise.resolve(key)\n .catch(() => undefined)\n .then((k) => verifyLicenses(k, licenseOptions({ product, tenantId })))\n .then((r) => {\n if (live) setResult(r);\n });\n return () => {\n live = false;\n };\n }, [key, product, tenantId]);\n return result;\n}\n","/**\r\n * Editing rules for the Scheduler — pure functions, no React, so node:test covers them.\r\n * Times are UTC ms inside; changes carry ISO strings so hosts can write them straight to\r\n * Dataverse or an API. The optimistic-save and undo machinery is @ninthforge/editing.\r\n */\r\nimport { dayLabelIn, snap, timeLabel, type Zone } from '@ninthforge/core';\r\nimport { defaultEqual, type ChangeSpec } from '@ninthforge/editing';\r\nimport { toMs, type Item } from './layout';\r\nimport type { ConflictMode, SchedBooking, SchedChange, SchedEditable, SchedFields } from './types';\r\n\r\nconst MINUTE = 60_000;\r\n\r\n/** Start/end compare as instants (an ISO string and the same ms are the same time). */\r\nexport const SCHED_FIELDS: ChangeSpec<SchedFields> = {\r\n keys: ['start', 'end', 'resourceId'],\r\n equal: (key, a, b) => {\r\n if (key === 'resourceId') return (a || null) === (b || null);\r\n const x = toMs(a as number | string | null | undefined);\r\n const y = toMs(b as number | string | null | undefined);\r\n return Number.isNaN(x) || Number.isNaN(y) ? defaultEqual(key, a, b) : x === y;\r\n },\r\n blank: (key) => (key === 'resourceId' ? null : undefined)\r\n};\r\n\r\nconst iso = (ms: number) => new Date(ms).toISOString();\r\n\r\n/* ---------- Permissions ---------- */\r\n\r\nexport type SchedAction = 'move' | 'resize' | 'create' | 'assign';\r\n\r\n/** Whether an action is allowed: it needs a handler, mustn't be switched off, and the booking mustn't be read-only. */\r\nexport function canEdit(editable: SchedEditable | undefined, hasHandler: boolean, action: SchedAction, booking?: Pick<SchedBooking, 'readOnly'>): boolean {\r\n if (!hasHandler || editable === false) return false;\r\n if (booking?.readOnly) return false;\r\n if (typeof editable === 'object' && editable[action] === false) return false;\r\n return true;\r\n}\r\n\r\n/* ---------- Drag maths ---------- */\r\n\r\nexport type DragMode = 'move' | 'start' | 'end';\r\n\r\nexport interface Span {\r\n start: number;\r\n end: number;\r\n}\r\n\r\n/**\r\n * Where a booking lands after dragging its body or an edge by `delta` ms, snapped on the wall\r\n * clock. Moves keep the duration; resizes keep at least one snap step.\r\n */\r\nexport function dragSpan(mode: DragMode, span: Span, delta: number, snapMinutes: number, zone: Zone): Span {\r\n const step = Math.max(1, snapMinutes) * MINUTE;\r\n if (mode === 'move') {\r\n const start = snap(span.start + delta, snapMinutes, zone);\r\n return { start, end: start + (span.end - span.start) };\r\n }\r\n if (mode === 'start') return { start: Math.min(snap(span.start + delta, snapMinutes, zone), span.end - step), end: span.end };\r\n return { start: span.start, end: Math.max(snap(span.end + delta, snapMinutes, zone), span.start + step) };\r\n}\r\n\r\n/** A new booking's span from where a drag across empty time started and ended (either direction). */\r\nexport function createSpan(a: number, b: number, snapMinutes: number, zone: Zone): Span {\r\n const step = Math.max(1, snapMinutes) * MINUTE;\r\n const start = snap(Math.min(a, b), snapMinutes, zone, 'floor');\r\n const end = Math.max(snap(Math.max(a, b), snapMinutes, zone, 'ceil'), start + step);\r\n return { start, end };\r\n}\r\n\r\n/* ---------- Building changes ---------- */\r\n\r\n/** The change that puts `booking` at `span` on `resourceId`, or null if nothing changes. */\r\nexport function placeChange(\r\n booking: SchedBooking,\r\n span: Span,\r\n resourceId: string | null,\r\n source: SchedChange['source']\r\n): SchedChange | null {\r\n const next: Partial<SchedFields> = {};\r\n const prev: Partial<SchedFields> = {};\r\n if (toMs(booking.start) !== span.start) {\r\n next.start = iso(span.start);\r\n prev.start = typeof booking.start === 'number' ? iso(booking.start) : booking.start;\r\n }\r\n if (toMs(booking.end) !== span.end) {\r\n next.end = iso(span.end);\r\n prev.end = typeof booking.end === 'number' ? iso(booking.end) : booking.end;\r\n }\r\n if ((booking.resourceId || null) !== (resourceId || null)) {\r\n next.resourceId = resourceId;\r\n prev.resourceId = booking.resourceId || null;\r\n }\r\n if (!Object.keys(next).length) return null;\r\n return { id: booking.id, ...next, previous: prev, source };\r\n}\r\n\r\n/* ---------- Keyboard ---------- */\r\n\r\nexport type KeyEdit = { kind: 'time'; mode: DragMode; dir: 1 | -1 } | { kind: 'resource'; dir: 1 | -1 };\r\n\r\n/**\r\n * Alt+←/→ moves a booking in time, Alt+↑/↓ to the previous/next resource, Alt+Shift+←/→\r\n * moves its end (the Gantt's scheme). Ctrl/Cmd combinations are left to the browser.\r\n */\r\nexport function keyEdit(e: { key: string; altKey: boolean; shiftKey: boolean; ctrlKey: boolean; metaKey: boolean }): KeyEdit | null {\r\n if (!e.altKey || e.ctrlKey || e.metaKey) return null;\r\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') return { kind: 'time', mode: e.shiftKey ? 'end' : 'move', dir: e.key === 'ArrowRight' ? 1 : -1 };\r\n if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !e.shiftKey) return { kind: 'resource', dir: e.key === 'ArrowDown' ? 1 : -1 };\r\n return null;\r\n}\r\n\r\n/* ---------- Conflicts and seats ---------- */\r\n\r\n/** Live (non-cancelled) items on `resourceId` that `span` would overlap, other than `id` itself. */\r\nexport function overlapping(items: readonly Item[], id: string, resourceId: string | null, span: Span): Item[] {\r\n if (!resourceId) return [];\r\n return items.filter((i) => i.id !== id && !i.cancelled && i.resourceId === resourceId && i.start < span.end && i.end > span.start);\r\n}\r\n\r\nexport type ConflictVerdict = { ok: true; warning?: string } | { ok: false; reason: string };\r\n\r\n/**\r\n * Whether a booking may land there under `mode`: `allow` always; `warn` allows with a\r\n * warning; `block` refuses overlaps. Over-seat room bookings always get a warning.\r\n */\r\nexport function checkPlacement(\r\n mode: ConflictMode,\r\n booking: SchedBooking,\r\n others: readonly Item[],\r\n resource: { name: string; seats?: number } | undefined\r\n): ConflictVerdict {\r\n const notes: string[] = [];\r\n if (others.length && mode !== 'allow') {\r\n const names = others.slice(0, 2).map((o) => `'${o.booking.name}'`).join(' and ');\r\n const more = others.length > 2 ? ` and ${others.length - 2} more` : '';\r\n if (mode === 'block') return { ok: false, reason: `${resource?.name ?? 'That'} is already booked then (${names}${more}).` };\r\n notes.push(`overlaps ${names}${more}`);\r\n }\r\n if (resource?.seats && booking.attendees && booking.attendees > resource.seats) notes.push(`${booking.attendees} people for ${resource.seats} seats`);\r\n return notes.length ? { ok: true, warning: notes.join('; ') } : { ok: true };\r\n}\r\n\r\n/* ---------- Words ---------- */\r\n\r\n/** \"Tue 27 Oct 09:00–11:00\", or with both days when it spans more than one. */\r\nexport function whenLabel(start: number, end: number, zone: Zone): string {\r\n const d1 = dayLabelIn(start, zone);\r\n const d2 = dayLabelIn(end - 1, zone);\r\n return d1 === d2 ? `${d1} ${timeLabel(start, zone)}–${timeLabel(end, zone)}` : `${d1} ${timeLabel(start, zone)} – ${dayLabelIn(end, zone)} ${timeLabel(end, zone)}`;\r\n}\r\n\r\n/** Screen-reader and toast text for a committed change. */\r\nexport function describeChange(\r\n change: SchedChange,\r\n ctx: { name: (id: string) => string; resource: (id: string | null) => string; booking: (id: string) => SchedBooking | undefined; zone: Zone }\r\n): string {\r\n const who = `'${ctx.name(change.id)}'`;\r\n const prefix = change.source === 'undo' ? 'Undone: ' : change.source === 'redo' ? 'Redone: ' : '';\r\n const b = ctx.booking(change.id);\r\n const start = toMs(change.start ?? b?.start);\r\n const end = toMs(change.end ?? b?.end);\r\n const when = Number.isNaN(start) || Number.isNaN(end) ? '' : whenLabel(start, end, ctx.zone);\r\n let text: string;\r\n if ('resourceId' in change) {\r\n text = change.resourceId\r\n ? `${who} ${change.previous.resourceId ? 'moved' : 'assigned'} to ${ctx.resource(change.resourceId)}${when ? `, ${when}` : ''}`\r\n : `${who} unassigned`;\r\n } else if ('start' in change && 'end' in change) text = `${who} moved to ${when}`;\r\n else if ('end' in change) text = `${who} now ends ${timeLabel(end, ctx.zone)} (${when})`;\r\n else text = `${who} now starts ${timeLabel(start, ctx.zone)} (${when})`;\r\n return prefix + text;\r\n}\r\n","import * as React from 'react';\nimport { useEffect, useState, type KeyboardEvent } from 'react';\nimport { dayLabelIn, dayToInstant, type Zone } from '@ninthforge/core';\nimport { agendaTime, buildRows, unassigned, type DayUtil, type Item, type ResourceRow } from './layout';\nimport type { SchedBooking, SchedResource, SchedulerLabels } from './types';\n\nexport interface AgendaProps {\n uid: string;\n /** Accessible name: \"Field service, 26 Oct – 1 Nov 2026\". */\n label: string;\n resources: readonly SchedResource[];\n /** What the user sees (host data with pending edits). */\n items: readonly Item[];\n startDay: number;\n days: number;\n zone: Zone;\n todayDay: number;\n filter: string;\n labels: SchedulerLabels;\n selected: string | null;\n appearance: ReadonlyMap<string, { color: string; style: string }>;\n /** The full sentence for screen readers (\"Install #1021, Will Grant, Wed 28 Oct, 09:00 to 11:00, …\"). */\n describe: (it: Item) => string;\n /** Booked vs capacity for one resource on one day; undefined on non-working days. */\n capacityOf: (row: ResourceRow, day: number) => DayUtil | undefined;\n onSelect: (id: string) => void;\n onOpen?: (booking: SchedBooking) => void;\n dir?: 'rtl';\n}\n\n/** Above this many bookings in the 7 days, Agenda draws the first day first. */\nconst PROGRESSIVE_OVER = 400;\n\ninterface DayGroup {\n day: number;\n rows: ResourceRow[];\n queue: Item[];\n}\n\n/**\n * Agenda: bookings as a list, grouped by day and then by resource, for narrow screens and\n * for reading a plan aloud. Selection and opening work as in the grid; moving and resizing\n * stay in the grid views.\n */\nexport function Agenda(props: AgendaProps) {\n const { zone, startDay, days } = props;\n const groups: DayGroup[] = [];\n for (let day = startDay; day < startDay + days; day++) {\n const from = dayToInstant(day, zone);\n const to = dayToInstant(day + 1, zone);\n const rows = buildRows(props.resources, props.items, { from, to, groupBy: false, filter: props.filter }).filter(\n (r): r is ResourceRow => r.kind === 'resource' && r.items.length > 0\n );\n const queue = props.filter ? [] : unassigned(props.items, from, to);\n if (rows.length || queue.length) groups.push({ day, rows, queue });\n }\n\n // Big boards (300 people × a week is ~1,500 bookings, ~140 ms to draw): draw the first day\n // now and the rest on the next task, so switching to Agenda never stalls a frame for long.\n const total = groups.reduce((n, g) => n + g.queue.length + g.rows.reduce((m, r) => m + r.items.length, 0), 0);\n const [fullFor, setFullFor] = useState<number | null>(null);\n const progressive = total > PROGRESSIVE_OVER && fullFor !== startDay;\n useEffect(() => {\n if (!progressive) return;\n // After the next paint (a task queued from rAF runs after it), with a timeout fallback\n // for hidden tabs, where rAF doesn't run.\n let after: ReturnType<typeof setTimeout> | undefined;\n const done = () => setFullFor(startDay);\n const raf = typeof requestAnimationFrame === 'function' ? requestAnimationFrame(() => (after = setTimeout(done, 0))) : 0;\n const fallback = setTimeout(done, 150);\n return () => {\n if (raf && typeof cancelAnimationFrame === 'function') cancelAnimationFrame(raf);\n if (after) clearTimeout(after);\n clearTimeout(fallback);\n };\n }, [progressive, startDay]);\n const shownGroups = progressive ? groups.slice(0, 1) : groups;\n\n // Up/Down walk every booking in reading order; Home/End jump; Enter opens.\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\n const list = Array.from(e.currentTarget.querySelectorAll<HTMLButtonElement>('.nf-s-ag-item'));\n const i = list.indexOf(document.activeElement as HTMLButtonElement);\n const to = (n: number) => {\n const el = list[Math.max(0, Math.min(list.length - 1, n))];\n if (!el) return;\n el.focus();\n props.onSelect(el.dataset.id ?? '');\n };\n if (e.key === 'ArrowDown') to(i + 1);\n else if (e.key === 'ArrowUp') to(i < 0 ? 0 : i - 1);\n else if (e.key === 'Home') to(0);\n else if (e.key === 'End') to(list.length - 1);\n else return;\n e.preventDefault();\n };\n\n // One booking is in the tab order: the selected one, else the first.\n const all = shownGroups.flatMap((g) => [...g.rows.flatMap((r) => r.items), ...g.queue]);\n const tabId = all.some((it) => it.id === props.selected) ? props.selected : all[0]?.id;\n let tabTaken = false;\n\n const item = (it: Item, day: number, conflict: boolean) => {\n const look = props.appearance.get(it.id);\n const b = it.booking;\n const meta = [b.location, b.status, it.kind === 'unavailable' ? 'Unavailable' : ''].filter(Boolean).join(' · ');\n const tabbable = !tabTaken && it.id === tabId;\n if (tabbable) tabTaken = true;\n return (\n <li key={`${day}-${it.id}`}>\n <button\n type=\"button\"\n className=\"nf-s-ag-item\"\n data-id={it.id}\n data-selected={props.selected === it.id ? '' : undefined}\n data-cancelled={it.cancelled ? '' : undefined}\n tabIndex={tabbable ? 0 : -1}\n onClick={() => props.onSelect(it.id)}\n onDoubleClick={() => props.onOpen?.(b)}\n onKeyDown={(e) => {\n if (e.key === 'Enter') {\n props.onOpen?.(b);\n e.preventDefault();\n }\n }}\n >\n <span className=\"nf-s-ag-time\" aria-hidden=\"true\">\n {agendaTime(it, day, zone)}\n </span>\n <span className=\"nf-s-ag-sw\" aria-hidden=\"true\" data-style={look?.style} style={{ background: look?.style === 'unavailable' ? undefined : look?.color, borderColor: look?.color }} />\n <span aria-hidden=\"true\">\n <span className=\"nf-s-ag-name\">{b.name}</span>\n {meta || conflict ? (\n <span className=\"nf-s-ag-meta\">\n {meta}\n {conflict ? <span className=\"nf-s-ag-warn\">{meta ? ' · ' : ''}Overlaps</span> : null}\n </span>\n ) : null}\n </span>\n <span className=\"nf-s-sr\">\n {props.describe(it)}\n {conflict ? ', overlaps another booking' : ''}\n </span>\n </button>\n </li>\n );\n };\n\n return (\n <div className=\"nf-s-agenda\" role=\"region\" aria-label={props.label} dir={props.dir} onKeyDown={onKeyDown}>\n {groups.length === 0 ? (\n <div className=\"nf-s-ag-empty\">{props.filter ? `Nothing matches “${props.filter}”.` : `No ${props.labels.bookings.toLowerCase()} in these 7 days.`}</div>\n ) : null}\n {shownGroups.map((g) => {\n const today = g.day === props.todayDay;\n const head = `${props.uid}-ag-${g.day}`;\n return (\n // A labelled group, not a heading: the component can't know the host page's heading levels.\n <div key={g.day} role=\"group\" aria-labelledby={head}>\n <div id={head} className=\"nf-s-ag-day\" data-today={today ? '' : undefined}>\n {dayLabelIn(dayToInstant(g.day, zone), zone)}\n {today ? ' · Today' : ''}\n </div>\n {g.rows.map((row) => {\n const cap = props.capacityOf(row, g.day);\n return (\n <div key={row.key} className=\"nf-s-ag-group\">\n <div className=\"nf-s-ag-res\">\n <b>{row.resource.name}</b>\n {cap ? (\n <span className=\"nf-s-cap\" data-level={cap.level === 'over' ? 'over' : 'ok'}>\n {cap.booked} / {cap.capacity} h{cap.level === 'over' ? <span className=\"nf-s-sr\">, over capacity</span> : null}\n </span>\n ) : null}\n </div>\n <ul className=\"nf-s-ag-list\" aria-label={`${row.resource.name}, ${dayLabelIn(dayToInstant(g.day, zone), zone)}`}>\n {row.items.map((it) => item(it, g.day, row.conflicts.has(it.id)))}\n </ul>\n </div>\n );\n })}\n {g.queue.length ? (\n <div className=\"nf-s-ag-group\">\n <div className=\"nf-s-ag-res\">\n <b>Unassigned</b>\n </div>\n <ul className=\"nf-s-ag-list\" aria-label={`Unassigned, ${dayLabelIn(dayToInstant(g.day, zone), zone)}`}>\n {g.queue.map((it) => item(it, g.day, false))}\n </ul>\n </div>\n ) : null}\n </div>\n );\n })}\n </div>\n );\n}\n","import { brand, motion } from '@ninthforge/tokens';\r\nimport { GROUP_H, HEADER_H, LANE_H, RESOURCE_W } from './layout';\r\n\r\n/** Scoped styles. Every colour comes from a --nf-* variable so customers can theme it. */\r\nexport const css = `\r\n.nf-sched{--nf-head:${HEADER_H}px;--nf-res:${RESOURCE_W}px;--nf-lane:${LANE_H}px;--nf-now:${brand.ember};position:relative;isolation:isolate;display:flex;flex-direction:column;width:100%;box-sizing:border-box;min-height:0;height:100%;box-sizing:border-box;background:var(--nf-surface);color:var(--nf-ink);font-family:var(--nf-font);font-size:var(--nf-text-body);line-height:1.3;border:1px solid var(--nf-line);border-radius:16px;overflow:hidden;-webkit-font-smoothing:antialiased}\r\n.nf-sched *,.nf-sched *::before,.nf-sched *::after{box-sizing:border-box}\r\n.nf-s-toolbar{display:flex;align-items:center;gap:10px;flex-wrap:wrap;min-height:60px;padding:10px 16px;border-bottom:1px solid var(--nf-line)}\r\n.nf-s-title{font-weight:600;font-size:var(--nf-text-title);letter-spacing:-0.01em;margin-inline-end:6px}\r\n.nf-s-range{font-weight:500;font-size:var(--nf-text-body);min-width:0}\r\n.nf-s-seg{display:inline-flex;flex-wrap:wrap;gap:2px;padding:3px;border-radius:9px;background:var(--nf-line-soft);max-width:100%}\r\n.nf-s-seg .nf-s-btn{white-space:nowrap;flex:none}\r\n.nf-s-btn{font:inherit;font-size:var(--nf-text-body);border:1px solid var(--nf-line);background:var(--nf-surface);color:var(--nf-ink);padding:7px 12px;border-radius:9px;cursor:pointer;min-height:32px;transition:background ${motion.fast} ${motion.ease}}\r\n.nf-s-btn:hover{background:var(--nf-line-soft)}\r\n.nf-s-btn[disabled]{opacity:.5;cursor:default}\r\n.nf-s-icon{padding:7px 10px;min-width:32px}\r\n.nf-s-seg .nf-s-btn{border:0;background:transparent;color:var(--nf-muted);padding:6px 12px;border-radius:7px}\r\n.nf-s-seg .nf-s-btn[aria-pressed=\"true\"]{background:var(--nf-surface);color:var(--nf-ink);font-weight:500;box-shadow:0 1px 2px rgba(14,17,22,.12)}\r\n.nf-s-spacer{flex:1}\r\n.nf-s-filter{font:inherit;font-size:var(--nf-text-body);min-height:32px;width:calc(var(--nf-text-body) * 170 / 13);max-width:40vw;padding:6px 10px;border-radius:9px;border:1px solid var(--nf-line);background:var(--nf-surface);color:var(--nf-ink)}\r\n.nf-s-switch{display:inline-flex;align-items:center;gap:8px;font:inherit;font-size:var(--nf-text-body);color:var(--nf-ink);background:none;border:0;padding:6px 4px;cursor:pointer;border-radius:8px}\r\n.nf-s-switch-track{position:relative;width:30px;height:18px;border-radius:9px;background:var(--nf-line);transition:background ${motion.fast} ${motion.ease}}\r\n.nf-s-switch-track::after{content:\"\";position:absolute;top:2px;left:2px;width:14px;height:14px;border-radius:7px;background:#fff;transition:transform ${motion.base} ${motion.ease}}\r\n.nf-s-switch[aria-pressed=\"true\"] .nf-s-switch-track{background:var(--nf-accent)}\r\n.nf-s-switch[aria-pressed=\"true\"] .nf-s-switch-track::after{transform:translateX(12px)}\r\n.nf-s-count{display:inline-block;min-width:20px;padding:1px 6px;margin-inline-start:6px;border-radius:9px;background:var(--nf-accent-solid,var(--nf-accent));color:var(--nf-accent-text);font-size:var(--nf-text-caption);font-weight:600;text-align:center}\r\n.nf-s-btn:focus-visible,.nf-s-switch:focus-visible,.nf-s-filter:focus-visible,.nf-s-scroll:focus-visible,.nf-s-grouphead:focus-visible,.nf-s-un-item:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-s-main{position:relative;flex:1;min-height:0;display:flex}\r\n.nf-s-scroll{position:relative;flex:1;min-width:0;overflow:auto;outline:none}\r\n.nf-s-agenda{position:relative;flex:1;min-width:0;overflow:auto;padding:0 16px 16px}\r\n.nf-s-agenda>*{max-width:720px}\r\n.nf-s-ag-day{position:sticky;top:0;z-index:1;margin:0;padding:14px 0 8px;background:var(--nf-surface);border-bottom:1px solid var(--nf-line);font-size:var(--nf-text-header);font-weight:600;color:var(--nf-ink)}\r\n.nf-s-ag-day[data-today]{box-shadow:inset 0 -2px 0 var(--nf-accent)}\r\n.nf-s-ag-res{display:flex;align-items:baseline;justify-content:space-between;gap:8px;padding:12px 0 6px;font-size:var(--nf-text-value);color:var(--nf-muted)}\r\n.nf-s-ag-res b{font-weight:600;color:var(--nf-ink)}\r\n.nf-s-ag-list{list-style:none;margin:0;padding:0;display:grid;gap:6px}\r\n.nf-s-ag-item{display:grid;grid-template-columns:minmax(76px,auto) 4px 1fr;align-items:start;gap:10px;width:100%;min-height:44px;text-align:start;font:inherit;font-size:var(--nf-text-body);line-height:1.35;color:var(--nf-ink);background:var(--nf-surface);border:1px solid var(--nf-line-soft);border-radius:8px;padding:8px 10px;cursor:pointer}\r\n.nf-s-ag-item:hover{border-color:var(--nf-line)}\r\n.nf-s-ag-item[data-selected]{border-color:var(--nf-accent);box-shadow:0 0 0 1px var(--nf-accent)}\r\n.nf-s-ag-item:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-s-ag-time{font-family:var(--nf-mono-font);font-size:var(--nf-text-value);color:var(--nf-muted);padding-top:1px;white-space:nowrap}\r\n.nf-s-ag-sw{align-self:stretch;width:4px;border-radius:2px;border:0 solid}\r\n.nf-s-ag-sw[data-style=\"tentative\"]{background:transparent!important;border-width:2px;border-style:dashed}\r\n.nf-s-ag-sw[data-style=\"unavailable\"]{background:repeating-linear-gradient(135deg,var(--nf-muted) 0 2px,transparent 2px 5px)}\r\n.nf-s-ag-sw[data-style=\"done\"]{opacity:.45}\r\n.nf-s-ag-name{font-weight:500}\r\n.nf-s-ag-item[data-cancelled] .nf-s-ag-name{text-decoration:line-through;color:var(--nf-muted)}\r\n.nf-s-ag-meta{display:block;margin-top:2px;font-size:var(--nf-text-callout);color:var(--nf-muted)}\r\n.nf-s-ag-warn{color:var(--nf-ink);text-decoration:underline 2px var(--nf-negative);text-underline-offset:3px}\r\n.nf-s-ag-empty{padding:32px 0;color:var(--nf-muted);font-size:var(--nf-text-body)}\r\n.nf-s-head{position:sticky;top:0;z-index:4;display:flex;height:var(--nf-head);background:var(--nf-surface);border-bottom:1px solid var(--nf-line)}\r\n.nf-s-corner{position:sticky;left:0;z-index:5;flex:none;width:var(--nf-res);display:flex;align-items:flex-end;padding:0 16px 10px;background:var(--nf-surface);border-right:1px solid var(--nf-line);font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);letter-spacing:.06em;color:var(--nf-muted);text-transform:uppercase}\r\n.nf-s-axis{position:relative;flex:none}\r\n.nf-s-day{position:absolute;top:0;height:calc(var(--nf-text-label) * 28 / 12);padding:8px 10px 0;border-left:1px solid var(--nf-line);font-size:var(--nf-text-label);font-weight:600;white-space:nowrap;overflow:hidden}\r\n.nf-s-day[data-today]{color:var(--nf-ink);font-weight:600;box-shadow:inset 0 -2px 0 var(--nf-accent)}\r\n.nf-s-hour{position:absolute;top:calc(var(--nf-text-label) * 32 / 12);padding-left:4px;font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);color:var(--nf-muted);white-space:nowrap}\r\n.nf-s-body{position:relative}\r\n.nf-s-layer{position:absolute;top:0;bottom:0;pointer-events:none}\r\n.nf-s-off{position:absolute;top:0;bottom:0;background:var(--nf-line-soft)}\r\n.nf-s-vline{position:absolute;top:0;bottom:0;width:1px;background:var(--nf-line-soft)}\r\n.nf-s-vline[data-day]{background:var(--nf-line)}\r\n.nf-s-nowline{position:absolute;top:0;bottom:0;width:2px;margin-left:-1px;background:var(--nf-now);z-index:2}\r\n.nf-s-row{position:absolute;left:0;display:flex}\r\n.nf-s-row[aria-selected=\"true\"] .nf-s-res{background:var(--nf-selected)}\r\n.nf-s-res{position:sticky;left:0;z-index:3;flex:none;width:var(--nf-res);display:flex;align-items:center;gap:10px;padding:0 12px 0 16px;background:var(--nf-surface);border-right:1px solid var(--nf-line);border-bottom:1px solid var(--nf-line-soft)}\r\n.nf-s-avatar{flex:none;width:calc(var(--nf-text-caption) * 28 / 11);height:calc(var(--nf-text-caption) * 28 / 11);border-radius:50%;display:inline-flex;align-items:center;justify-content:center;font-size:var(--nf-text-caption);font-weight:600;background:var(--nf-accent-tint);color:var(--nf-ink);overflow:hidden}\r\n.nf-s-avatar img{width:100%;height:100%;object-fit:cover}\r\n.nf-s-who{min-width:0;flex:1;display:flex;flex-direction:column;gap:1px}\r\n.nf-s-name{font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\r\n.nf-s-sub{font-size:var(--nf-text-callout);color:var(--nf-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\r\n.nf-s-cap{flex:none;font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);color:var(--nf-muted);text-align:end}\r\n.nf-s-cap[data-level=\"over\"]{color:var(--nf-ink);font-weight:600;text-decoration:underline 2px var(--nf-negative);text-underline-offset:3px}\r\n.nf-s-lanes{position:relative;flex:none;border-bottom:1px solid var(--nf-line-soft)}\r\n.nf-s-overday{position:absolute;top:0;bottom:0;pointer-events:none;background:repeating-linear-gradient(135deg,var(--nf-negative) 0 1px,transparent 1px 7px);opacity:.16}\r\n.nf-s-util{position:absolute;bottom:2px;height:3px;border-radius:2px;background:var(--nf-line-soft);overflow:hidden;pointer-events:none}\r\n.nf-s-util>i{display:block;height:100%;border-radius:inherit;background:var(--nf-accent);opacity:.45}\r\n.nf-s-util>i[data-level=\"near\"]{opacity:1}\r\n.nf-s-util>i[data-level=\"over\"]{background:var(--nf-negative);opacity:1}\r\n.nf-s-group{position:absolute;left:0;height:var(--nf-group,${GROUP_H}px);display:flex;align-items:center;background:var(--nf-surface-alt);border-bottom:1px solid var(--nf-line)}\r\n.nf-s-groupcell{position:sticky;left:0;height:100%;display:flex}\r\n.nf-s-grouphead{display:inline-flex;align-items:center;gap:8px;height:100%;padding:0 16px;font:inherit;font-size:var(--nf-text-label);font-weight:600;letter-spacing:.02em;color:var(--nf-ink);background:none;border:0;cursor:pointer}\r\n.nf-s-grouphead svg{transition:transform ${motion.fast} ${motion.ease}}\r\n.nf-s-grouphead[aria-expanded=\"false\"] svg{transform:rotate(-90deg)}\r\n.nf-sched[data-rtl] .nf-s-grouphead[aria-expanded=\"false\"] svg{transform:rotate(90deg)}\r\n.nf-s-groupcount{font-weight:400;color:var(--nf-muted)}\r\n.nf-s-bk{position:absolute;height:calc(var(--nf-lane) - 4px);border-radius:6px;padding:0 8px;display:flex;align-items:center;gap:6px;font-size:var(--nf-text-value);line-height:1.15;white-space:nowrap;overflow:hidden;cursor:pointer;background:var(--bk);color:var(--bk-text);border:1px solid var(--bk);transform-origin:left center;animation:nf-s-in ${motion.base} ${motion.ease} both}\r\n.nf-s-bk b{font-weight:600;overflow:hidden;text-overflow:ellipsis}\r\n.nf-s-bk span{overflow:hidden;text-overflow:ellipsis;font-weight:400}\r\n.nf-s-bk[data-style=\"tentative\"]{background:color-mix(in srgb,var(--bk) 16%,var(--nf-surface));color:var(--nf-ink);border-style:dashed}\r\n.nf-s-bk[data-style=\"done\"]{background:var(--nf-line-soft);color:var(--nf-ink);border-color:var(--nf-line);box-shadow:inset 3px 0 0 var(--bk)}\r\n.nf-s-bk[data-style=\"cancelled\"]{background:var(--nf-surface);color:var(--nf-muted);border:1px dashed var(--nf-line);text-decoration:line-through}\r\n.nf-s-bk[data-style=\"unavailable\"]{background:repeating-linear-gradient(135deg,var(--nf-line-soft) 0 6px,var(--nf-surface) 6px 12px);color:var(--nf-ink);border-color:var(--nf-line)}\r\n.nf-s-bk[data-conflict]::after{content:\"\";position:absolute;left:0;right:0;top:0;height:3px;background:var(--nf-negative)}\r\n.nf-s-bk[data-overseat]::before{content:\"\";position:absolute;left:0;right:0;bottom:0;height:3px;background:var(--nf-negative)}\r\n.nf-s-seats{flex:none;order:2;margin-inline-start:auto;padding:1px 5px;border-radius:4px;background:var(--nf-surface);color:var(--nf-ink);box-shadow:inset 0 0 0 1px var(--nf-negative);font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);font-weight:600}\r\n.nf-s-bk[aria-selected=\"true\"]{box-shadow:0 0 0 2px var(--nf-surface),0 0 0 4px var(--nf-accent);z-index:1}\r\n.nf-s-settled .nf-s-bk{animation:none}\r\n.nf-sched[data-narrow] .nf-s-unassigned{position:absolute;inset:0;z-index:6;width:auto;border-left:0}\r\n.nf-s-unassigned{flex:none;width:var(--nf-unassigned-w,280px);display:flex;flex-direction:column;border-left:1px solid var(--nf-line);background:var(--nf-surface)}\r\n.nf-s-un-head{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border-bottom:1px solid var(--nf-line);font-weight:600}\r\n/* RTL hosts: numbers, times and ranges stay in reading order (\"0 / 8 h\", \"08:00–09:00\"). */\r\n.nf-s-cap,.nf-s-count,.nf-s-ag-time,.nf-s-ltr,.nf-s-range{direction:ltr;unicode-bidi:isolate}\r\n.nf-s-menu [role=\"menuitem\"]{unicode-bidi:plaintext}\r\n.nf-s-menuwrap{position:relative;display:inline-flex}\r\n.nf-s-menu{position:absolute;top:calc(100% + 6px);right:0;z-index:9;min-width:180px;display:flex;flex-direction:column;padding:4px;border-radius:10px;border:1px solid var(--nf-line);background:var(--nf-surface);box-shadow:0 8px 24px rgba(14,17,22,.14)}\r\n.nf-s-menu [role=\"menuitem\"]{font:inherit;font-size:var(--nf-text-body);text-align:start;color:var(--nf-ink);background:none;border:0;border-radius:7px;padding:8px 10px;cursor:pointer}\r\n.nf-s-menu [role=\"menuitem\"]:hover,.nf-s-menu [role=\"menuitem\"]:focus-visible{background:var(--nf-line-soft);outline:none}\r\n.nf-s-menu [role=\"menuitem\"]:focus-visible{box-shadow:inset 0 0 0 2px var(--nf-focus)}\r\n.nf-s-un-tools{display:flex;gap:6px;padding:8px 8px 0}\r\n.nf-s-un-tools .nf-s-filter{flex:1;min-width:0;width:auto;max-width:none}\r\n.nf-s-pick{display:inline-flex;align-items:center;gap:6px;font-size:var(--nf-text-body);color:var(--nf-muted)}\r\n.nf-s-pick select{font:inherit;font-size:var(--nf-text-body);min-height:32px;padding:5px 8px;border-radius:9px;border:1px solid var(--nf-line);background:var(--nf-surface);color:var(--nf-ink);cursor:pointer}\r\n.nf-s-pick select:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-s-un-list{flex:1;overflow:auto;padding:8px;display:flex;flex-direction:column;gap:6px;margin:0;list-style:none}\r\n.nf-s-un-day{font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);letter-spacing:.06em;text-transform:uppercase;color:var(--nf-muted);padding:8px 8px 2px}\r\n.nf-s-un-item{display:block;width:100%;text-align:start;font:inherit;font-size:var(--nf-text-body);padding:8px 10px;border-radius:9px;border:1px solid var(--nf-line);border-left:3px solid var(--nf-accent);background:var(--nf-surface);color:var(--nf-ink);cursor:pointer}\r\n.nf-s-un-item[aria-pressed=\"true\"]{background:var(--nf-selected);border-color:var(--nf-accent)}\r\n.nf-s-un-item small{display:block;color:var(--nf-muted);font-size:var(--nf-text-callout);margin-top:2px}\r\n.nf-s-un-empty{padding:16px;color:var(--nf-muted);font-size:var(--nf-text-body)}\r\n.nf-s-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\r\n.nf-s-empty{position:absolute;left:var(--nf-res);right:0;top:40px;text-align:center;color:var(--nf-muted);font-size:var(--nf-text-body)}.nf-s-emptyaction{display:flex;flex-direction:column;align-items:center;gap:8px;margin-top:12px;color:var(--nf-muted);font-size:var(--nf-text-body)}.nf-s-emptyaction small{font-size:var(--nf-text-callout);max-width:360px}.nf-s-emptyaction [role=\"alert\"]{color:var(--nf-negative,#D93F2B)}\r\n.nf-s-bk{user-select:none;touch-action:none}\r\n.nf-s-bk[data-editable]{cursor:grab}\r\n.nf-s-dragging,.nf-s-dragging *{cursor:grabbing!important;user-select:none}\r\n.nf-s-bk[data-preview]{box-shadow:0 6px 18px rgba(14,17,22,.22);z-index:2}\r\n.nf-s-h{position:absolute;top:0;bottom:0;width:8px;cursor:ew-resize;z-index:1}\r\n.nf-s-h-l{left:0}\r\n.nf-s-h-r{right:0}\r\n.nf-s-bk:hover .nf-s-h::after,.nf-s-bk[aria-selected=\"true\"] .nf-s-h::after{content:\"\";position:absolute;top:6px;bottom:6px;left:3px;width:2px;border-radius:1px;background:currentColor;opacity:.55}\r\n.nf-s-lanes[data-create]{cursor:crosshair}\r\n.nf-s-ghost{position:absolute;height:calc(var(--nf-lane) - 4px);border-radius:6px;border:1.5px dashed var(--nf-accent);background:var(--nf-accent-tint);color:var(--nf-ink);font-size:var(--nf-text-caption);font-family:var(--nf-mono-font);padding:0 8px;display:flex;align-items:center;white-space:nowrap;overflow:hidden;pointer-events:none}\r\n.nf-s-draglabel{position:absolute;z-index:5;padding:3px 8px;border-radius:6px;background:var(--nf-tooltip-bg);color:var(--nf-tooltip-ink);font-size:var(--nf-text-caption);font-family:var(--nf-mono-font);white-space:nowrap;pointer-events:none}\r\n.nf-s-un-item[data-editable]{cursor:grab;touch-action:none}\r\n.nf-s-toasts{position:absolute;left:12px;bottom:12px;z-index:9;display:flex;flex-direction:column;gap:8px;max-width:min(460px,calc(100% - 24px))}\r\n.nf-s-toast{display:flex;align-items:center;gap:10px;padding:8px 8px 8px 12px;border-radius:10px;background:var(--nf-tooltip-bg);color:var(--nf-tooltip-ink);box-shadow:0 8px 24px rgba(14,17,22,.24);font-size:var(--nf-text-body);animation:nf-s-in ${motion.fast} ${motion.ease} both}\r\n.nf-s-toast[data-tone=\"error\"]{background:var(--nf-negative);color:#fff}\r\n.nf-s-toast-text{flex:1;min-width:0}\r\n.nf-s-toast-btn{font:inherit;font-size:var(--nf-text-body);font-weight:600;color:inherit;background:rgba(255,255,255,.14);border:0;padding:5px 10px;border-radius:7px;cursor:pointer}\r\n.nf-s-toast-x{font:inherit;font-size:var(--nf-text-title);line-height:1;color:inherit;opacity:.7;background:none;border:0;padding:4px 6px;border-radius:6px;cursor:pointer}\r\n.nf-s-toast button:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-s-mark{position:absolute;right:12px;bottom:12px;z-index:7;display:inline-flex;align-items:center;gap:6px;padding:5px 9px 5px 6px;border-radius:8px;background:var(--nf-surface);border:1px solid var(--nf-line);box-shadow:0 2px 8px rgba(14,17,22,.08);font-size:var(--nf-text-caption);color:var(--nf-muted);text-decoration:none;font-family:inherit;cursor:pointer}\r\n.nf-s-mark:hover{color:var(--nf-ink)}\r\n.nf-s-locknote{position:absolute;left:12px;bottom:12px;z-index:7;margin:0;max-width:calc(100% - 220px);padding:5px 9px;border-radius:8px;background:var(--nf-surface);border:1px solid var(--nf-line);font-size:var(--nf-text-caption);color:var(--nf-muted)}\r\n.nf-s-renew{position:absolute;right:12px;bottom:12px;z-index:7;display:inline-flex;align-items:center;gap:6px;padding:5px 9px;border-radius:8px;background:var(--nf-surface);border:1px solid var(--nf-line);box-shadow:0 2px 8px rgba(14,17,22,.08);font-size:var(--nf-text-caption);color:var(--nf-muted);text-decoration:none}\r\n.nf-s-renew:hover{color:var(--nf-ink)}\r\n.nf-s-renew:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-s-renew::before{content:\"\";width:6px;height:6px;border-radius:3px;background:var(--nf-milestone)}\r\n.nf-s-mark:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n@keyframes nf-s-in{from{opacity:0;transform:scaleX(.96)}to{opacity:1;transform:none}}\r\n@media (prefers-reduced-motion: reduce){.nf-sched *{animation:none!important;transition:none!important}}\r\n@media (forced-colors: active){\r\n.nf-sched{border-color:CanvasText}\r\n.nf-s-bk,.nf-s-nowline,.nf-s-off,.nf-s-avatar,.nf-s-count,.nf-s-switch-track,.nf-s-switch-track::after{forced-color-adjust:none}\r\n.nf-s-bk{background:Canvas;color:CanvasText;border:1px solid CanvasText}\r\n.nf-s-bk[data-style=\"tentative\"],.nf-s-bk[data-style=\"cancelled\"]{border-style:dashed}\r\n.nf-s-bk[data-style=\"unavailable\"]{background:repeating-linear-gradient(135deg,GrayText 0 2px,Canvas 2px 8px)}\r\n.nf-s-bk[data-style=\"unavailable\"]>b,.nf-s-bk[data-style=\"unavailable\"]>span[aria-hidden]{forced-color-adjust:none;background:Canvas;color:CanvasText;padding:0 3px;border-radius:2px}\r\n.nf-s-bk[data-conflict]::after{background:Highlight}\r\n.nf-s-bk[data-overseat]::before{background:Highlight}\r\n.nf-s-seats{forced-color-adjust:none;background:Highlight;color:HighlightText}\r\n.nf-s-ghost,.nf-s-draglabel,.nf-s-toast{forced-color-adjust:none;background:Canvas;color:CanvasText;border:1px solid CanvasText}\r\n.nf-s-bk[aria-selected=\"true\"]{box-shadow:none;outline:2px solid Highlight;outline-offset:2px}\r\n.nf-s-nowline{background:Highlight}\r\n.nf-s-off{background:transparent}\r\n.nf-s-util,.nf-s-util>i,.nf-s-overday{forced-color-adjust:none}\r\n.nf-s-util{background:Canvas;outline:1px solid GrayText}\r\n.nf-s-util>i{background:GrayText;opacity:1}\r\n.nf-s-util>i[data-level=\"near\"]{background:CanvasText}\r\n.nf-s-util>i[data-level=\"over\"]{background:Highlight}\r\n.nf-s-overday{background:repeating-linear-gradient(135deg,Highlight 0 1px,transparent 1px 7px);opacity:.35}\r\n.nf-s-avatar,.nf-s-count{background:Canvas;color:CanvasText;border:1px solid CanvasText}\r\n.nf-sched :focus-visible{outline:2px solid Highlight}\r\n.nf-s-ag-item{border-color:CanvasText}\r\n.nf-s-menu{border-color:CanvasText}\r\n.nf-s-menu [role=\"menuitem\"]:focus-visible{outline:2px solid Highlight;box-shadow:none}\r\n.nf-s-ag-item[data-selected]{outline:2px solid Highlight;box-shadow:none}\r\n.nf-s-ag-sw{forced-color-adjust:none;background:CanvasText!important}\r\n/* Forced colours drop box-shadows: today and the pressed view need real borders. */\r\n.nf-s-day[data-today],.nf-s-ag-day[data-today]{box-shadow:none;border-bottom:2px solid Highlight}\r\n.nf-s-seg .nf-s-btn[aria-pressed=\"true\"]{outline:2px solid Highlight;outline-offset:-2px}\r\n.nf-s-switch[aria-pressed=\"true\"] .nf-s-switch-track{background:Highlight}\r\n.nf-s-switch-track{background:Canvas;border:1px solid CanvasText}\r\n.nf-s-switch-track::after{background:CanvasText}\r\n.nf-s-switch[aria-pressed=\"true\"] .nf-s-switch-track::after{background:HighlightText}\r\n}\r\n`;\r\n","import { accentForText, darkTheme, lightTheme, themeVars, type ComponentTheme } from '@ninthforge/tokens';\nimport type { SchedulerProps } from './types';\n\n/**\n * Theme → CSS variables, the same rules as the Gantt: a custom accent also drives the focus\n * ring, tint and selection, and small text on the accent always reaches 4.5:1\n * (`--nf-accent-solid` is the accent shaded just enough when neither white nor ink passes).\n */\nexport function resolveTheme(theme: SchedulerProps['theme'], accent?: string): Record<string, string> {\n const base = theme === 'dark' ? darkTheme : lightTheme;\n const overrides: Partial<ComponentTheme> = typeof theme === 'object' ? { ...theme } : {};\n const ownText = overrides.accentText;\n if (accent) {\n overrides.accent = accent;\n overrides.focus = overrides.focus ?? accent;\n const surface = overrides.surface ?? base.surface;\n const dark = theme === 'dark';\n overrides.accentTint = overrides.accentTint ?? `color-mix(in srgb, ${accent} ${dark ? 32 : 20}%, ${surface})`;\n overrides.selected = overrides.selected ?? `color-mix(in srgb, ${accent} ${dark ? 14 : 6}%, ${surface})`;\n }\n const finalAccent = overrides.accent ?? base.accent;\n const pair = ownText ? { background: finalAccent, text: ownText } : accentForText(finalAccent);\n overrides.accentText = pair.text;\n return { ...themeVars(overrides, base), '--nf-accent-solid': pair.background };\n}\n\n/** The accent as a plain colour (for computing bar colours in JS). */\nexport function accentOf(theme: SchedulerProps['theme'], accent?: string): string {\n if (accent) return accent;\n if (typeof theme === 'object' && theme.accent) return theme.accent;\n return (theme === 'dark' ? darkTheme : lightTheme).accent;\n}\n\n/** Plain colour values (not CSS variables) for print and PNG. Print passes `forceLight`: paper is white. */\nexport function palette(theme: SchedulerProps['theme'], accent: string | undefined, forceLight: boolean): ComponentTheme {\n const base = theme === 'dark' && !forceLight ? darkTheme : lightTheme;\n const own: Partial<ComponentTheme> = typeof theme === 'object' && !forceLight ? theme : {};\n return { ...base, ...own, ...(accent ? { accent, accentText: own.accentText ?? accentForText(accent).text } : {}) };\n}\n","import {\n canvasColor,\n dayLabelIn,\n dayToInstant,\n downloadCanvas,\n escapeHtml,\n exportRatio,\n timeLabel,\n type InstantScale,\n type Zone\n} from '@ninthforge/core';\nimport { brand, type ComponentTheme } from '@ninthforge/tokens';\nimport { agendaTime, buildRows, LANE_H, RESOURCE_W, ROW_PAD, unassigned, type BarStyle, type Item, type ResourceRow, type Row } from './layout';\nimport type { SchedResource, SchedulerLabels } from './types';\n\n/* Print and PNG for the Scheduler. Print is a self-contained HTML document (no scripts, no\n network) printed from an about:blank iframe; PNG redraws the board on a canvas. */\n\nexport interface Look {\n color: string;\n text: string;\n style: BarStyle;\n}\n\nexport interface BoardExport {\n title: string;\n /** \"26 Oct – 1 Nov 2026\". */\n range: string;\n /** \"Mon 26 Oct 2026\". */\n printedOn: string;\n /** All rows (groups and resources), not just the visible ones. */\n rows: Row[];\n /** The time scale to draw on: fitted to the page for print, the screen's for PNG. */\n scale: InstantScale;\n zone: Zone;\n labels: SchedulerLabels;\n theme: ComponentTheme;\n look: ReadonlyMap<string, Look>;\n /** Row header capacity text (\"32 / 40 h\"), or null. */\n capacity: (row: ResourceRow) => string | null;\n /** Unassigned work in the range (listed after the board when printing). */\n queue: readonly Item[];\n /** The now line, when it falls in the range. */\n now?: number;\n /** Day view: hour labels in the header. */\n showHours: boolean;\n}\n\n/** Timeline width for print: A4/Letter landscape leaves ~1,040 px; the rest is the resource column. */\nexport const PRINT_TIMELINE_W = 820;\nconst PRINT_RES_W = 190;\nconst PRINT_LANE = 18;\n\nconst e = escapeHtml;\n\n/** Characters that fit in `px` at ~10 px text (an estimate; print and canvas both clip anyway). */\nfunction fit(text: string, px: number, perChar = 5.4): string {\n const n = Math.floor((px - 6) / perChar);\n if (n <= 0) return '';\n return text.length <= n ? text : `${text.slice(0, Math.max(1, n - 1))}…`;\n}\n\n/** Bars of one resource row as SVG, in its lanes. */\nfunction rowSvg(row: ResourceRow, o: BoardExport, w: number): string {\n const t = o.theme;\n const s = o.scale;\n const h = Math.max(24, row.lanes * PRINT_LANE + 6);\n const out: string[] = [];\n for (const b of s.nonWorking) out.push(`<rect x=\"${b.x}\" y=\"0\" width=\"${b.width}\" height=\"${h}\" fill=\"${t.lineSoft}\"/>`);\n for (const d of s.days) out.push(`<line x1=\"${d.x}\" x2=\"${d.x}\" y1=\"0\" y2=\"${h}\" stroke=\"${t.line}\"/>`);\n if (o.now !== undefined && o.now >= s.start && o.now < s.end) {\n const nx = s.x(o.now);\n out.push(`<line x1=\"${nx}\" x2=\"${nx}\" y1=\"0\" y2=\"${h}\" stroke=\"${brand.ember}\" stroke-width=\"1.5\"/>`);\n }\n for (const it of row.items) {\n const x0 = s.x(Math.max(it.start, s.start));\n const x1 = s.x(Math.min(it.end, s.end));\n const bw = Math.max(2, x1 - x0);\n const y = 3 + (row.lane.get(it.id) ?? 0) * PRINT_LANE;\n const look = o.look.get(it.id) ?? { color: t.accent, text: t.accentText, style: 'solid' as BarStyle };\n let fill = look.color;\n let ink = look.text;\n let extra = '';\n if (look.style === 'tentative') {\n extra = ` fill-opacity=\".16\" stroke=\"${look.color}\" stroke-dasharray=\"3 2\"`;\n ink = t.ink;\n } else if (look.style === 'done') {\n extra = ` fill-opacity=\".4\"`;\n ink = t.ink;\n } else if (look.style === 'cancelled') {\n fill = t.lineSoft;\n extra = ` stroke=\"${t.line}\"`;\n ink = t.muted;\n } else if (look.style === 'unavailable') {\n fill = 'url(#nf-hatch)';\n extra = ` stroke=\"${t.line}\"`;\n ink = t.ink;\n }\n out.push(`<rect x=\"${x0}\" y=\"${y}\" width=\"${bw}\" height=\"14\" rx=\"3\"${extra} fill=\"${fill}\"/>`);\n if (row.conflicts.has(it.id)) out.push(`<rect x=\"${x0}\" y=\"${y}\" width=\"${bw}\" height=\"2\" fill=\"${t.negative}\"/>`);\n const label = fit(it.booking.name, bw);\n if (label)\n out.push(\n `<text x=\"${x0 + 4}\" y=\"${y + 10.5}\" fill=\"${ink}\"${look.style === 'cancelled' ? ' text-decoration=\"line-through\"' : ''}>${e(label)}</text>`\n );\n }\n return `<svg width=\"${w}\" height=\"${h}\" viewBox=\"0 0 ${w} ${h}\" aria-hidden=\"true\">${out.join('')}</svg>`;\n}\n\nfunction headerSvg(o: BoardExport, w: number): string {\n const t = o.theme;\n const out: string[] = [];\n for (const d of o.scale.days) {\n out.push(`<line x1=\"${d.x}\" x2=\"${d.x}\" y1=\"0\" y2=\"30\" stroke=\"${t.line}\"/>`);\n out.push(`<text x=\"${d.x + 4}\" y=\"12\" font-weight=\"600\" fill=\"${t.ink}\">${e(fit(d.label, d.width, 5.8))}</text>`);\n }\n if (o.showHours) {\n // Label every hour that has room (≥ 34 px apart).\n let lastX = -Infinity;\n for (const hr of o.scale.hours) {\n if (hr.x - lastX < 34) continue;\n lastX = hr.x;\n out.push(`<text x=\"${hr.x + 2}\" y=\"26\" fill=\"${t.muted}\">${e(hr.label)}</text>`);\n }\n }\n return `<svg width=\"${w}\" height=\"30\" viewBox=\"0 0 ${w} 30\" aria-hidden=\"true\">${out.join('')}</svg>`;\n}\n\nfunction legend(t: ComponentTheme, withNow: boolean): string {\n const sw = (style: string) => `<i style=\"${style}\"></i>`;\n return [\n `<span>${sw(`background:${t.accent}`)}Booked</span>`,\n `<span>${sw(`border:1px dashed ${t.accent};background:transparent`)}Tentative</span>`,\n `<span>${sw(`background:${t.accent};opacity:.4`)}Done</span>`,\n `<span>${sw(`background:${t.lineSoft};border:1px solid ${t.line}`)}Cancelled</span>`,\n `<span>${sw(`background:repeating-linear-gradient(135deg,${t.muted} 0 1px,transparent 1px 4px);border:1px solid ${t.line}`)}Unavailable</span>`,\n `<span>${sw(`background:${t.negative};height:2px`)}Overlap</span>`,\n withNow ? `<span>${sw(`background:${brand.ember};width:2px`)}Now</span>` : ''\n ].join('');\n}\n\nfunction shell(title: string, meta: string, legendHtml: string, t: ComponentTheme, body: string): string {\n return `<!doctype html><html><head><meta charset=\"utf-8\"><title>${e(title)}</title><style>\n@page{size:landscape;margin:10mm}\n*{box-sizing:border-box}\nhtml,body{margin:0;background:#fff;color:${t.ink};font-family:Geist,system-ui,-apple-system,\"Segoe UI\",sans-serif;font-size:10px;-webkit-print-color-adjust:exact;print-color-adjust:exact}\nheader{display:flex;align-items:baseline;flex-wrap:wrap;gap:12px;margin-bottom:8px}\nh1{font-size:16px;margin:0;letter-spacing:-0.01em}\nh2{font-size:12px;margin:14px 0 6px}\nh3{font-size:10.5px;margin:8px 0 4px;color:${t.muted};font-weight:600}\n.meta{color:${t.muted}}\n.legend{display:flex;flex-wrap:wrap;gap:12px;margin-left:auto;color:${t.muted}}\n.legend span{display:inline-flex;align-items:center;gap:5px}\n.legend i{display:inline-block;width:18px;height:9px;border-radius:2px}\ntable{width:100%;border-collapse:collapse;table-layout:fixed}\nthead{display:table-header-group}\ntr{page-break-inside:avoid;break-inside:avoid}\nth{font-family:\"Geist Mono\",ui-monospace,monospace;font-weight:500;font-size:8.5px;letter-spacing:.06em;text-transform:uppercase;color:${t.muted};text-align:left;padding:0 6px 4px;border-bottom:1px solid ${t.line};vertical-align:bottom}\nth.tl{padding:0}\ntd{padding:2px 6px;border-bottom:1px solid ${t.lineSoft};vertical-align:middle;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\ntd.tl{padding:0}\ntd.tl svg,th.tl svg{display:block}\nsvg text{font-family:inherit;font-size:9px}\n.res b{display:block;font-weight:600}\n.res small{color:${t.muted}}\n.cap{float:right;font-family:\"Geist Mono\",ui-monospace,monospace;color:${t.muted}}\ntr.group td{background:${t.surfaceAlt};font-weight:600;padding:4px 6px}\n.num{font-variant-numeric:tabular-nums;white-space:nowrap}\n.muted{color:${t.muted}}\n.strike{text-decoration:line-through;color:${t.muted}}\n</style></head><body>\n<svg width=\"0\" height=\"0\" style=\"position:absolute\" aria-hidden=\"true\"><defs><pattern id=\"nf-hatch\" width=\"5\" height=\"5\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate(45)\"><rect width=\"5\" height=\"5\" fill=\"#fff\"/><line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"5\" stroke=\"${t.line}\" stroke-width=\"2\"/></pattern></defs></svg>\n<header><h1>${e(title)}</h1><span class=\"meta\">${meta}</span><div class=\"legend\">${legendHtml}</div></header>\n${body}\n</body></html>`;\n}\n\nfunction queueTable(queue: readonly Item[], zone: Zone, labels: SchedulerLabels): string {\n if (!queue.length) return '';\n const rows = queue\n .map(\n (it) =>\n `<tr><td class=\"num\">${e(dayLabelIn(it.start, zone))}, ${it.booking.allDay ? 'all day' : `${timeLabel(it.start, zone)}–${timeLabel(it.end, zone)}`}</td><td${\n it.cancelled ? ' class=\"strike\"' : ''\n }>${e(it.booking.name)}</td><td>${e(it.booking.location ?? '')}</td><td class=\"muted\">${e(it.booking.status ?? '')}</td></tr>`\n )\n .join('');\n return `<h2>Unassigned ${e(labels.bookings.toLowerCase())} (${queue.length})</h2><table><colgroup><col style=\"width:190px\"><col><col style=\"width:200px\"><col style=\"width:110px\"></colgroup><thead><tr><th>When</th><th>${e(\n labels.booking\n )}</th><th>Location</th><th>Status</th></tr></thead><tbody>${rows}</tbody></table>`;\n}\n\n/** The board as a print document: resources down, time across. Pure: same inputs, same HTML. */\nexport function boardPrintHtml(o: BoardExport): string {\n const t = o.theme;\n const w = o.scale.width;\n const resources = o.rows.filter((r): r is ResourceRow => r.kind === 'resource');\n const body = o.rows\n .map((r) => {\n if (r.kind === 'group') return `<tr class=\"group\"><td colspan=\"2\">${e(r.label)} <span class=\"muted\">${r.count}</span></td></tr>`;\n const cap = o.capacity(r);\n return `<tr><td class=\"res\">${cap ? `<span class=\"cap\">${e(cap)}</span>` : ''}<b>${e(r.resource.name)}</b>${\n r.resource.subtitle ? `<small>${e(r.resource.subtitle)}</small>` : ''\n }</td><td class=\"tl\">${rowSvg(r, o, w)}</td></tr>`;\n })\n .join('');\n const table = `<table><colgroup><col style=\"width:${PRINT_RES_W}px\"><col style=\"width:${w}px\"></colgroup><thead><tr><th>${e(\n o.labels.resources\n )}</th><th class=\"tl\">${headerSvg(o, w)}</th></tr></thead><tbody>${body}</tbody></table>`;\n const showsNow = o.now !== undefined && o.now >= o.scale.start && o.now < o.scale.end;\n const meta = `${e(o.range)} · ${resources.length} ${e((resources.length === 1 ? o.labels.resource : o.labels.resources).toLowerCase())} · Printed on ${e(o.printedOn)}`;\n return shell(o.title, meta, legend(t, showsNow), t, table + queueTable(o.queue, o.zone, o.labels));\n}\n\nexport interface AgendaExport {\n title: string;\n range: string;\n printedOn: string;\n resources: readonly SchedResource[];\n items: readonly Item[];\n startDay: number;\n days: number;\n zone: Zone;\n filter: string;\n labels: SchedulerLabels;\n theme: ComponentTheme;\n}\n\n/** Agenda as a print document: a list by day, then resource. */\nexport function agendaPrintHtml(o: AgendaExport): string {\n const parts: string[] = [];\n let count = 0;\n for (let day = o.startDay; day < o.startDay + o.days; day++) {\n const from = dayToInstant(day, o.zone);\n const to = dayToInstant(day + 1, o.zone);\n const rows = buildRows(o.resources, o.items, { from, to, groupBy: false, filter: o.filter }).filter(\n (r): r is ResourceRow => r.kind === 'resource' && r.items.length > 0\n );\n const queue = o.filter ? [] : unassigned(o.items, from, to);\n if (!rows.length && !queue.length) continue;\n const line = (it: Item, conflict: boolean) => {\n count++;\n return `<tr><td class=\"num\" style=\"width:110px\">${e(agendaTime(it, day, o.zone))}</td><td${it.cancelled ? ' class=\"strike\"' : ''}>${e(it.booking.name)}${\n conflict ? ' <span class=\"muted\">· overlaps</span>' : ''\n }</td><td style=\"width:200px\">${e(it.booking.location ?? '')}</td><td class=\"muted\" style=\"width:110px\">${e(\n it.kind === 'unavailable' ? 'Unavailable' : (it.booking.status ?? '')\n )}</td></tr>`;\n };\n parts.push(`<h2>${e(dayLabelIn(dayToInstant(day, o.zone), o.zone))}</h2>`);\n for (const r of rows) parts.push(`<h3>${e(r.resource.name)}</h3><table><tbody>${r.items.map((it) => line(it, r.conflicts.has(it.id))).join('')}</tbody></table>`);\n if (queue.length) parts.push(`<h3>Unassigned</h3><table><tbody>${queue.map((it) => line(it, false)).join('')}</tbody></table>`);\n }\n if (!parts.length) parts.push(`<p class=\"muted\">No ${e(o.labels.bookings.toLowerCase())} in these days.</p>`);\n const meta = `${e(o.range)} · ${count} ${e((count === 1 ? o.labels.booking : o.labels.bookings).toLowerCase())} · Printed on ${e(o.printedOn)}`;\n return shell(o.title, meta, '', o.theme, parts.join(''));\n}\n\n/* ---------- PNG ---------- */\n\nconst TITLE_H = 48;\nconst HEAD_H = 44;\nconst FONT = 'Geist, system-ui, -apple-system, \"Segoe UI\", sans-serif';\n\n/** Size of the PNG for the board at screen scale, or null when it's too big for one image. */\nexport function boardPngSize(rows: readonly Row[], timelineWidth: number): { width: number; height: number; ratio: number } | null {\n const last = rows[rows.length - 1];\n const body = last ? last.top + last.height : 60;\n const width = Math.ceil(RESOURCE_W + timelineWidth);\n const height = Math.ceil(TITLE_H + HEAD_H + body);\n const ratio = exportRatio(width, height);\n return ratio === null ? null : { width, height, ratio };\n}\n\nfunction roundRect(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number) {\n const rr = Math.min(r, w / 2, h / 2);\n ctx.beginPath();\n ctx.moveTo(x + rr, y);\n ctx.arcTo(x + w, y, x + w, y + h, rr);\n ctx.arcTo(x + w, y + h, x, y + h, rr);\n ctx.arcTo(x, y + h, x, y, rr);\n ctx.arcTo(x, y, x + w, y, rr);\n ctx.closePath();\n}\n\nexport function drawBoard(ctx: CanvasRenderingContext2D, size: { width: number; height: number; ratio: number }, o: BoardExport): void {\n const c = (v: string | undefined, fb: string) => canvasColor(ctx, v, fb);\n const T = {\n surface: c(o.theme.surface, '#fff'),\n alt: c(o.theme.surfaceAlt, '#f6f6f3'),\n ink: c(o.theme.ink, '#111'),\n muted: c(o.theme.muted, '#666'),\n line: c(o.theme.line, '#ddd'),\n soft: c(o.theme.lineSoft, '#eee'),\n negative: c(o.theme.negative, '#d93f2b')\n };\n const s = o.scale;\n const X0 = RESOURCE_W;\n const Y0 = TITLE_H + HEAD_H;\n ctx.save();\n ctx.scale(size.ratio, size.ratio);\n ctx.fillStyle = T.surface;\n ctx.fillRect(0, 0, size.width, size.height);\n ctx.textBaseline = 'middle';\n\n // Title and range.\n ctx.fillStyle = T.ink;\n ctx.font = `600 16px ${FONT}`;\n ctx.fillText(o.title, 16, 24);\n const tw = ctx.measureText(o.title).width;\n ctx.fillStyle = T.muted;\n ctx.font = `400 12px ${FONT}`;\n ctx.fillText(o.range, 16 + tw + 12, 25);\n\n // Header: days (and hours in Day view).\n ctx.fillStyle = T.soft;\n ctx.fillRect(0, TITLE_H + HEAD_H - 1, size.width, 1);\n for (const d of s.days) {\n ctx.fillStyle = T.line;\n ctx.fillRect(X0 + d.x, TITLE_H, 1, size.height - TITLE_H);\n ctx.save();\n ctx.beginPath();\n ctx.rect(X0 + d.x, TITLE_H, d.width, HEAD_H);\n ctx.clip();\n ctx.fillStyle = T.ink;\n ctx.font = `600 12px ${FONT}`;\n ctx.fillText(d.label, X0 + d.x + 8, TITLE_H + 14);\n ctx.restore();\n }\n if (o.showHours) {\n ctx.fillStyle = T.muted;\n ctx.font = `400 11px ${FONT}`;\n let lastX = -Infinity;\n for (const hr of s.hours) {\n if (hr.x - lastX < 40) continue;\n lastX = hr.x;\n ctx.fillText(hr.label, X0 + hr.x + 4, TITLE_H + 32);\n }\n }\n ctx.fillStyle = T.muted;\n ctx.font = `500 11px ${FONT}`;\n ctx.fillText(o.labels.resources.toUpperCase(), 16, TITLE_H + HEAD_H - 14);\n\n // Non-working time.\n ctx.fillStyle = T.soft;\n for (const b of s.nonWorking) ctx.fillRect(X0 + b.x, Y0, b.width, size.height - Y0);\n\n // Rows.\n for (const r of o.rows) {\n const y = Y0 + r.top;\n if (r.kind === 'group') {\n ctx.fillStyle = T.alt;\n ctx.fillRect(0, y, size.width, r.height);\n ctx.fillStyle = T.ink;\n ctx.font = `600 12px ${FONT}`;\n ctx.fillText(`${r.label} ${r.count}`, 16, y + r.height / 2);\n continue;\n }\n ctx.fillStyle = T.soft;\n ctx.fillRect(0, y + r.height - 1, size.width, 1);\n ctx.fillStyle = T.ink;\n ctx.font = `500 13px ${FONT}`;\n const sub = r.resource.subtitle;\n ctx.fillText(r.resource.name, 16, y + (sub ? r.height / 2 - 7 : r.height / 2), RESOURCE_W - 96);\n if (sub) {\n ctx.fillStyle = T.muted;\n ctx.font = `400 11px ${FONT}`;\n ctx.fillText(sub, 16, y + r.height / 2 + 8, RESOURCE_W - 96);\n }\n const cap = o.capacity(r);\n if (cap) {\n ctx.fillStyle = T.muted;\n ctx.font = `400 11px ${FONT}`;\n ctx.textAlign = 'right';\n ctx.fillText(cap, RESOURCE_W - 12, y + r.height / 2);\n ctx.textAlign = 'left';\n }\n for (const it of r.items) {\n const x0 = X0 + s.x(Math.max(it.start, s.start));\n const x1 = X0 + s.x(Math.min(it.end, s.end));\n const bw = Math.max(3, x1 - x0);\n const by = y + ROW_PAD + (r.lane.get(it.id) ?? 0) * LANE_H;\n const bh = LANE_H - 4;\n const look = o.look.get(it.id);\n const color = c(look?.color, '#2E54FF');\n let ink = c(look?.text, '#fff');\n roundRect(ctx, x0, by, bw, bh, 6);\n ctx.save();\n if (look?.style === 'tentative' || look?.style === 'done') {\n ctx.globalAlpha = look.style === 'done' ? 0.4 : 0.16;\n ctx.fillStyle = color;\n ctx.fill();\n ctx.globalAlpha = 1;\n if (look.style === 'tentative') {\n ctx.setLineDash([4, 3]);\n ctx.strokeStyle = color;\n ctx.stroke();\n }\n ink = T.ink;\n } else if (look?.style === 'cancelled') {\n ctx.fillStyle = T.soft;\n ctx.fill();\n ctx.strokeStyle = T.line;\n ctx.stroke();\n ink = T.muted;\n } else if (look?.style === 'unavailable') {\n ctx.fillStyle = T.surface;\n ctx.fill();\n ctx.clip();\n ctx.strokeStyle = T.line;\n ctx.lineWidth = 2;\n for (let hx = x0 - bh; hx < x0 + bw; hx += 8) {\n ctx.beginPath();\n ctx.moveTo(hx, by + bh);\n ctx.lineTo(hx + bh, by);\n ctx.stroke();\n }\n ink = T.ink;\n } else {\n ctx.fillStyle = color;\n ctx.fill();\n }\n ctx.restore();\n if (r.conflicts.has(it.id)) {\n ctx.fillStyle = T.negative;\n ctx.fillRect(x0 + 3, by, Math.max(0, bw - 6), 2);\n }\n if (bw > 24) {\n ctx.save();\n ctx.beginPath();\n ctx.rect(x0 + 6, by, bw - 10, bh);\n ctx.clip();\n ctx.fillStyle = ink;\n ctx.font = `500 12px ${FONT}`;\n ctx.fillText(it.booking.name, x0 + 8, by + bh / 2 + 0.5);\n if (look?.style === 'cancelled') ctx.fillRect(x0 + 8, by + bh / 2, Math.min(bw - 12, ctx.measureText(it.booking.name).width), 1);\n ctx.restore();\n }\n }\n }\n\n // Resource column edge and the now line.\n ctx.fillStyle = T.line;\n ctx.fillRect(RESOURCE_W - 1, TITLE_H, 1, size.height - TITLE_H);\n if (o.now !== undefined && o.now >= s.start && o.now < s.end) {\n ctx.fillStyle = brand.ember;\n ctx.fillRect(X0 + s.x(o.now) - 1, TITLE_H, 2, size.height - TITLE_H);\n }\n ctx.restore();\n}\n\n/** Draw the board and download it. False when it's too big for one image. */\nexport function exportBoardPng(o: BoardExport, fileName: string, doc: Document = document): boolean {\n const size = boardPngSize(o.rows, o.scale.width);\n if (!size) return false;\n const canvas = doc.createElement('canvas');\n canvas.width = Math.round(size.width * size.ratio);\n canvas.height = Math.round(size.height * size.ratio);\n const ctx = canvas.getContext('2d');\n if (!ctx) return false;\n drawBoard(ctx, size, o);\n downloadCanvas(canvas, fileName, doc);\n return true;\n}\n","import { atHour, toDay, weekday, type Zone } from '@ninthforge/core';\nimport type { SchedBooking, SchedResource, SchedulerPreset } from './types';\n\n/*\n * Demo data for each scenario: realistic, deterministic (seeded), around an anchor date,\n * with overlaps, unavailable blocks, over-capacity days and (for jobs) an unassigned queue.\n * Mirrors the Dev seed (tools/dataverse/sched-rows.mjs) at a demo-friendly size.\n */\n\nfunction rng(seed: number) {\n let s = seed | 0;\n return () => {\n s = (s + 0x6d2b79f5) | 0;\n let t = Math.imul(s ^ (s >>> 15), 1 | s);\n t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;\n return ((t ^ (t >>> 14)) >>> 0) / 4294967296;\n };\n}\n\nexport interface Scenario {\n resources: SchedResource[];\n bookings: SchedBooking[];\n}\n\nconst iso = (ms: number) => new Date(ms).toISOString();\n\n/** Sample resources and bookings for a preset, ±3 weeks around `anchor` (ISO date), wall times in `zone`. */\nexport function sampleScenario(preset: SchedulerPreset, anchor: string, zone: Zone): Scenario {\n const rand = rng(preset.length * 7919 + toDay(anchor));\n const pick = <T,>(list: readonly T[]): T => list[Math.floor(rand() * list.length)]!;\n const anchorDay = toDay(anchor);\n const monday = anchorDay - weekday(anchorDay);\n const days: number[] = [];\n for (let d = monday - 21; d < monday + 21; d++) days.push(d);\n const weekdays = days.filter((d) => weekday(d) < 5);\n const at = (day: number, hour: number) => atHour(day, hour, zone);\n const resources: SchedResource[] = [];\n const bookings: SchedBooking[] = [];\n let n = 0;\n const book = (b: Omit<SchedBooking, 'id' | 'start' | 'end'> & { start: number; end: number }) =>\n bookings.push({ ...b, id: `${preset}-${++n}`, start: iso(b.start), end: iso(b.end) });\n const status = (day: number) => (day < anchorDay - 1 ? 'Done' : rand() < 0.75 ? 'Confirmed' : 'Tentative');\n\n if (preset === 'people' || preset === 'custom') {\n const teams: Record<string, string[]> = {\n 'Customer Care': ['Aisha Khan', 'Ben Walker', 'Chloe Nguyen', 'Dev Patel', 'Ella Brown'],\n Warehouse: ['Grace Lee', 'Hamish Scott', 'Isla Chen', 'Jack Wilson'],\n Clinic: ['Maya Rossi', 'Noah Kim', 'Olivia Hart', 'Priya Das']\n };\n for (const [team, names] of Object.entries(teams))\n for (const name of names) {\n const early = team === 'Warehouse';\n const r: SchedResource = { id: `p-${resources.length + 1}`, name, group: team, subtitle: team === 'Clinic' ? 'Nurse' : early ? 'Picker' : 'Agent', capacityHours: 8, workStart: early ? 7 : 9, workEnd: early ? 15.5 : 17 };\n resources.push(r);\n for (const day of weekdays) {\n if (rand() < 0.3) continue;\n const s = Number(r.workStart) + (rand() < 0.25 ? 1 : 0);\n const len = pick([4, 6, 8, 8]);\n book({ name: `${team} shift`, resourceId: r.id, start: at(day, s), end: at(day, s + len), status: status(day), subtitle: team });\n if (rand() < 0.07) book({ name: 'Overtime cover', resourceId: r.id, start: at(day, s + len - 2), end: at(day, s + len + 3), status: 'Tentative' });\n }\n if (rand() < 0.4) {\n const d = pick(weekdays);\n book({ name: 'Annual leave', resourceId: r.id, start: at(d, 0), end: at(d + 3, 0), kind: 'unavailable', allDay: true, status: 'Confirmed' });\n }\n }\n }\n\n if (preset === 'rooms') {\n const rooms: [string, string, number][] = [\n ['Harbour Room', 'Building A', 12],\n ['Ridge Room', 'Building A', 6],\n ['Studio 1', 'Building A', 4],\n ['Boardroom', 'Building A', 20],\n ['Kings Park', 'Building B', 8],\n ['Swan Room', 'Building B', 10],\n ['Focus Pod', 'Building B', 2]\n ];\n const meetings = ['Team stand-up', 'Client workshop', 'Interview', 'Planning', 'Training', 'Board meeting', '1:1'];\n for (const [name, building, seats] of rooms) {\n const r: SchedResource = { id: `rm-${resources.length + 1}`, name, group: building, seats, subtitle: `${seats} seats`, capacityHours: 10, workStart: 8, workEnd: 18 };\n resources.push(r);\n for (const day of weekdays) {\n let h = 8.5;\n const count = 1 + Math.floor(rand() * 3);\n for (let i = 0; i < count && h < 16.5; i++) {\n h += pick([0, 0.5, 1]);\n const len = pick([0.5, 1, 1, 1.5, 2]);\n const over = rand() < 0.08;\n const attendees = Math.max(1, Math.round(seats * (over ? 1.3 : 0.4 + rand() * 0.6)));\n book({ name: pick(meetings), resourceId: r.id, start: at(day, h), end: at(day, h + len), attendees, status: status(day), subtitle: `${attendees} people` });\n h += rand() < 0.08 ? len - 0.5 : len;\n }\n }\n }\n }\n\n if (preset === 'vehicles' || preset === 'equipment') {\n const fleet: [string, string, string][] =\n preset === 'vehicles'\n ? [\n ['Hilux 1', 'North depot', '1ABC 234'],\n ['Hilux 2', 'North depot', '1ABD 771'],\n ['Transit van', 'South depot', '1XYZ 410'],\n ['Prado', 'South depot', '1PRA 090'],\n ['Tipper truck', 'South depot', '1TIP 655'],\n ['Ute 3', 'North depot', '1UTE 303']\n ]\n : [\n ['Scissor lift', 'Lifting', 'EQ-1001'],\n ['Forklift', 'Lifting', 'EQ-1002'],\n ['Total station', 'Survey', 'EQ-2001'],\n ['GPS rover', 'Survey', 'EQ-2002'],\n ['Generator 20kVA', 'Power', 'EQ-3001'],\n ['Light tower', 'Power', 'EQ-3002']\n ];\n const sites = ['Kwinana site', 'Joondalup depot', 'Fremantle port', 'Midland yard', 'Rockingham job', 'CBD fit-out'];\n for (const [name, group, id] of fleet) {\n const r: SchedResource = { id: `${preset[0]}-${resources.length + 1}`, name, group, subtitle: id, capacityHours: 10, workStart: 7, workEnd: 17 };\n resources.push(r);\n let day = days[0]! + Math.floor(rand() * 3);\n while (day < days[days.length - 1]!) {\n const len = 1 + Math.floor(rand() * 4);\n const site = pick(sites);\n book({ name: `${site} hire`, resourceId: r.id, start: at(day, 7), end: at(day + len - 1, 17), location: site, status: status(day), subtitle: site });\n day += len + Math.floor(rand() * 3);\n }\n const m = pick(weekdays);\n book({ name: 'Maintenance', resourceId: r.id, start: at(m, 7), end: at(m + 1, 17), kind: 'unavailable', status: 'Confirmed', subtitle: 'Service' });\n }\n }\n\n if (preset === 'jobs') {\n const areas: Record<string, string[]> = {\n 'East area': [\"Sam O'Brien\", 'Tara Mills', 'Umar Ali', 'Vera Novak'],\n 'West area': ['Will Grant', 'Xin Zhou', 'Yusuf Kaya', 'Zoe Clarke']\n };\n const jobTypes = ['Air-con service', 'Fault call-out', 'Install', 'Inspection', 'Repair', 'Commissioning'];\n const suburbs = ['Subiaco', 'Midland', 'Cannington', 'Joondalup', 'Fremantle', 'Morley', 'Armadale', 'Scarborough'];\n for (const [area, names] of Object.entries(areas))\n for (const name of names) {\n const r: SchedResource = { id: `t-${resources.length + 1}`, name, group: area, subtitle: 'Field technician', capacityHours: 8, workStart: 7.5, workEnd: 16 };\n resources.push(r);\n for (const day of weekdays) {\n if (rand() < 0.3) continue;\n let h = 7.5;\n const count = rand() < 0.1 ? 4 : 1 + Math.floor(rand() * 2);\n for (let i = 0; i < count; i++) {\n const len = pick([1.5, 2, 3]);\n const loc = pick(suburbs);\n book({ name: pick(jobTypes), resourceId: r.id, start: at(day, h), end: at(day, h + len), location: loc, subtitle: loc, status: status(day) });\n h += len + 0.5;\n }\n }\n }\n for (let i = 0; i < 25; i++) {\n const day = anchorDay + Math.floor(rand() * 10);\n const loc = pick(suburbs);\n const h = pick([8, 9, 10, 13, 14]);\n book({ name: `${pick(jobTypes)} #${1000 + i}`, resourceId: null, start: at(day, h), end: at(day, h + pick([1, 2, 3])), location: loc, subtitle: loc, status: 'Tentative' });\n }\n }\n\n return { resources, bookings };\n}\n\n/**\n * The scale target (docs/specs/scheduler.md, \"Scale\"): `resources` people in 10 teams, each with\n * `perResource` bookings inside the two weeks from the Monday of `anchor`'s week (so a Two weeks\n * view shows resources × perResource bookings), plus some overlaps, leave and unassigned jobs.\n */\nexport function scaleScenario(anchor: string, zone: Zone, resources = 300, perResource = 10): Scenario {\n const rand = rng(toDay(anchor) * 31 + resources);\n const anchorDay = toDay(anchor);\n const monday = anchorDay - weekday(anchorDay);\n const workdays: number[] = [];\n for (let d = monday; d < monday + 14; d++) if (weekday(d) < 5) workdays.push(d);\n const out: Scenario = { resources: [], bookings: [] };\n const statuses = ['Confirmed', 'Confirmed', 'Confirmed', 'Tentative', 'Done'];\n for (let r = 0; r < resources; r++) {\n const id = `s${r}`;\n out.resources.push({ id, name: `Person ${String(r + 1).padStart(3, '0')}`, group: `Team ${String.fromCharCode(65 + (r % 10))}`, subtitle: r % 3 ? 'Technician' : 'Lead' });\n for (let k = 0; k < perResource; k++) {\n const day = workdays[k % workdays.length]!;\n const startH = 7 + Math.floor(rand() * 6);\n const len = 2 + Math.floor(rand() * 6);\n // Every 25th booking is leave for the whole day.\n const leave = (r * perResource + k) % 25 === 0;\n out.bookings.push({\n id: `${id}-${k}`,\n name: leave ? 'Leave' : `Job ${r * perResource + k + 1}`,\n start: iso(atHour(day, leave ? 0 : startH, zone)),\n end: iso(atHour(leave ? day + 1 : day, leave ? 0 : startH + len, zone)),\n resourceId: id,\n status: leave ? undefined : statuses[Math.floor(rand() * statuses.length)],\n kind: leave ? 'unavailable' : 'booking',\n location: rand() < 0.3 ? 'Site ' + (1 + Math.floor(rand() * 40)) : undefined\n });\n }\n }\n for (let j = 0; j < 40; j++) {\n const day = workdays[j % workdays.length]!;\n out.bookings.push({ id: `u${j}`, name: `Unassigned job ${j + 1}`, start: iso(atHour(day, 9, zone)), end: iso(atHour(day, 11, zone)), resourceId: null, status: 'Tentative' });\n }\n return out;\n}\n","import * as React from 'react';\r\nimport { useMemo, useRef, useState, type CSSProperties, type KeyboardEvent } from 'react';\r\nimport { HOST_DIM, isContextMenuKey, isHighlighted, isMultiSelect, type HostFormat, type PointerInfo, type SelectInfo } from '@ninthforge/core';\r\nimport { licenseMessage } from '@ninthforge/license';\r\nimport { nextUid, Watermark, type WatermarkHostProps } from '@ninthforge/editing';\r\nimport { markSvg, motion, type ComponentTheme } from '@ninthforge/tokens';\r\nimport { capacityHeadline, capacityStep, cellKey, STEP_LABELS, stepColors, utilisationPct, weekLabels, weekName, weeksOf, type CapacityCell, type CapacityRow } from './capacity';\r\nimport { palette, resolveTheme } from './theme';\r\nimport { useLicense } from './useLicense';\r\n\r\nexport interface CapacityFormatContext {\r\n field: 'utilisation' | 'hours';\r\n use: 'label' | 'tooltip';\r\n}\r\n\r\nexport interface CapacityColors {\r\n /** Five colours, lowest step first (under 60% … over 100%). */\r\n steps: string[];\r\n foreground: string;\r\n background: string;\r\n}\r\n\r\n/** What a hook receives: the row, and the cell when the pointer is on one. */\r\nexport interface CapacityTarget {\r\n row: CapacityRow;\r\n cell: CapacityCell;\r\n /** `cellKey(row.id, cell.week)`. */\r\n key: string;\r\n}\r\n\r\nexport interface CapacityHeatmapProps extends WatermarkHostProps {\r\n /** Resources or teams, with booked and available hours per week (`weeklyUtilisation`, `rollUp`, or a host's totals). */\r\n rows: CapacityRow[];\r\n /** Column order. Default: every week in the rows, sorted. */\r\n weeks?: string[];\r\n /** Default: a message naming the most overbooked row (\"Front-end squad overbooked in Nov\"). */\r\n title?: string;\r\n /** Column labels. Default \"2 Nov\", \"9\", \"16\", … */\r\n weekLabel?: (weekIso: string, index: number) => string;\r\n /** Header over the names column. */\r\n rowHeader?: string;\r\n theme?: 'light' | 'dark' | Partial<ComponentTheme>;\r\n accent?: string;\r\n /** A Plan key (or several). Missing keys show a small watermark; nothing breaks. */\r\n licenseKey?: string | PromiseLike<string | undefined>;\r\n tenantId?: string;\r\n height?: number | string;\r\n className?: string;\r\n dir?: 'ltr' | 'rtl';\r\n /** Hide the legend (the host draws its own). */\r\n showLegend?: boolean;\r\n\r\n /* ---------- Host hooks (Power BI); all optional ---------- */\r\n /** `none`: no built-in tooltip (the host shows its own from `onHover`). */\r\n tooltip?: 'built-in' | 'none';\r\n onHover?: (target: CapacityTarget | null, at: PointerInfo) => void;\r\n /** Click, Enter or Space on a cell. `null` when Escape clears. */\r\n onSelect?: (target: CapacityTarget | null, info: SelectInfo) => void;\r\n /** Cell keys (`cellKey`) or row ids kept at full strength; the rest dim. */\r\n highlighted?: readonly string[];\r\n onContextMenu?: (target: CapacityTarget, at: PointerInfo) => void;\r\n formatValue?: HostFormat<CapacityFormatContext>;\r\n colors?: Partial<CapacityColors>;\r\n animate?: boolean;\r\n}\r\n\r\nconst css = `\r\n.nf-cap{position:relative;box-sizing:border-box;display:flex;flex-direction:column;gap:10px;padding:16px 18px;min-height:0;background:var(--nf-surface);color:var(--nf-ink);font-family:var(--nf-font);font-size:var(--nf-text-body);line-height:1.3;border:1px solid var(--nf-line);border-radius:16px;-webkit-font-smoothing:antialiased}\r\n.nf-cap *,.nf-cap *::before,.nf-cap *::after{box-sizing:border-box}\r\n.nf-cap-title{margin:0;font-size:var(--nf-text-title);font-weight:600;letter-spacing:-0.01em}\r\n.nf-cap-grid{display:grid;gap:3px;outline:none;overflow:auto;min-height:0;border-radius:8px}\r\n.nf-cap-grid:focus-visible{box-shadow:0 0 0 2px var(--nf-surface),0 0 0 4px var(--nf-focus)}\r\n.nf-cap-row{display:contents}\r\n.nf-cap-colhead{font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);color:var(--nf-muted);text-align:center;white-space:nowrap;padding:0 0 2px}\r\n.nf-cap-name{font-size:var(--nf-text-label);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;align-self:center;padding-inline-end:8px}\r\n.nf-cap-cell{height:32px;border-radius:4px;display:flex;align-items:center;justify-content:center;font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);cursor:pointer;user-select:none;transition:opacity ${motion.fast} ${motion.ease}}\r\n.nf-cap-cell[data-step=\"4\"]{font-weight:600}\r\n.nf-cap-cell[aria-selected=\"true\"],.nf-cap-cell[data-active]{box-shadow:0 0 0 2px var(--nf-surface),0 0 0 4px var(--nf-focus)}\r\n.nf-cap-cell[data-dim],.nf-cap-name[data-dim]{opacity:${HOST_DIM}}\r\n.nf-cap-legend{display:flex;flex-wrap:wrap;gap:6px 12px;align-items:center;font-size:var(--nf-text-caption);color:var(--nf-muted)}\r\n.nf-cap-legend i{display:inline-block;width:14px;height:10px;border-radius:2px;margin-inline-end:4px;vertical-align:-1px}\r\n.nf-cap-tip{position:fixed;z-index:20;pointer-events:none;padding:8px 10px;border-radius:8px;background:var(--nf-tooltip-bg);color:var(--nf-tooltip-ink);font-size:var(--nf-text-value);box-shadow:0 8px 24px rgba(14,17,22,.24);max-width:260px}\r\n.nf-cap-tip b{display:block;font-weight:600;margin-bottom:2px}\r\n.nf-cap-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}\r\n.nf-cap-mark{position:absolute;right:12px;bottom:12px;display:inline-flex;align-items:center;gap:6px;padding:5px 9px 5px 6px;border-radius:8px;background:var(--nf-surface);border:1px solid var(--nf-line);font-size:var(--nf-text-caption);color:var(--nf-muted);text-decoration:none;font-family:inherit;cursor:pointer}\r\n.nf-cap-mark:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-cap[data-static] *{animation:none!important;transition:none!important}\r\n@media (prefers-reduced-motion: reduce){.nf-cap *{animation:none!important;transition:none!important}}\r\n@media (forced-colors: active){.nf-cap-cell{forced-color-adjust:none;border:1px solid CanvasText}}\r\n`;\r\n\r\n/**\r\n * NF Capacity: who is booked how much, week by week. A resource (or team) × week heatmap with\r\n * % labels, five steps from the accent, and over 100% in the Negative colour.\r\n */\r\nexport function CapacityHeatmap(props: CapacityHeatmapProps) {\r\n const { rows } = props;\r\n const uid = useState(() => nextUid('nfc'))[0];\r\n const weeks = useMemo(() => props.weeks ?? weeksOf(rows), [props.weeks, rows]);\r\n const labels = useMemo(() => weeks.map((w, i) => (props.weekLabel ? props.weekLabel(w, i) : weekLabels(weeks)[i]!)), [weeks, props.weekLabel]);\r\n const license = useLicense(props.licenseKey, { product: 'plan', tenantId: props.tenantId });\r\n\r\n const plain = palette(props.theme, props.accent, false);\r\n const surface = props.colors?.background ?? plain.surface;\r\n const steps = stepColors(plain.accent, surface, props.colors?.steps);\r\n const themeOverrides: Partial<ComponentTheme> = {};\r\n if (props.colors?.background) themeOverrides.surface = themeOverrides.tooltipInk = props.colors.background;\r\n if (props.colors?.foreground) themeOverrides.ink = themeOverrides.muted = themeOverrides.line = themeOverrides.tooltipBg = themeOverrides.focus = props.colors.foreground;\r\n // Host colours layer over the chosen theme (a theme object loses the dark base, so spell it out).\r\n const vars = resolveTheme(Object.keys(themeOverrides).length ? { ...plain, ...themeOverrides } : props.theme, props.accent);\r\n\r\n const cellAt = (r: CapacityRow, w: string): CapacityCell => r.cells.find((c) => c.week === w) ?? { week: w, booked: 0, capacity: 0 };\r\n const pctText = (c: CapacityCell, use: 'label' | 'tooltip') => {\r\n const pct = utilisationPct(c);\r\n if (pct === null) return c.booked > 0 ? 'Over' : '–';\r\n return props.formatValue ? props.formatValue(pct, { field: 'utilisation', use }) : `${pct}%`;\r\n };\r\n const hours = (v: number) => (props.formatValue ? props.formatValue(v, { field: 'hours', use: 'tooltip' }) : `${Math.round(v * 10) / 10} h`);\r\n const target = (ri: number, wi: number): CapacityTarget | null => {\r\n const r = rows[ri];\r\n const w = weeks[wi];\r\n if (!r || w === undefined) return null;\r\n return { row: r, cell: cellAt(r, w), key: cellKey(r.id, w) };\r\n };\r\n\r\n const hl = props.highlighted && props.highlighted.length ? new Set(props.highlighted) : undefined;\r\n const dimmed = (r: CapacityRow, w: string) => !!hl && !isHighlighted(r.id, hl) && !isHighlighted(cellKey(r.id, w), hl);\r\n\r\n const [active, setActive] = useState<{ r: number; w: number } | null>(null);\r\n const [selected, setSelected] = useState<string | null>(null);\r\n const [tip, setTip] = useState<{ r: number; w: number; x: number; y: number } | null>(null);\r\n const hovering = useRef(false);\r\n const hover = (t: CapacityTarget | null, at: PointerInfo) => {\r\n if (!props.onHover) return;\r\n if (!t && !hovering.current) return;\r\n hovering.current = !!t;\r\n props.onHover(t, at);\r\n };\r\n const anchor = (ri: number, wi: number): PointerInfo | null => {\r\n const el = typeof document !== 'undefined' ? document.getElementById(`${uid}-${ri}-${wi}`) : null;\r\n const b = el?.getBoundingClientRect();\r\n return b ? { clientX: b.left + b.width / 2, clientY: b.top + b.height / 2, multi: false } : null;\r\n };\r\n const choose = (ri: number, wi: number, multi: boolean) => {\r\n const t = target(ri, wi);\r\n if (!t) return;\r\n setSelected(t.key);\r\n setActive({ r: ri, w: wi });\r\n props.onSelect?.(t, { multi });\r\n };\r\n\r\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\r\n if (!rows.length || !weeks.length) return;\r\n const a = active ?? { r: 0, w: 0 };\r\n const rtl = props.dir === 'rtl';\r\n let next: { r: number; w: number } | null = null;\r\n if (e.key === 'ArrowRight') next = { r: a.r, w: Math.min(weeks.length - 1, Math.max(0, a.w + (rtl ? -1 : 1))) };\r\n else if (e.key === 'ArrowLeft') next = { r: a.r, w: Math.min(weeks.length - 1, Math.max(0, a.w + (rtl ? 1 : -1))) };\r\n else if (e.key === 'ArrowDown') next = { r: Math.min(rows.length - 1, a.r + 1), w: a.w };\r\n else if (e.key === 'ArrowUp') next = { r: Math.max(0, a.r - 1), w: a.w };\r\n else if (e.key === 'Home') next = { r: a.r, w: 0 };\r\n else if (e.key === 'End') next = { r: a.r, w: weeks.length - 1 };\r\n else if (e.key === 'Enter' || e.key === ' ') {\r\n choose(a.r, a.w, isMultiSelect(e));\r\n } else if (e.key === 'Escape') {\r\n setSelected(null);\r\n props.onSelect?.(null, { multi: false });\r\n } else if (isContextMenuKey(e) && props.onContextMenu) {\r\n const t = target(a.r, a.w);\r\n const at = anchor(a.r, a.w);\r\n if (t && at) props.onContextMenu(t, at);\r\n } else return;\r\n e.preventDefault();\r\n if (next) {\r\n setActive(next);\r\n const t = target(next.r, next.w);\r\n const n = next;\r\n if (props.onHover && t) requestAnimationFrame(() => {\r\n const at = anchor(n.r, n.w);\r\n if (at) hover(t, at);\r\n });\r\n }\r\n };\r\n\r\n const title = props.title ?? capacityHeadline(rows);\r\n const columns = `minmax(96px, max-content) repeat(${weeks.length}, minmax(40px, 1fr))`;\r\n const tipT = tip && props.tooltip !== 'none' ? target(tip.r, tip.w) : null;\r\n const style = { ...vars, height: props.height } as CSSProperties;\r\n const activeId = active ? `${uid}-${active.r}-${active.w}` : undefined;\r\n const describe = (t: CapacityTarget, label: string) =>\r\n `${t.row.name}, week of ${label}: ${pctText(t.cell, 'tooltip')}, ${hours(t.cell.booked)} of ${hours(t.cell.capacity)}${capacityStep(t.cell) === 4 ? ', over capacity' : ''}`;\r\n\r\n return (\r\n <div\r\n className={`nf-cap${props.className ? ' ' + props.className : ''}`}\r\n style={style}\r\n dir={props.dir}\r\n data-static={props.animate === false ? '' : undefined}\r\n data-hl={hl ? '' : undefined}\r\n >\r\n {/* Raw CSS: a text child gets HTML-escaped when server-rendered (quotes become &quot;). */}\r\n <style dangerouslySetInnerHTML={{ __html: css }} />\r\n <p className=\"nf-cap-title\" id={`${uid}-t`}>\r\n {title}\r\n </p>\r\n <div\r\n className=\"nf-cap-grid\"\r\n role=\"grid\"\r\n aria-labelledby={`${uid}-t`}\r\n aria-rowcount={rows.length + 1}\r\n aria-colcount={weeks.length + 1}\r\n tabIndex={0}\r\n aria-activedescendant={activeId}\r\n style={{ gridTemplateColumns: columns }}\r\n onKeyDown={onKeyDown}\r\n onFocus={() => setActive((a) => a ?? (rows.length && weeks.length ? { r: 0, w: 0 } : null))}\r\n onMouseLeave={(e) => {\r\n setTip(null);\r\n hover(null, { clientX: e.clientX, clientY: e.clientY, multi: false });\r\n }}\r\n >\r\n <div className=\"nf-cap-row\" role=\"row\" aria-rowindex={1}>\r\n <div className=\"nf-cap-colhead\" role=\"columnheader\" style={{ textAlign: 'start' }}>\r\n {props.rowHeader ?? <span className=\"nf-cap-sr\">Resource</span>}\r\n </div>\r\n {weeks.map((w, wi) => (\r\n <div key={w} className=\"nf-cap-colhead\" role=\"columnheader\">\r\n {labels[wi]}\r\n </div>\r\n ))}\r\n </div>\r\n {rows.map((r, ri) => (\r\n <div key={r.id} className=\"nf-cap-row\" role=\"row\" aria-rowindex={ri + 2}>\r\n <div className=\"nf-cap-name\" role=\"rowheader\" title={r.name} data-dim={hl && !isHighlighted(r.id, hl) && !weeks.some((w) => isHighlighted(cellKey(r.id, w), hl)) ? '' : undefined}>\r\n {r.name}\r\n </div>\r\n {weeks.map((w, wi) => {\r\n const c = cellAt(r, w);\r\n const step = capacityStep(c);\r\n const col = steps[step]!;\r\n const key = cellKey(r.id, w);\r\n const t: CapacityTarget = { row: r, cell: c, key };\r\n return (\r\n <div\r\n key={w}\r\n id={`${uid}-${ri}-${wi}`}\r\n className=\"nf-cap-cell\"\r\n role=\"gridcell\"\r\n aria-selected={selected === key}\r\n aria-label={describe(t, weekName(w))}\r\n data-step={step}\r\n data-active={active && active.r === ri && active.w === wi ? '' : undefined}\r\n data-dim={dimmed(r, w) ? '' : undefined}\r\n style={{ background: col.background, color: col.text }}\r\n onClick={(e) => choose(ri, wi, isMultiSelect(e))}\r\n onMouseMove={(e) => {\r\n if (!tip || tip.r !== ri || tip.w !== wi) setTip({ r: ri, w: wi, x: e.clientX, y: e.clientY });\r\n hover(t, { clientX: e.clientX, clientY: e.clientY, multi: isMultiSelect(e) });\r\n }}\r\n onContextMenu={\r\n props.onContextMenu\r\n ? (e) => {\r\n e.preventDefault();\r\n props.onContextMenu!(t, { clientX: e.clientX, clientY: e.clientY, multi: isMultiSelect(e) });\r\n }\r\n : undefined\r\n }\r\n >\r\n <span aria-hidden=\"true\">{pctText(c, 'label')}</span>\r\n </div>\r\n );\r\n })}\r\n </div>\r\n ))}\r\n </div>\r\n\r\n {props.showLegend === false ? null : (\r\n <div className=\"nf-cap-legend\" aria-hidden=\"true\">\r\n <span>Booked vs capacity</span>\r\n {STEP_LABELS.map((l, i) => (\r\n <span key={l}>\r\n <i style={{ background: steps[i]!.background }} />\r\n {l}\r\n </span>\r\n ))}\r\n </div>\r\n )}\r\n\r\n {tipT && tip ? (\r\n <div className=\"nf-cap-tip\" role=\"tooltip\" style={{ left: tip.x + 12, top: tip.y + 12 }}>\r\n <b>{tipT.row.name}</b>\r\n Week of {weekName(tipT.cell.week)}: {pctText(tipT.cell, 'tooltip')}\r\n <br />\r\n {hours(tipT.cell.booked)} booked of {hours(tipT.cell.capacity)}\r\n </div>\r\n ) : null}\r\n\r\n {license && !license.licensed && props.watermark !== false ? (\r\n <Watermark prefix=\"nf-cap\" title={licenseMessage(license)} markHtml={markSvg({ size: 16 })} variant={props.watermarkStyle} onClick={props.onWatermarkClick} />\r\n ) : null}\r\n </div>\r\n );\r\n}\r\n\r\n","import { KEY_SEPARATOR, MONTHS, parseHour, parts, toDay, weekday, type WorkingWeek, type Zone } from '@ninthforge/core';\nimport { accentForText, semantic } from '@ninthforge/tokens';\nimport { bookedWorkingHours, isoOfDay, normalise } from './layout';\nimport type { SchedBooking, SchedResource } from './types';\n\n/**\n * NF Capacity: utilisation per resource (or team) per week, for a heatmap. Hosts that already\n * have totals (a Power BI model) pass `CapacityRow`s straight to `<CapacityHeatmap>`; Power Apps\n * and code apps can build them from bookings with `weeklyUtilisation`.\n */\n\nexport interface CapacityCell {\n /** The week's Monday, ISO `YYYY-MM-DD`. */\n week: string;\n /** Hours booked in working time. */\n booked: number;\n /** Hours available that week. */\n capacity: number;\n}\n\nexport interface CapacityRow {\n id: string;\n /** A resource or a team. */\n name: string;\n group?: string;\n cells: CapacityCell[];\n}\n\n/** The five steps on the board: under 60%, 60–79%, 80–94%, 95–100%, over 100%. */\nexport type CapacityStep = 0 | 1 | 2 | 3 | 4;\nexport const STEP_LABELS = ['under 60%', '60–79%', '80–94%', '95–100%', 'over 100%'] as const;\n\n/** Utilisation as a whole percentage, or `null` when nothing is available (capacity 0). */\nexport function utilisationPct(c: Pick<CapacityCell, 'booked' | 'capacity'>): number | null {\n if (!(c.capacity > 0)) return null;\n return Math.round((c.booked / c.capacity) * 100);\n}\n\nexport function capacityStep(c: Pick<CapacityCell, 'booked' | 'capacity'>): CapacityStep {\n const pct = utilisationPct(c);\n if (pct === null) return c.booked > 0 ? 4 : 0;\n if (pct > 100) return 4;\n if (pct >= 95) return 3;\n if (pct >= 80) return 2;\n if (pct >= 60) return 1;\n return 0;\n}\n\n/** The key a cell is selected and highlighted by: row id + U+001F + week. A row id alone means the whole row. */\nexport function cellKey(rowId: string, week: string): string {\n return rowId + KEY_SEPARATOR + week;\n}\n\n/** `count` Mondays from the week containing `fromIso`. */\nexport function weekStarts(fromIso: string, count: number): string[] {\n const d = toDay(fromIso);\n if (Number.isNaN(d)) return [];\n const monday = d - weekday(d);\n const out: string[] = [];\n for (let i = 0; i < count; i++) out.push(isoOfDay(monday + i * 7));\n return out;\n}\n\nexport interface WeeklyOptions {\n zone: Zone;\n /** `mon-fri` (default): capacity counts five days a week. */\n week?: WorkingWeek;\n /** Defaults for resources that don't set their own (decimal hours or \"09:00\"). */\n workStart?: number | string;\n workEnd?: number | string;\n /** Hours per day (default 8). */\n capacityHours?: number;\n}\n\n/**\n * Booked vs available hours per resource per week, using the Scheduler's capacity rules:\n * bookings clipped to each working day, cancelled ones ignored, leave counting up to a full day.\n */\nexport function weeklyUtilisation(\n resources: readonly SchedResource[],\n bookings: readonly SchedBooking[],\n weeks: readonly string[],\n o: WeeklyOptions\n): CapacityRow[] {\n const items = normalise(bookings);\n const byResource = new Map<string, typeof items>();\n for (const it of items) {\n if (!it.resourceId) continue;\n const list = byResource.get(it.resourceId) ?? [];\n list.push(it);\n byResource.set(it.resourceId, list);\n }\n const week = o.week ?? 'mon-fri';\n const days = week === 'mon-fri' ? 5 : 7;\n const hour = (v: number | string | undefined, fallback: number) => {\n const h = v === undefined ? NaN : parseHour(v);\n return Number.isNaN(h) ? fallback : h;\n };\n const ws0 = hour(o.workStart, 9);\n const we0 = hour(o.workEnd, 17);\n return resources.map((r) => {\n const ws = hour(r.workStart, ws0);\n const we = hour(r.workEnd, we0);\n const perDay = r.capacityHours ?? o.capacityHours ?? 8;\n const mine = byResource.get(r.id) ?? [];\n return {\n id: r.id,\n name: r.name,\n group: r.group,\n cells: weeks.map((w) => ({\n week: w,\n booked: bookedWorkingHours(mine, toDay(w), 7, ws, we, week, o.zone, perDay),\n capacity: perDay * days\n }))\n };\n });\n}\n\n/** Rows summed by `group` (resources → teams), in first-seen order. Rows without a group keep their name. */\nexport function rollUp(rows: readonly CapacityRow[]): CapacityRow[] {\n const out = new Map<string, CapacityRow>();\n for (const r of rows) {\n const key = r.group || r.name;\n let t = out.get(key);\n if (!t) out.set(key, (t = { id: key, name: key, cells: r.cells.map((c) => ({ ...c })) }));\n else {\n for (const c of r.cells) {\n const m = t.cells.find((x) => x.week === c.week);\n if (m) {\n m.booked = Math.round((m.booked + c.booked) * 10) / 10;\n m.capacity += c.capacity;\n } else t.cells.push({ ...c });\n }\n }\n }\n return [...out.values()];\n}\n\n/** Every week that appears in the rows, sorted. */\nexport function weeksOf(rows: readonly CapacityRow[]): string[] {\n const s = new Set<string>();\n for (const r of rows) for (const c of r.cells) s.add(c.week);\n return [...s].sort();\n}\n\n/** A week in full, for tooltips and screen readers: \"16 Nov 2026\". */\nexport function weekName(week: string): string {\n const p = parts(toDay(week));\n return `${p.date} ${MONTHS[p.month] ?? ''} ${p.year}`;\n}\n\n/** Column labels like the board: \"2 Nov\", then \"9\", \"16\", …, and the month again when it changes. */\nexport function weekLabels(weeks: readonly string[]): string[] {\n let lastMonth = -1;\n return weeks.map((w) => {\n const p = parts(toDay(w));\n const label = p.month === lastMonth ? String(p.date) : `${p.date} ${MONTHS[p.month] ?? ''}`;\n lastMonth = p.month;\n return label;\n });\n}\n\n/**\n * A message title naming who is overbooked: \"Front-end squad overbooked in Nov\", with \"and 2\n * others\" when more rows are over, or \"No one over capacity\".\n */\nexport function capacityHeadline(rows: readonly CapacityRow[], noun = 'No one'): string {\n let worst: { row: CapacityRow; over: number; first: string } | undefined;\n let count = 0;\n for (const r of rows) {\n let over = 0;\n let first = '';\n for (const c of r.cells) {\n if (capacityStep(c) !== 4) continue;\n over += c.booked - c.capacity;\n if (!first || c.week < first) first = c.week;\n }\n if (!first) continue;\n count++;\n if (!worst || over > worst.over) worst = { row: r, over, first };\n }\n if (!worst) return `${noun} over capacity`;\n const month = MONTHS[parts(toDay(worst.first)).month] ?? '';\n const others = count - 1;\n return `${worst.row.name}${others ? ` and ${others} other${others === 1 ? '' : 's'}` : ''} overbooked in ${month}`;\n}\n\n/* ---------- Colour ---------- */\n\nfunction hexParts(h: string): [number, number, number] | null {\n const s = h.trim().replace('#', '');\n const f = s.length === 3 ? s.replace(/./g, (c) => c + c) : s;\n if (!/^[0-9a-f]{6}$/i.test(f)) return null;\n return [0, 2, 4].map((i) => parseInt(f.slice(i, i + 2), 16)) as [number, number, number];\n}\n\n/** `a` moved toward `b` by `t` (0–1). Non-hex input returns `a`. */\nexport function mixColor(a: string, b: string, t: number): string {\n const x = hexParts(a);\n const y = hexParts(b);\n if (!x || !y) return a;\n return '#' + x.map((v, i) => Math.round(v + (y[i]! - v) * t).toString(16).padStart(2, '0')).join('').toUpperCase();\n}\n\nexport interface StepColor {\n background: string;\n text: string;\n}\n\n/**\n * The five step colours from the accent: three tints, a deep shade at 95–100%, and the Negative\n * token over 100%. Text on each reaches 4.5:1. `override` (a host's five colours) wins.\n */\nexport function stepColors(accent: string, surface: string, override?: readonly string[]): StepColor[] {\n const bgs = override && override.length === 5\n ? override\n : [mixColor(accent, surface, 0.9), mixColor(accent, surface, 0.75), mixColor(accent, surface, 0.45), mixColor(accent, '#000000', 0.25), semantic.negative];\n return bgs.map((bg) => accentForText(bg));\n}\n"],"mappings":"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","names":["React","useEffect","useMemo","useRef","useState","brand","text","iso","MINUTE","isWorkingDay","width","next","ratio","e","text","MINUTE","clean","e","ratio","next","React","text","next","DAY","iso","parts","useEffect","useState","useState","useEffect","MINUTE","next","e","text","React","useEffect","useState","useState","useEffect","e","text","parts","ratio","WEEKDAYS","text","useMemo","useState","useEffect","useRef","x","e","row","span","next","items","iso","React","useMemo","useRef","useState","css","useState","useMemo","useRef","e","next"]}