@ninthforge/gantt 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/index.ts","../src/Gantt.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","../../license/src/codec.ts","../../license/src/browser.ts","../../license/src/index.ts","../../tokens/src/text.ts","../../tokens/src/index.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","../src/edit.ts","../src/layout.ts","../src/styles.ts","../src/exportPng.ts","../src/print.ts","../src/useEditing.ts","../src/useLicense.ts","../src/sample.ts"],"sourcesContent":["export { Gantt } from './Gantt';\r\nexport type {\r\n AddTaskRequest,\r\n EditAction,\r\n Editable,\r\n GanttChange,\r\n GanttColumn,\r\n GanttColors,\r\n GanttColumns,\r\n GanttFormatContext,\r\n GanttProps,\r\n GanttTask,\r\n GanttTaskKind\r\n} from './types';\r\nexport { buildRows, rowsRange, dependencyPaths, dependencyLinks, barGeometry, ROW_H, HEADER_H } from './layout';\r\nexport type { DependencyLink, Row } from './layout';\r\nexport { brokenLinks, validateLink } from './edit';\r\nexport type { Zoom } from '@ninthforge/core';\r\nexport { sampleTasks } from './sample';\r\n/** This package's version (the PCF reports it in its diagnostics output). */\r\nexport { VERSION } from './version';\r\n","import * as React from 'react';\r\nimport { VERSION } from './version';\r\nimport {\r\n useEffect,\r\n useLayoutEffect,\r\n useMemo,\r\n useRef,\r\n useState,\r\n type CSSProperties,\r\n type KeyboardEvent,\r\n type PointerEvent as ReactPointerEvent\r\n} from 'react';\r\nimport { copyDiagnostics, cssScale, localPoint, createTimeScale, formatShort, fromDay, isContextMenuKey, isHighlighted, isMultiSelect, type PointerInfo, today as todayLocal, toDay, ZOOMS, type TimeScale, type WorkingWeek, type Zoom, workingDaysBetween } from '@ninthforge/core';\r\nimport { licenseMessage, renewalNotice as graceNotice, unlocksPro } from '@ninthforge/license';\r\nimport { accentForText, clampTextScale, darkTheme, lightTheme, markSvg, scaleWithText, textVars, themeVars, visibleOn, type ComponentTheme } from '@ninthforge/tokens';\r\nimport {\r\n brokenAmong,\r\n brokenLinks,\r\n canEdit,\r\n dateCellChange,\r\n dragProgress,\r\n fieldsOf,\r\n isNoop,\r\n keyEdit,\r\n linkChange,\r\n progressChange,\r\n pxToDays,\r\n renameChange,\r\n shiftSpan,\r\n spanChange,\r\n durationLabel,\r\n spanLabel,\r\n unlinkChange,\r\n validateLink,\r\n type SpanMode\r\n} from './edit';\r\nimport {\r\n barGeometry,\r\n buildRows,\r\n COLUMN_LABEL,\r\n COLUMN_ORDER,\r\n COLUMN_WIDTH,\r\n scaleColumns,\r\n dependencyLinks,\r\n filterRows,\r\n rowMatches,\r\n HEADER_H,\r\n MIN_COLUMN_WIDTH,\r\n MIN_NAME_WIDTH,\r\n parentIds,\r\n placeTip,\r\n ticksUnderTag,\r\n todayTagWidth,\r\n ROW_H,\r\n rowsRange,\r\n visibleRange,\r\n visibleColumns,\r\n type ColumnKey,\r\n type Row\r\n} from './layout';\r\nimport { css } from './styles';\r\nimport { EmptyActionView, lockedMessage, lockFor, nextUid, Watermark } from '@ninthforge/editing';\r\nimport { Toasts } from './Toast';\r\nimport { exportPng, pngFileName } from './exportPng';\r\nimport { printDocument, printHtml } from './print';\r\nimport type { AddTaskRequest, EditAction, GanttChange, GanttColors, GanttColumns, GanttProps, GanttTask } from './types';\r\nimport { useEditing } from './useEditing';\r\nimport { useLicense } from './useLicense';\r\n\r\nconst ZOOM_LABEL: Record<Zoom, string> = { week: 'Week', month: 'Month', quarter: 'Quarter' };\r\nconst TIP_W = 264;\r\n/** Tooltip height estimate: padding + title, then one line per detail. */\r\nconst TIP_BASE_H = 42;\r\nconst TIP_LINE_H = 22;\r\n/** Pixels of movement before a press becomes a drag, so clicks still select. */\r\nconst DRAG_THRESHOLD = 4;\r\n/** Bars narrower than this get no resize handles or progress knob (they'd cover the bar). */\r\nconst MIN_HANDLE_BAR = 24;\r\n/** Approximate width of the replace-link confirm, for keeping it in view. */\r\nconst CONFIRM_W = 440;\r\n/** The grid never takes more than this; the timeline needs room. */\r\nconst MAX_GRID_WIDTH = 900;\r\n/** Phone width (QA-2026-10-31-8): below this the task list shows only the name, at NARROW_SHARE of the width. */\r\nconst NARROW_WIDTH = 600;\r\nconst NARROW_SHARE = 0.42;\r\n/** Keyboard step for resizing the grid divider. */\r\nconst DIVIDER_STEP = 24;\r\n/** A click on a selected row's cell waits this long before editing, so a double-click can still open. */\r\nconst EDIT_CLICK_DELAY = 280;\r\n\r\ntype InlineField = 'name' | 'start' | 'end';\r\n\r\n/** Rows rendered before the viewport has been measured (and in server rendering). */\r\nconst INITIAL_ROWS = 60;\r\n/** After this, the entrance animation is off (it covers the 400ms stagger + 240ms grow). */\r\nconst SETTLE_MS = 900;\r\n\r\n/** Menu wording for the column chooser (headers use the shorter COLUMN_LABEL). */\r\nconst COLUMN_FULL_LABEL: Record<ColumnKey, string> = {\r\n owner: 'Owner',\r\n progress: '% done',\r\n start: 'Start date',\r\n end: 'End date',\r\n duration: 'Duration'\r\n};\r\n\r\ntype Zone = 'move' | 'start' | 'end' | 'progress' | 'link';\r\n\r\ninterface DragState {\r\n id: string;\r\n zone: Zone;\r\n pointerId: number;\r\n x0: number;\r\n y0: number;\r\n dx: number;\r\n clientX: number;\r\n clientY: number;\r\n started: boolean;\r\n row: Row;\r\n barWidth: number;\r\n ghost: { left: number; width: number } | null;\r\n /** CSS scale around the chart at pointer-down: screen px ÷ this = chart px. */\r\n sx: number;\r\n}\r\n\r\ninterface Preview {\r\n id: string;\r\n patch: Partial<GanttTask>;\r\n ghost: { left: number; width: number } | null;\r\n}\r\n\r\nfunction 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\nfunction resolveTheme(theme: GanttProps['theme'], accent?: string, colors?: Partial<GanttColors>): Record<string, string> {\r\n const base = theme === 'dark' ? darkTheme : lightTheme;\r\n const overrides: Partial<ComponentTheme> = { ...(typeof theme === 'object' ? theme : {}), ...hostColors(colors) };\r\n const ownText = overrides.accentText;\r\n // A host's progress colour wins over the accent (a report theme or high contrast).\r\n if (accent && !colors?.progress) {\r\n overrides.accent = accent;\r\n overrides.focus = overrides.focus ?? accent;\r\n // Derive the tint and selection from the accent so a rebrand stays coherent.\r\n const surface = overrides.surface ?? base.surface;\r\n const dark = theme === 'dark';\r\n overrides.accentTint = overrides.accentTint ?? `color-mix(in srgb, ${accent} ${dark ? 32 : 20}%, ${surface})`;\r\n overrides.selected = overrides.selected ?? `color-mix(in srgb, ${accent} ${dark ? 14 : 6}%, ${surface})`;\r\n }\r\n // Small text on the accent (today tag, primary buttons) must reach 4.5:1 whatever accent a\r\n // customer picks: flip the text to white or ink, and shade the background a touch if needed.\r\n // A theme that sets its own accentText is trusted as is.\r\n const finalAccent = overrides.accent ?? base.accent;\r\n const pair = ownText ? { background: finalAccent, text: ownText } : accentForText(finalAccent);\r\n overrides.accentText = pair.text;\r\n // Tooltip warnings (\"3 days behind baseline\") are small text on the tooltip's own background,\r\n // which is dark in the light theme: lift the semantic colours to 4.5:1 there.\r\n const tipBg = overrides.tooltipBg ?? base.tooltipBg;\r\n return {\r\n ...themeVars(overrides, base),\r\n '--nf-accent-solid': pair.background,\r\n '--nf-tip-warn': visibleOn(overrides.milestone ?? base.milestone, tipBg, 4.5),\r\n '--nf-tip-danger': visibleOn(overrides.negative ?? base.negative, tipBg, 4.5)\r\n };\r\n}\r\n\r\n/**\r\n * Host colours as theme values. Foreground and background (high contrast) replace every grey,\r\n * so nothing is drawn in a colour the host didn't give.\r\n */\r\nexport function hostColors(c?: Partial<GanttColors>): Partial<ComponentTheme> {\r\n if (!c) return {};\r\n const o: Partial<ComponentTheme> = {};\r\n if (c.bar) o.accentTint = c.bar;\r\n if (c.progress) o.accent = o.focus = c.progress;\r\n if (c.phase) o.phase = c.phase;\r\n if (c.milestone) o.milestone = c.milestone;\r\n // lineSoft is also a background (segmented controls, hovers), so it takes the background.\r\n if (c.background) o.surface = o.surfaceAlt = o.selected = o.lineSoft = o.tooltipInk = c.background;\r\n if (c.foreground) o.ink = o.muted = o.line = o.baseline = o.tooltipBg = c.foreground;\r\n // Broken links: the host's highlight colour, never a red the host didn't give.\r\n if (c.foreground) o.negative = c.milestone ?? c.foreground;\r\n return o;\r\n}\r\n\r\n/** Plain colour values (not CSS variables) for print and PNG export. */\r\nfunction palette(theme: GanttProps['theme'], accent: string | undefined, forceLight: boolean): ComponentTheme {\r\n const base = theme === 'dark' && !forceLight ? darkTheme : lightTheme;\r\n const own = typeof theme === 'object' && !forceLight ? theme : {};\r\n return { ...base, ...own, ...(accent ? { accent, accentText: (own as Partial<ComponentTheme>).accentText ?? accentForText(accent).text } : {}) };\r\n}\r\n\r\n/** Marks an action that needs a key (view-only without one). */\r\nconst KeyIcon = () => (\r\n <svg className=\"nf-g-key\" width=\"12\" height=\"12\" viewBox=\"0 0 12 12\" aria-hidden=\"true\">\r\n <circle cx=\"4\" cy=\"6\" r=\"2.4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" />\r\n <path d=\"M6.4 6 H11 M9.2 6 V8 M10.6 6 V7.4\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.4\" strokeLinecap=\"round\" />\r\n </svg>\r\n);\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\nfunction Tooltip({\r\n row,\r\n left,\r\n top,\r\n fmtDay,\r\n fmtPct,\r\n brokenFrom,\r\n locked,\r\n week,\r\n rtl\r\n}: {\r\n row: Row;\r\n left: number;\r\n top: number;\r\n fmtDay: (day: number) => string;\r\n fmtPct: (v: number, use: 'grid' | 'tooltip') => string;\r\n brokenFrom: string[];\r\n locked: boolean;\r\n week: WorkingWeek;\r\n rtl: boolean;\r\n}) {\r\n const t = row.task;\r\n const isMs = row.kind === 'milestone';\r\n const slip = !Number.isNaN(row.baselineEnd) && !Number.isNaN(row.end) ? row.end - row.baselineEnd : 0;\r\n return (\r\n <div className=\"nf-g-tip\" style={{ left, top }} role=\"tooltip\" dir={rtl ? 'rtl' : undefined}>\r\n <b>{t.name}</b>\r\n <div className=\"nf-g-meta\">\r\n {isMs\r\n ? fmtDay(row.start)\r\n : `${fmtDay(row.start)} → ${fmtDay(row.end)} · ${durationLabel(row.start, row.end, week)}`}\r\n {!isMs ? ` · ${fmtPct(row.progress, 'tooltip')} done` : ''}\r\n </div>\r\n {t.owner ? <div className=\"nf-g-meta\">{t.owner.name}</div> : null}\r\n {slip !== 0 ? (\r\n <div className=\"nf-g-warn\">\r\n {Math.abs(slip)} {Math.abs(slip) === 1 ? 'day' : 'days'} {slip > 0 ? 'behind' : 'ahead of'} baseline\r\n </div>\r\n ) : null}\r\n {brokenFrom.map((name) => (\r\n <div key={name} className=\"nf-g-warn\" data-tone=\"danger\">\r\n Starts before '{name}' ends\r\n </div>\r\n ))}\r\n {locked ? <div className=\"nf-g-meta\">Read-only</div> : null}\r\n </div>\r\n );\r\n}\r\n\r\nexport function Gantt(props: GanttProps) {\r\n const {\r\n tasks,\r\n title = 'Work plan',\r\n showToolbar = true,\r\n height = '100%',\r\n gridWidth: initialGridWidthProp,\r\n columns: columnsProp = {},\r\n showFilter = true,\r\n onOpen,\r\n onTaskChange,\r\n onAddTask,\r\n singlePredecessor = false,\r\n workingWeek = 'mon-fri'\r\n } = props;\r\n\r\n /* ---------- Grid layout: columns, widths, filter ---------- */\r\n\r\n const [columnOverride, setColumnOverride] = useState<GanttColumns>({});\r\n // Phone width (QA-2026-10-31-8): under NARROW_WIDTH the list shows just the name and takes\r\n // NARROW_SHARE of the width, so the bars stay on screen. Columns or a width the user picks win.\r\n const [boxWidth, setBoxWidth] = useState(0);\r\n const gridTouched = useRef(false);\r\n const narrow = boxWidth > 0 && boxWidth < NARROW_WIDTH;\r\n const allCols = visibleColumns({ ...columnsProp, ...columnOverride });\r\n const cols = narrow ? allCols.filter((c) => columnOverride[c] === true) : allCols;\r\n const [colWidths, setColWidths] = useState<Record<ColumnKey, number>>(() => scaleColumns(COLUMN_WIDTH, clampTextScale(props.textScale ?? 1)));\r\n // The task list starts wider with bigger text, so names stay readable (360 px at 100 %).\r\n const [gridWidthState, setGridWidthState] = useState(() => initialGridWidthProp ?? Math.round(360 * clampTextScale(props.textScale ?? 1)));\r\n // The name column always keeps at least MIN_NAME_WIDTH (32 = the cell's horizontal padding).\r\n const minGrid = Math.round(MIN_NAME_WIDTH * clampTextScale(props.textScale ?? 1)) + 32 + cols.reduce((s, c) => s + colWidths[c], 0);\r\n const gridWidth = Math.max(narrow && !gridTouched.current ? Math.min(gridWidthState, Math.round(boxWidth * NARROW_SHARE)) : gridWidthState, minGrid);\r\n const setGridWidth = (w: number) => {\r\n const next = Math.round(Math.max(minGrid, Math.min(w, MAX_GRID_WIDTH)));\r\n gridTouched.current = true;\r\n setGridWidthState(next);\r\n props.onGridWidthChange?.(next);\r\n };\r\n const toggleColumn = (c: ColumnKey) => {\r\n const adding = !cols.includes(c);\r\n // Grow or shrink the grid with the column so the name column keeps its width.\r\n // (Render clamps to the new minimum, which already accounts for the column change.)\r\n gridTouched.current = true;\r\n const nextWidth = Math.min(MAX_GRID_WIDTH, gridWidth + (adding ? colWidths[c] : -colWidths[c]));\r\n setGridWidthState(nextWidth);\r\n props.onGridWidthChange?.(nextWidth);\r\n const nextOverride = { ...columnOverride, [c]: adding };\r\n setColumnOverride(nextOverride);\r\n const now = visibleColumns({ ...columnsProp, ...nextOverride });\r\n props.onColumnsChange?.(Object.fromEntries(COLUMN_ORDER.map((k) => [k, now.includes(k)])) as Required<GanttColumns>);\r\n };\r\n const [filter, setFilter] = useState('');\r\n const filtering = filter.trim() !== '';\r\n // Toolbar menus (Columns, Export): one open at a time.\r\n const [toolMenu, setToolMenu] = useState<'columns' | 'export' | null>(null);\r\n const colMenu = toolMenu === 'columns';\r\n const toolMenuRef = useRef<HTMLDivElement>(null);\r\n useEffect(() => {\r\n if (!toolMenu) return;\r\n toolMenuRef.current?.querySelector<HTMLButtonElement>('button')?.focus();\r\n const close = (e: globalThis.PointerEvent) => {\r\n if ((e.target as HTMLElement).closest?.('.nf-g-menuwrap')?.getAttribute('data-menu') !== toolMenu) setToolMenu(null);\r\n };\r\n document.addEventListener('pointerdown', close, true);\r\n return () => document.removeEventListener('pointerdown', close, true);\r\n }, [toolMenu]);\r\n const menuKeys = (e: React.KeyboardEvent) => {\r\n const items = Array.from(toolMenuRef.current?.querySelectorAll<HTMLButtonElement>('button') ?? []);\r\n const at = items.indexOf(document.activeElement as HTMLButtonElement);\r\n if (e.key === 'Escape' || e.key === 'Tab') {\r\n setToolMenu(null);\r\n if (e.key === 'Tab') return;\r\n (toolMenuRef.current?.parentElement?.querySelector('[aria-haspopup]') as HTMLElement | null)?.focus();\r\n } else if (e.key === 'ArrowDown') items[(at + 1) % items.length]?.focus();\r\n else if (e.key === 'ArrowUp') items[(at - 1 + items.length) % items.length]?.focus();\r\n else if (e.key === 'Home') items[0]?.focus();\r\n else if (e.key === 'End') items[items.length - 1]?.focus();\r\n else return;\r\n e.preventDefault();\r\n };\r\n\r\n const uid = useState(() => nextUid('nfg'))[0];\r\n // Text size (docs/specs/text-size.md): one scale for every role; rows, header and tooltip grow with it.\r\n const textScale = clampTextScale(props.textScale ?? 1);\r\n const rowH = scaleWithText(ROW_H, textScale);\r\n const headerH = scaleWithText(HEADER_H, textScale);\r\n const tipW = Math.round(TIP_W * textScale);\r\n const rootRef = useRef<HTMLDivElement>(null);\r\n // Right-to-left: from the prop, else inherited from the page. The task list mirrors; the\r\n // timeline keeps time running left to right (the convention for Gantt charts).\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 scrollRef = useRef<HTMLDivElement>(null);\r\n const bodyRef = useRef<HTMLDivElement>(null);\r\n\r\n const [zoomState, setZoomState] = useState<Zoom>(props.defaultZoom ?? 'month');\r\n const zoom = props.zoom ?? zoomState;\r\n const setZoom = (z: Zoom) => {\r\n if (props.zoom === undefined) setZoomState(z);\r\n props.onZoomChange?.(z);\r\n };\r\n\r\n const [selState, setSelState] = useState<string | null>(null);\r\n const selectedId = props.selectedId !== undefined ? props.selectedId : selState;\r\n const select = (id: string | null, multi = false) => {\r\n if (props.selectedId === undefined) setSelState(id);\r\n props.onSelect?.(id, { multi });\r\n };\r\n // Host hooks: formatters, dimming, hover and context menu.\r\n const fmtDay = (day: number) => (props.formatDate ? props.formatDate(fromDay(day)) : formatShort(day));\r\n const fmtPct = (v: number, use: 'grid' | 'tooltip') => (props.formatValue ? props.formatValue(v, { field: 'progress', use }) : `${v}%`);\r\n const hl = props.highlighted && props.highlighted.length ? new Set(props.highlighted) : undefined;\r\n const hovering = useRef(false);\r\n const hostHover = (task: GanttTask | null, at: PointerInfo) => {\r\n if (!props.onHover) return;\r\n if (task === null && !hovering.current) return;\r\n hovering.current = task !== null;\r\n props.onHover(task, at);\r\n };\r\n /** Where a task's bar (or milestone, or row) is on screen: the pointer position for keyboard hooks. */\r\n const anchorOf = (id: string): PointerInfo | null => {\r\n const el = typeof document !== 'undefined' ? document.getElementById(`${uid}-${id}`) : null;\r\n const mark = el?.querySelector('.nf-g-bar, .nf-g-ms') ?? el;\r\n const r = mark?.getBoundingClientRect();\r\n return r ? { clientX: r.left + r.width / 2, clientY: r.top + r.height / 2, multi: false } : null;\r\n };\r\n\r\n const [baselineState, setBaselineState] = useState(true);\r\n const showBaseline = props.showBaseline ?? baselineState;\r\n const [collapsed, setCollapsed] = useState<Set<string>>(() => new Set());\r\n const [hoverId, setHoverId] = useState<string | null>(null);\r\n\r\n const toggle = (id: string, open?: boolean) =>\r\n setCollapsed((prev) => {\r\n const next = new Set(prev);\r\n const shouldOpen = open ?? next.has(id);\r\n if (shouldOpen) next.delete(id);\r\n else next.add(id);\r\n return next;\r\n });\r\n\r\n /* ---------- Editing state ---------- */\r\n\r\n // View-only without a key that unlocks Pro actions (Matt, 2026-11-08). Quiet while checking.\r\n const license = useLicense(props.licenseKey, { product: 'plan', tenantId: props.tenantId });\r\n const pro = unlocksPro(license);\r\n const editing = useEditing(tasks, onTaskChange, lockFor(license, pro, 'Plan', 'gantt'));\r\n const [preview, setPreview] = useState<Preview | null>(null);\r\n const [linkDrag, setLinkDrag] = useState<{ fromId: string; x: number; y: number; targetId: string | null } | null>(null);\r\n const [linkMode, setLinkMode] = useState<string | null>(null);\r\n const [selectedLink, setSelectedLink] = useState<string | null>(null);\r\n const [menu, setMenu] = useState<{ id: string; x: number; y: number } | null>(null);\r\n const [confirm, setConfirm] = useState<{ change: GanttChange; text: string; top: number; left: number } | null>(null);\r\n const dragRef = useRef<DragState | null>(null);\r\n const rafRef = useRef(0);\r\n const suppressClick = useRef(false);\r\n\r\n const can = (action: EditAction, row?: Row) =>\r\n canEdit(props.editable, action === 'add' ? !!onAddTask : !!onTaskChange, action, row);\r\n\r\n // What the user sees: host data + committed edits + the in-flight drag.\r\n const committed = editing.shown;\r\n const shown = useMemo(\r\n () => (preview ? committed.map((t) => (t.id === preview.id ? { ...t, ...preview.patch } : t)) : committed),\r\n [committed, preview]\r\n );\r\n const taskById = useMemo(() => new Map(shown.map((t) => [t.id, t] as const)), [shown]);\r\n const nameOf = (id: string) => taskById.get(id)?.name ?? 'task';\r\n\r\n // Track the timeline viewport so the scale always fills the available width.\r\n const [viewport, setViewport] = useState(0);\r\n useEffect(() => {\r\n const el = scrollRef.current;\r\n if (!el) return;\r\n const measure = () => {\r\n setBoxWidth(el.clientWidth);\r\n setViewport(Math.max(0, el.clientWidth - gridWidth));\r\n };\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 }, [gridWidth]);\r\n\r\n // While filtering, collapsed phases open so matches inside them show.\r\n const noneCollapsed = useMemo(() => new Set<string>(), []);\r\n const allRows = useMemo(() => buildRows(shown, filtering ? noneCollapsed : collapsed), [shown, collapsed, filtering, noneCollapsed]);\r\n const rows = useMemo(() => filterRows(allRows, filter), [allRows, filter]);\r\n useEffect(() => {\r\n // Count the matches, not the phases kept around them for context (QA question 2026-10-31).\r\n const n = rows.filter((r) => rowMatches(r, filter)).length;\r\n if (filtering) editing.announce(`${n} ${n === 1 ? 'task matches' : 'tasks match'}`);\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [filter]);\r\n // The range ignores the in-flight drag so the scale doesn't shift under the pointer.\r\n const range = useMemo(() => rowsRange(buildRows(committed)), [committed]);\r\n const todayDay = props.today ? toDay(props.today) : todayLocal();\r\n const scale = useMemo(() => {\r\n const r = range ?? { from: todayDay - 14, to: todayDay + 60 };\r\n return createTimeScale({ from: r.from, to: r.to, zoom, minWidth: viewport });\r\n }, [range, zoom, todayDay, viewport]);\r\n\r\n const rowsRef = useRef(rows);\r\n rowsRef.current = rows;\r\n const scaleRef = useRef<TimeScale>(scale);\r\n scaleRef.current = scale;\r\n const rowHRef = useRef(rowH);\r\n rowHRef.current = rowH;\r\n const headerHRef = useRef(headerH);\r\n headerHRef.current = headerH;\r\n\r\n const renewal = props.renewalNotice !== false ? graceNotice(license) : null;\r\n const style = {\r\n ...resolveTheme(props.theme, props.accent, props.colors),\r\n ...textVars(textScale, props.textSizes),\r\n '--nf-row': `${rowH}px`,\r\n '--nf-head': `${headerH}px`,\r\n '--nf-tip-w': `${tipW}px`,\r\n height\r\n } as CSSProperties;\r\n\r\n const todayInView = todayDay >= scale.startDay && todayDay < scale.endDay;\r\n const todayX = scale.x(todayDay) + scale.dayWidth / 2;\r\n const underTag = useMemo(\r\n () => (todayInView ? ticksUnderTag(scale.minor, todayX, todayTagWidth(formatShort(todayDay), textScale), 2, textScale) : new Set<number>()),\r\n [todayInView, scale, todayX, todayDay]\r\n );\r\n\r\n // Open on today (or the start of the plan) whenever the zoom changes.\r\n useEffect(() => {\r\n const el = scrollRef.current;\r\n if (!el) return;\r\n el.scrollLeft = todayInView ? Math.max(0, todayX - 200) : 0;\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [zoom]);\r\n\r\n const scrollToToday = () => {\r\n const el = scrollRef.current;\r\n if (!el || !todayInView) return;\r\n const still = typeof matchMedia === 'function' && matchMedia('(prefers-reduced-motion: reduce)').matches;\r\n el.scrollTo({ left: Math.max(0, todayX - 200), behavior: still ? 'auto' : 'smooth' });\r\n editing.announce(`Scrolled to today, ${formatShort(todayDay)}`);\r\n };\r\n\r\n const selectedIndex = rows.findIndex((r) => r.task.id === selectedId);\r\n\r\n /* ---------- Virtualised rows ---------- */\r\n\r\n // Only rows near the viewport render; state changes only when that window moves.\r\n const [vis, setVis] = useState(() => ({ first: 0, last: Math.min(rows.length, INITIAL_ROWS) - 1 }));\r\n const scrollRaf = useRef(0);\r\n const updateVisible = () => {\r\n const el = scrollRef.current;\r\n if (!el) return;\r\n const v = visibleRange(el.scrollTop, el.clientHeight, rowsRef.current.length, rowHRef.current, headerHRef.current);\r\n setVis((prev) => (prev.first === v.first && prev.last === v.last ? prev : v));\r\n };\r\n // The tooltip sits outside the scroll area, so it re-renders on scroll to follow its bar.\r\n const tipShown = useRef(false);\r\n const [, setScrollTick] = useState(0);\r\n const onScroll = () => {\r\n if (scrollRaf.current) return;\r\n scrollRaf.current = requestAnimationFrame(() => {\r\n scrollRaf.current = 0;\r\n updateVisible();\r\n if (tipShown.current) setScrollTick((t) => t + 1);\r\n });\r\n };\r\n useEffect(() => {\r\n updateVisible();\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [rows.length, viewport]);\r\n useEffect(() => () => cancelAnimationFrame(scrollRaf.current), []);\r\n // Connectors and broken-link checks only for links crossing the rendered rows: a drag frame\r\n // on a 5,000-task plan then costs what's on screen, not every link in the plan.\r\n const visLinks = useMemo(() => dependencyLinks(rows, scale, rowH, vis), [rows, scale, vis, rowH]);\r\n const broken = useMemo(() => brokenAmong(visLinks, rows), [visLinks, rows]);\r\n // Bars animate in on first paint only; rows that mount while scrolling appear instantly.\r\n const [settled, setSettled] = useState(props.animate === false);\r\n useEffect(() => {\r\n if (props.animate === false) return;\r\n const t = setTimeout(() => setSettled(true), SETTLE_MS);\r\n return () => clearTimeout(t);\r\n }, []);\r\n const collapseAll = () => {\r\n setCollapsed(parentIds(buildRows(shown)));\r\n editing.announce('All phases collapsed');\r\n };\r\n const expandAll = () => {\r\n setCollapsed(new Set());\r\n editing.announce('All phases expanded');\r\n };\r\n const dragging = preview !== null || linkDrag !== null;\r\n const tipRow = dragging || confirm || menu || props.tooltip === 'none'\r\n ? undefined\r\n : rows.find((r) => r.task.id === (hoverId ?? selectedId) && r.kind !== 'phase' && !Number.isNaN(r.start));\r\n\r\n const focusGrid = () => scrollRef.current?.focus({ preventScroll: true });\r\n // Empty states sit in the horizontally scrolling area: pin them to what's on screen.\r\n const emptyStyle: CSSProperties = { position: 'sticky', left: 0, width: viewport + gridWidth || undefined };\r\n // \"Loading tasks…\" is a role=\"status\", which may not sit inside the treegrid (QA-2026-10-31-5),\r\n // so it renders in the root, over the chart body: just below the scroll area's top and header.\r\n const loadingShown = rows.length === 0 && !filtering && !!props.loading;\r\n const [scrollTop0, setScrollTop0] = useState(0);\r\n useLayoutEffect(() => {\r\n if (loadingShown && scrollRef.current) setScrollTop0(scrollRef.current.offsetTop);\r\n }, [loadingShown, height]);\r\n\r\n /* ---------- Committing edits ---------- */\r\n\r\n const commitEdit = (change: GanttChange) => {\r\n const t = committed.find((x) => x.id === change.id);\r\n if (!t || isNoop(t, change)) return;\r\n editing.commit(change);\r\n };\r\n\r\n const spanFor = (r: Row) => ({ start: r.start, end: r.end });\r\n\r\n /* ---------- Resizing the grid and its columns ---------- */\r\n\r\n const resizeRef = useRef<{ kind: 'grid' | ColumnKey; x0: number; w0: number; g0: number; pointerId: number; sx: number } | null>(null);\r\n const resizeHandlers = (kind: 'grid' | ColumnKey) => ({\r\n onPointerDown: (e: ReactPointerEvent<HTMLElement>) => {\r\n if (e.button !== 0) return;\r\n e.preventDefault();\r\n e.stopPropagation();\r\n e.currentTarget.setPointerCapture?.(e.pointerId);\r\n resizeRef.current = { kind, x0: e.clientX, w0: kind === 'grid' ? gridWidth : colWidths[kind], g0: gridWidth, pointerId: e.pointerId, sx: cssScale(rootRef.current).x };\r\n },\r\n onPointerMove: (e: ReactPointerEvent<HTMLElement>) => {\r\n const r = resizeRef.current;\r\n if (!r || r.pointerId !== e.pointerId) return;\r\n const dx = (e.clientX - r.x0) / r.sx;\r\n if (r.kind === 'grid') {\r\n setGridWidth(r.w0 + dx);\r\n return;\r\n }\r\n // A column grows or shrinks the grid with it, so the name column keeps its width.\r\n // In RTL a column's resize edge is its left edge, so dragging left widens it.\r\n const w = Math.max(MIN_COLUMN_WIDTH, Math.round(r.w0 + (rtl ? -dx : dx)));\r\n const key = r.kind;\r\n setColWidths((cw) => ({ ...cw, [key]: w }));\r\n setGridWidth(r.g0 + (w - r.w0));\r\n },\r\n onPointerUp: () => {\r\n resizeRef.current = null;\r\n },\r\n onPointerCancel: () => {\r\n resizeRef.current = null;\r\n }\r\n });\r\n\r\n /* ---------- Inline editing (name, start, end) ---------- */\r\n\r\n const [inline, setInline] = useState<{ id: string; field: InlineField; value: string } | null>(null);\r\n /** The open inline edit has been saved or cancelled (its blur must not save it again). */\r\n const inlineDone = useRef(false);\r\n const editTimer = useRef<ReturnType<typeof setTimeout> | null>(null);\r\n const cancelEditTimer = () => {\r\n if (editTimer.current) clearTimeout(editTimer.current);\r\n editTimer.current = null;\r\n };\r\n useEffect(() => cancelEditTimer, []);\r\n\r\n const canInline = (r: Row, field: InlineField) =>\r\n field === 'name'\r\n ? can('rename', r)\r\n : r.kind !== 'phase' && !Number.isNaN(r.start) && (field === 'start' ? can('move', r) : r.kind === 'task' && can('resize', r));\r\n\r\n const startInline = (r: Row, field: InlineField) => {\r\n if (!canInline(r, field)) return;\r\n const value = field === 'name' ? r.task.name : fromDay(field === 'start' ? r.start : r.end);\r\n inlineDone.current = false;\r\n setInline({ id: r.task.id, field, value });\r\n editing.announce(`Editing ${field === 'name' ? 'name' : field + ' date'} of '${r.task.name}'. Enter saves, Escape cancels.`);\r\n };\r\n\r\n const finishInline = (save: boolean) => {\r\n // Enter and Escape move focus to the grid, which blurs the input and calls this again with\r\n // the edit still in its closure. Only the first call counts (QA-2026-10-31-1: Escape saved).\r\n if (inlineDone.current) return;\r\n inlineDone.current = true;\r\n const cur = inline;\r\n setInline(null);\r\n focusGrid();\r\n if (!cur || !save) return;\r\n const row = rows.find((r) => r.task.id === cur.id);\r\n const task = committed.find((t) => t.id === cur.id);\r\n if (!row || !task) return;\r\n if (cur.field === 'name') {\r\n const change = renameChange(task, cur.value, 'keyboard');\r\n if (change) commitEdit(change);\r\n return;\r\n }\r\n const day = toDay(cur.value);\r\n if (Number.isNaN(day)) return;\r\n commitEdit(dateCellChange(task, row.kind === 'milestone', spanFor(row), cur.field, day, 'keyboard'));\r\n };\r\n\r\n /** A click on an already-selected row's cell edits it — after a pause, so double-click still opens. */\r\n const clickToEdit = (r: Row, field: InlineField, wasSelected: boolean) => {\r\n cancelEditTimer();\r\n if (!wasSelected || !canInline(r, field)) return;\r\n editTimer.current = setTimeout(() => startInline(r, field), EDIT_CLICK_DELAY);\r\n };\r\n\r\n const inlineInput = (r: Row, field: InlineField, label: string) =>\r\n inline && inline.id === r.task.id && inline.field === field ? (\r\n <input\r\n className=\"nf-g-inline\"\r\n type={field === 'name' ? 'text' : 'date'}\r\n aria-label={`${label} of ${r.task.name}`}\r\n value={inline.value}\r\n autoFocus\r\n onFocus={(e) => field === 'name' && e.currentTarget.select()}\r\n onChange={(e) => setInline({ ...inline, value: e.target.value })}\r\n onClick={(e) => e.stopPropagation()}\r\n onDoubleClick={(e) => e.stopPropagation()}\r\n onPointerDown={(e) => e.stopPropagation()}\r\n onKeyDown={(e) => {\r\n if (e.key === 'Enter') {\r\n e.preventDefault();\r\n finishInline(true);\r\n } else if (e.key === 'Escape') {\r\n e.preventDefault();\r\n finishInline(false);\r\n editing.announce('Edit cancelled');\r\n }\r\n }}\r\n onBlur={() => finishInline(true)}\r\n />\r\n ) : null;\r\n\r\n /** Link `fromId` → `toId`, after validation and (single predecessor) a replace confirm. */\r\n const requestLink = (fromId: string, toId: string, source: GanttChange['source']) => {\r\n const toRow = rows.find((r) => r.task.id === toId);\r\n const target = committed.find((t) => t.id === toId);\r\n if (!toRow || !target) return;\r\n if (!can('link', toRow)) {\r\n editing.toast({ text: `'${target.name}' can't be linked.`, tone: 'error' });\r\n return;\r\n }\r\n const check = validateLink(committed, fromId, toId);\r\n if (check === 'self') return editing.toast({ text: \"A task can't depend on itself.\", tone: 'error' });\r\n if (check === 'exists') return editing.announce(`'${target.name}' already follows '${nameOf(fromId)}'`);\r\n if (check === 'cycle')\r\n return editing.toast({\r\n text: `That would make a loop: '${nameOf(fromId)}' already depends on '${target.name}'.`,\r\n tone: 'error'\r\n });\r\n const { change, replaces } = linkChange(target, fromId, singlePredecessor, source);\r\n if (replaces) {\r\n // Anchor under the target bar, kept inside the visible part of the timeline.\r\n const el = scrollRef.current;\r\n const want = gridWidth + (barGeometry(toRow.start, toRow.end, scale)?.left ?? 0);\r\n const visibleFrom = (el?.scrollLeft ?? 0) + gridWidth + 8;\r\n const visibleTo = (el?.scrollLeft ?? 0) + (el?.clientWidth ?? 800) - CONFIRM_W - 8;\r\n setConfirm({\r\n change,\r\n text: `Replace dependency on '${nameOf(replaces)}'?`,\r\n top: toRow.index * rowH + rowH,\r\n left: Math.max(visibleFrom, Math.min(want, visibleTo))\r\n });\r\n return;\r\n }\r\n commitEdit(change);\r\n };\r\n\r\n /* ---------- Pointer editing ---------- */\r\n\r\n const zoneAction = (zone: Zone): EditAction =>\r\n zone === 'progress' ? 'progress' : zone === 'link' ? 'link' : zone === 'move' ? 'move' : 'resize';\r\n\r\n const bodyPoint = (clientX: number, clientY: number) => {\r\n if (!bodyRef.current) return { x: 0, y: 0 };\r\n const p = localPoint(bodyRef.current, clientX, clientY);\r\n return { x: p.x - gridWidth, y: p.y };\r\n };\r\n\r\n const patchFor = (d: DragState): Partial<GanttTask> => {\r\n const t = d.row.task;\r\n if (d.zone === 'progress') return { progress: dragProgress(d.row.progress, d.dx, d.barWidth) };\r\n const mode: SpanMode = d.zone === 'start' ? 'start' : d.zone === 'end' ? 'end' : 'move';\r\n const span = shiftSpan(mode, spanFor(d.row), pxToDays(d.dx, scaleRef.current.dayWidth), workingWeek);\r\n return fieldsOf(spanChange(t, d.row.kind === 'milestone', span, 'drag'));\r\n };\r\n\r\n const flush = () => {\r\n rafRef.current = 0;\r\n const d = dragRef.current;\r\n if (!d || !d.started) return;\r\n if (d.zone === 'link') {\r\n const p = bodyPoint(d.clientX, d.clientY);\r\n const target = rowsRef.current[Math.floor(p.y / rowH)];\r\n setLinkDrag({ fromId: d.id, x: p.x, y: p.y, targetId: target && target.task.id !== d.id ? target.task.id : null });\r\n return;\r\n }\r\n setPreview({ id: d.id, patch: patchFor(d), ghost: d.ghost });\r\n };\r\n\r\n const cancelDrag = (announce = true) => {\r\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\r\n rafRef.current = 0;\r\n const wasActive = dragRef.current?.started;\r\n dragRef.current = null;\r\n setPreview(null);\r\n setLinkDrag(null);\r\n if (announce && wasActive) editing.announce('Drag cancelled');\r\n };\r\n\r\n const onEditPointerDown = (e: ReactPointerEvent<HTMLElement>, r: Row) => {\r\n if (e.button !== 0) return;\r\n const el = (e.target as HTMLElement).closest<HTMLElement>('[data-drag]');\r\n const zone = (el?.getAttribute('data-drag') as Zone | null) ?? 'move';\r\n if (!can(zoneAction(zone), r)) return;\r\n e.stopPropagation();\r\n // No text selection or native drag; keep keyboard focus on the grid for undo and nudges.\r\n e.preventDefault();\r\n focusGrid();\r\n e.currentTarget.setPointerCapture?.(e.pointerId);\r\n const g = r.kind === 'milestone' ? null : barGeometry(r.start, r.end, scale);\r\n dragRef.current = {\r\n id: r.task.id,\r\n zone,\r\n pointerId: e.pointerId,\r\n x0: e.clientX,\r\n y0: e.clientY,\r\n dx: 0,\r\n clientX: e.clientX,\r\n clientY: e.clientY,\r\n started: false,\r\n row: r,\r\n barWidth: g?.width ?? 0,\r\n ghost: zone === 'link' || zone === 'progress' ? null : g,\r\n sx: cssScale(rootRef.current).x\r\n };\r\n };\r\n\r\n const onEditPointerMove = (e: ReactPointerEvent<HTMLElement>) => {\r\n const d = dragRef.current;\r\n if (!d || e.pointerId !== d.pointerId) return;\r\n const dx = (e.clientX - d.x0) / d.sx;\r\n const dy = (e.clientY - d.y0) / d.sx;\r\n if (!d.started) {\r\n if (Math.hypot(dx, dy) < DRAG_THRESHOLD) return;\r\n d.started = true;\r\n setMenu(null);\r\n setSelectedLink(null);\r\n }\r\n d.dx = dx;\r\n d.clientX = e.clientX;\r\n d.clientY = e.clientY;\r\n if (!rafRef.current) rafRef.current = requestAnimationFrame(flush);\r\n };\r\n\r\n const onEditPointerUp = (e: ReactPointerEvent<HTMLElement>) => {\r\n const d = dragRef.current;\r\n if (!d || e.pointerId !== d.pointerId) return;\r\n if (rafRef.current) cancelAnimationFrame(rafRef.current);\r\n rafRef.current = 0;\r\n dragRef.current = null;\r\n if (!d.started) return; // a click: the row's onClick selects it\r\n suppressClick.current = true;\r\n d.dx = (e.clientX - d.x0) / d.sx;\r\n setPreview(null);\r\n setLinkDrag(null);\r\n select(d.id);\r\n const task = committed.find((t) => t.id === d.id);\r\n if (!task) return;\r\n if (d.zone === 'link') {\r\n const p = bodyPoint(e.clientX, e.clientY);\r\n const target = rowsRef.current[Math.floor(p.y / rowH)];\r\n if (target && target.task.id !== d.id) requestLink(d.id, target.task.id, 'drag');\r\n return;\r\n }\r\n if (d.zone === 'progress') {\r\n commitEdit(progressChange(task, dragProgress(d.row.progress, d.dx, d.barWidth), 'drag'));\r\n return;\r\n }\r\n const mode: SpanMode = d.zone === 'start' ? 'start' : d.zone === 'end' ? 'end' : 'move';\r\n const span = shiftSpan(mode, spanFor(d.row), pxToDays(d.dx, scale.dayWidth), workingWeek);\r\n commitEdit(spanChange(task, d.row.kind === 'milestone', span, 'drag'));\r\n };\r\n\r\n const editHandlers = (r: Row) => ({\r\n onPointerDown: (e: ReactPointerEvent<HTMLElement>) => onEditPointerDown(e, r),\r\n onPointerMove: onEditPointerMove,\r\n onPointerUp: onEditPointerUp,\r\n onPointerCancel: () => cancelDrag(false)\r\n });\r\n\r\n // Esc cancels a drag; closing popovers on outside clicks.\r\n useEffect(() => {\r\n if (!dragging) return;\r\n const onKey = (e: globalThis.KeyboardEvent) => {\r\n if (e.key === 'Escape') {\r\n e.preventDefault();\r\n cancelDrag();\r\n }\r\n };\r\n window.addEventListener('keydown', onKey, true);\r\n return () => window.removeEventListener('keydown', onKey, true);\r\n // eslint-disable-next-line react-hooks/exhaustive-deps\r\n }, [dragging]);\r\n\r\n useEffect(() => {\r\n if (!menu) return;\r\n const close = (e: globalThis.PointerEvent) => {\r\n if (!(e.target as HTMLElement).closest?.('.nf-g-menu')) setMenu(null);\r\n };\r\n document.addEventListener('pointerdown', close, true);\r\n return () => document.removeEventListener('pointerdown', close, true);\r\n }, [menu]);\r\n\r\n const menuRef = useRef<HTMLDivElement>(null);\r\n useEffect(() => {\r\n if (menu) menuRef.current?.querySelector<HTMLButtonElement>('button')?.focus();\r\n }, [menu]);\r\n const confirmRef = useRef<HTMLButtonElement>(null);\r\n useEffect(() => {\r\n if (confirm) confirmRef.current?.focus({ preventScroll: true });\r\n }, [confirm]);\r\n\r\n /* ---------- Adding ---------- */\r\n\r\n const suggestStart = (r?: Row) => fromDay(r && !Number.isNaN(r.end) ? r.end + 1 : todayDay);\r\n\r\n const requestAdd = (req: AddTaskRequest, words: string) => {\r\n if (!can('add')) return;\r\n setMenu(null);\r\n if (editing.locked) return editing.refuse();\r\n editing.announce(words);\r\n onAddTask?.(req);\r\n };\r\n const addBelow = (r: Row) =>\r\n requestAdd({ parentId: r.task.parentId ?? null, afterId: r.task.id, start: suggestStart(r) }, `Adding a task below '${r.task.name}'`);\r\n const addSubtask = (r: Row) =>\r\n requestAdd({ parentId: r.task.id, start: suggestStart(r) }, `Adding a subtask under '${r.task.name}'`);\r\n\r\n const openMenuAt = (id: string, clientX: number, clientY: number) => {\r\n const root = rootRef.current;\r\n if (!root) return;\r\n const p = localPoint(root, clientX, clientY);\r\n setMenu({ id, x: Math.min(p.x, root.offsetWidth - 200), y: Math.min(p.y, root.offsetHeight - 100) });\r\n };\r\n\r\n /* ---------- Keyboard ---------- */\r\n\r\n const onKeyDown = (e: KeyboardEvent<HTMLDivElement>) => {\r\n // Keys aimed at controls inside the grid (the replace confirm) are theirs, not the grid's.\r\n if (e.target !== e.currentTarget) return;\r\n if (rows.length === 0) return;\r\n const i = selectedIndex;\r\n const go = (n: number) => {\r\n const r = rows[Math.max(0, Math.min(rows.length - 1, n))]!;\r\n select(r.task.id);\r\n setSelectedLink(null);\r\n if (props.onHover) {\r\n requestAnimationFrame(() => {\r\n const at = anchorOf(r.task.id);\r\n if (at) hostHover(r.task, at);\r\n });\r\n }\r\n const el = scrollRef.current;\r\n if (el) {\r\n const top = r.index * rowH;\r\n if (top < el.scrollTop) el.scrollTop = top;\r\n else if (top + rowH + headerH > el.scrollTop + el.clientHeight) el.scrollTop = top + rowH + headerH - el.clientHeight;\r\n }\r\n };\r\n const row = i >= 0 ? rows[i] : undefined;\r\n const mod = e.ctrlKey || e.metaKey;\r\n\r\n // F2: rename the selected task in place.\r\n if (e.key === 'F2' && row) {\r\n startInline(row, 'name');\r\n e.preventDefault();\r\n return;\r\n }\r\n\r\n // Undo / redo\r\n if (mod && !e.altKey && (e.key === 'z' || e.key === 'Z' || e.key === 'y' || e.key === 'Y') && onTaskChange) {\r\n if (e.key === 'y' || e.key === 'Y' || e.shiftKey) editing.redo();\r\n else editing.undo();\r\n e.preventDefault();\r\n return;\r\n }\r\n\r\n // Move / resize / progress\r\n const ke = keyEdit(e);\r\n if (ke) {\r\n e.preventDefault();\r\n if (!row) return;\r\n const task = committed.find((t) => t.id === row.task.id);\r\n if (!task) return;\r\n if (ke.kind === 'progress') {\r\n if (can('progress', row)) commitEdit(progressChange(task, row.progress + ke.delta, 'keyboard'));\r\n return;\r\n }\r\n if (ke.mode === 'end' ? !can('resize', row) : !can('move', row)) return;\r\n commitEdit(spanChange(task, row.kind === 'milestone', shiftSpan(ke.mode, spanFor(row), ke.days, workingWeek), 'keyboard'));\r\n return;\r\n }\r\n\r\n // Keyboard linking: L on a task, then choose the successor and press Enter.\r\n if (linkMode) {\r\n if (e.key === 'Enter') {\r\n if (row) requestLink(linkMode, row.task.id, 'keyboard');\r\n setLinkMode(null);\r\n e.preventDefault();\r\n return;\r\n }\r\n if (e.key === 'Escape') {\r\n setLinkMode(null);\r\n editing.announce('Linking cancelled');\r\n e.preventDefault();\r\n return;\r\n }\r\n }\r\n if ((e.key === 'l' || e.key === 'L') && !mod && !e.altKey && row && can('link', row)) {\r\n setLinkMode(row.task.id);\r\n editing.announce(`Linking from '${row.task.name}'. Choose the task that follows it with the arrow keys, then press Enter. Escape cancels.`);\r\n e.preventDefault();\r\n return;\r\n }\r\n\r\n // Unlink: a selected connector, or Shift+Delete on a task.\r\n if (e.key === 'Delete' || e.key === 'Backspace') {\r\n if (selectedLink) {\r\n const [fromId, toId] = selectedLink.split('>') as [string, string];\r\n const to = committed.find((t) => t.id === toId);\r\n const toRow = rows.find((r) => r.task.id === toId);\r\n if (to && toRow && can('link', toRow)) commitEdit(unlinkChange(to, fromId, 'keyboard'));\r\n setSelectedLink(null);\r\n e.preventDefault();\r\n return;\r\n }\r\n if (e.key === 'Delete' && e.shiftKey && row && can('link', row) && row.task.dependsOn?.length) {\r\n const task = committed.find((t) => t.id === row.task.id);\r\n if (task) commitEdit(unlinkChange(task, undefined, 'keyboard'));\r\n e.preventDefault();\r\n return;\r\n }\r\n }\r\n\r\n // Add\r\n if (e.key === 'Insert' && can('add')) {\r\n if (row && mod) addSubtask(row);\r\n else if (row) addBelow(row);\r\n else requestAdd({ parentId: null, start: suggestStart() }, 'Adding a task');\r\n e.preventDefault();\r\n return;\r\n }\r\n if (isContextMenuKey(e) && row && props.onContextMenu) {\r\n const at = anchorOf(row.task.id);\r\n if (at) props.onContextMenu(row.task, at);\r\n e.preventDefault();\r\n return;\r\n }\r\n if (isContextMenuKey(e) && row && can('add')) {\r\n const el = document.getElementById(`${uid}-${row.task.id}`);\r\n const rect = el?.getBoundingClientRect();\r\n if (rect) openMenuAt(row.task.id, rect.left + 24, rect.bottom - 4);\r\n e.preventDefault();\r\n return;\r\n }\r\n\r\n switch (e.key) {\r\n case 'ArrowDown':\r\n go(i + 1);\r\n break;\r\n case 'ArrowUp':\r\n go(i < 0 ? 0 : i - 1);\r\n break;\r\n case 'Home':\r\n go(0);\r\n break;\r\n case 'End':\r\n go(rows.length - 1);\r\n break;\r\n case 'ArrowLeft':\r\n if (row?.hasChildren && row.collapsed === rtl) toggle(row.task.id, rtl);\r\n break;\r\n case 'ArrowRight':\r\n if (row?.hasChildren && row.collapsed !== rtl) toggle(row.task.id, !rtl);\r\n break;\r\n case 'Enter':\r\n if (row) onOpen?.(row.task);\r\n break;\r\n case 'Escape':\r\n if (selectedLink) setSelectedLink(null);\r\n else select(null);\r\n break;\r\n default:\r\n return;\r\n }\r\n e.preventDefault();\r\n };\r\n\r\n /* ---------- Print and export ---------- */\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: 'Gantt',\r\n version: VERSION,\r\n licence: license,\r\n rows: props.tasks.length,\r\n ...host\r\n }).then((r) => editing.toast({ text: r.message, tone: r.ok ? 'info' : 'error' }));\r\n setToolMenu(null);\r\n };\r\n const printChart = () => {\r\n setToolMenu(null);\r\n // Paper is white: print uses the light palette with the customer's accent.\r\n printDocument(\r\n printHtml({\r\n title,\r\n rows,\r\n theme: palette(props.theme, props.accent, true),\r\n today: todayDay,\r\n printedOn: todayLocal(),\r\n week: workingWeek,\r\n showBaseline\r\n }),\r\n rootRef.current?.ownerDocument ?? document\r\n );\r\n editing.announce('Print dialog opened');\r\n };\r\n\r\n const downloadPng = () => {\r\n setToolMenu(null);\r\n const ok = exportPng(\r\n { title, rows, scale, gridWidth, theme: palette(props.theme, props.accent, false), today: todayDay, showBaseline, broken: brokenLinks(rows) },\r\n pngFileName(title, fromDay(todayLocal())),\r\n rootRef.current?.ownerDocument ?? document\r\n );\r\n if (ok) editing.announce('Image downloaded');\r\n else editing.toast({ text: 'Too big for one image. Collapse phases or filter first, or use Print.', tone: 'info' });\r\n };\r\n\r\n /* ---------- Render ---------- */\r\n\r\n const gridCols = ['minmax(0,1fr)', ...cols.map((c) => `${colWidths[c]}px`)].join(' ');\r\n const canAdd = can('add');\r\n const bodyHeight = rows.length * rowH + (canAdd ? rowH : 0);\r\n\r\n // Rows in the visible window, plus any row that must stay mounted (selection for\r\n // aria-activedescendant, an open inline editor, an in-flight drag or link).\r\n const pinned = new Set([selectedId, inline?.id, preview?.id, linkDrag?.fromId].filter((x): x is string => !!x));\r\n const renderRows = rows.filter((r) => (r.index >= vis.first && r.index <= vis.last) || pinned.has(r.task.id));\r\n const linksEditable = !!onTaskChange && props.editable !== false && !(typeof props.editable === 'object' && props.editable.link === false);\r\n\r\n const tipBroken = tipRow\r\n ? (tipRow.task.dependsOn ?? []).filter((f) => broken.has(`${f}>${tipRow.task.id}`)).map(nameOf)\r\n : [];\r\n let tip: { row: Row; left: number; top: number } | null = null;\r\n if (tipRow) {\r\n const g = tipRow.kind === 'milestone' ? { left: scale.x(tipRow.start), width: scale.dayWidth } : barGeometry(tipRow.start, tipRow.end, scale);\r\n if (g) {\r\n // Keep the tooltip inside the visible part of the timeline: flip left/right and up/down near edges.\r\n const sc = scrollRef.current;\r\n const view = sc\r\n ? { left: sc.scrollLeft + gridWidth, right: sc.scrollLeft + sc.clientWidth, top: sc.scrollTop, bottom: sc.scrollTop + sc.clientHeight - headerH }\r\n : { left: gridWidth, right: gridWidth + scale.width, top: 0, bottom: Infinity };\r\n const slips = !Number.isNaN(tipRow.baselineEnd) && !Number.isNaN(tipRow.end) && tipRow.end !== tipRow.baselineEnd;\r\n const lines = 1 + (tipRow.task.owner ? 1 : 0) + (slips ? 1 : 0) + tipBroken.length + (tipRow.task.readOnly ? 1 : 0);\r\n // No tooltip for a mark scrolled out of view: it would point at nothing (QA-2026-10-31-3).\r\n const inView = gridWidth + g.left < view.right && gridWidth + g.left + g.width > view.left;\r\n if (inView) tip = { row: tipRow, ...placeTip({ left: gridWidth + g.left, width: g.width }, tipRow.index * rowH, view, tipW, (TIP_BASE_H + lines * TIP_LINE_H) * textScale, rowH) };\r\n }\r\n }\r\n // The tooltip and the replace-link confirm render outside the treegrid (it may only own rows:\r\n // axe aria-required-children, QA-2026-10-31-5), so body coordinates become the root's: add the\r\n // scroll area's offset and the body's place in it, minus the scroll.\r\n const toRoot = (p: { left: number; top: number }) => {\r\n const sc = scrollRef.current;\r\n const body = bodyRef.current;\r\n // Before mount (server render): no offsets to add yet.\r\n if (!sc || !body) return { left: p.left, top: p.top + headerH };\r\n return { left: sc.offsetLeft + p.left - sc.scrollLeft, top: sc.offsetTop + body.offsetTop + p.top - sc.scrollTop };\r\n };\r\n const tipAt = tip ? toRoot(tip) : null;\r\n const confirmAt = confirm ? toRoot(confirm) : null;\r\n tipShown.current = !!tipAt || !!confirmAt;\r\n\r\n // Live label above the dragged bar.\r\n let dragLabel: { text: string; left: number; top: number } | null = null;\r\n if (preview) {\r\n const r = rows.find((x) => x.task.id === preview.id);\r\n if (r) {\r\n const g = r.kind === 'milestone' ? { left: scale.x(r.start), width: scale.dayWidth } : barGeometry(r.start, r.end, scale);\r\n if (g) {\r\n const text = preview.patch.progress !== undefined ? `${r.progress}% done` : spanLabel(spanFor(r), r.kind === 'milestone', workingWeek);\r\n const above = r.index * rowH - 26;\r\n dragLabel = { text, left: gridWidth + g.left, top: above >= 2 ? above : r.index * rowH + rowH + 2 };\r\n }\r\n }\r\n }\r\n\r\n const linkFromRow = linkDrag ? rows.find((r) => r.task.id === linkDrag.fromId) : undefined;\r\n const linkStart = linkFromRow\r\n ? {\r\n x:\r\n linkFromRow.kind === 'milestone'\r\n ? scale.x(linkFromRow.start) + scale.dayWidth / 2 + 10\r\n : (barGeometry(linkFromRow.start, linkFromRow.end, scale)?.left ?? 0) +\r\n (barGeometry(linkFromRow.start, linkFromRow.end, scale)?.width ?? 0) +\r\n 6,\r\n y: linkFromRow.index * rowH + rowH / 2\r\n }\r\n : null;\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-static={props.animate === false ? '' : undefined}\r\n data-hl={hl ? '' : undefined}\r\n data-hc={props.colors?.foreground && props.colors?.background ? '' : undefined}\r\n className={`nf-gantt${dragging ? ' nf-g-dragging' : ''}${settled ? ' nf-g-settled' : ''}${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-g-toolbar\" dir={rtl ? 'rtl' : undefined}>\r\n <div className=\"nf-g-title\">{title}</div>\r\n <div className=\"nf-g-seg\" role=\"group\" aria-label=\"Zoom\">\r\n {ZOOMS.map((z) => (\r\n <button key={z} type=\"button\" className=\"nf-g-btn\" aria-pressed={z === zoom} onClick={() => setZoom(z)}>\r\n {ZOOM_LABEL[z]}\r\n </button>\r\n ))}\r\n </div>\r\n <button type=\"button\" className=\"nf-g-btn\" onClick={scrollToToday} disabled={!todayInView}>\r\n Today\r\n </button>\r\n <div className=\"nf-g-seg\" role=\"group\" aria-label=\"Phases\">\r\n <button type=\"button\" className=\"nf-g-btn\" onClick={expandAll} disabled={collapsed.size === 0 || filtering}>\r\n Expand all\r\n </button>\r\n <button type=\"button\" className=\"nf-g-btn\" onClick={collapseAll} disabled={filtering}>\r\n Collapse all\r\n </button>\r\n </div>\r\n <div className=\"nf-g-spacer\" />\r\n {showFilter ? (\r\n <input\r\n className=\"nf-g-filter\"\r\n type=\"search\"\r\n placeholder=\"Filter tasks\"\r\n aria-label=\"Filter tasks by name or owner\"\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 ) : null}\r\n <div className=\"nf-g-menuwrap\" data-menu=\"columns\">\r\n <button\r\n type=\"button\"\r\n className=\"nf-g-btn\"\r\n aria-haspopup=\"menu\"\r\n aria-expanded={colMenu}\r\n onClick={() => setToolMenu(colMenu ? null : 'columns')}\r\n >\r\n Columns\r\n </button>\r\n {colMenu ? (\r\n <div ref={toolMenuRef} className=\"nf-g-menu nf-g-colmenu\" role=\"menu\" aria-label=\"Columns\" onKeyDown={menuKeys}>\r\n {COLUMN_ORDER.map((c) => (\r\n <button\r\n key={c}\r\n type=\"button\"\r\n role=\"menuitemcheckbox\"\r\n aria-checked={cols.includes(c)}\r\n onClick={() => toggleColumn(c)}\r\n >\r\n <span className=\"nf-g-check\" aria-hidden=\"true\">\r\n {cols.includes(c) ? '✓' : ''}\r\n </span>\r\n {COLUMN_FULL_LABEL[c]}\r\n </button>\r\n ))}\r\n </div>\r\n ) : null}\r\n </div>\r\n {props.exports !== false ? (\r\n <div className=\"nf-g-menuwrap\" data-menu=\"export\">\r\n <button\r\n type=\"button\"\r\n className=\"nf-g-btn\"\r\n aria-haspopup=\"menu\"\r\n aria-expanded={toolMenu === 'export'}\r\n onClick={() => setToolMenu(toolMenu === 'export' ? null : 'export')}\r\n >\r\n Export\r\n </button>\r\n {toolMenu === 'export' ? (\r\n <div ref={toolMenuRef} className=\"nf-g-menu nf-g-colmenu\" role=\"menu\" aria-label=\"Export\" onKeyDown={menuKeys}>\r\n <button type=\"button\" role=\"menuitem\" onClick={printChart}>\r\n <span className=\"nf-g-check\" aria-hidden=\"true\" />\r\n Print…\r\n </button>\r\n <button type=\"button\" role=\"menuitem\" onClick={downloadPng}>\r\n <span className=\"nf-g-check\" aria-hidden=\"true\" />\r\n Download image (PNG)\r\n </button>\r\n {props.diagnostics !== false ? (\r\n <button type=\"button\" role=\"menuitem\" onClick={copySupport}>\r\n <span className=\"nf-g-check\" aria-hidden=\"true\" />\r\n Copy diagnostics\r\n </button>\r\n ) : null}\r\n </div>\r\n ) : null}\r\n </div>\r\n ) : null}\r\n <button\r\n type=\"button\"\r\n className=\"nf-g-switch\"\r\n aria-pressed={showBaseline}\r\n onClick={() => {\r\n if (props.showBaseline === undefined) setBaselineState(!showBaseline);\r\n }}\r\n >\r\n <span className=\"nf-g-switch-track\" aria-hidden=\"true\" />\r\n Baseline\r\n </button>\r\n </div>\r\n ) : null}\r\n\r\n <div\r\n ref={scrollRef}\r\n className=\"nf-g-scroll\"\r\n role=\"treegrid\"\r\n aria-label={title}\r\n aria-busy={loadingShown || undefined}\r\n aria-rowcount={rows.length + 1 + (canAdd ? 1 : 0)}\r\n aria-activedescendant={selectedId ? `${uid}-${selectedId}` : undefined}\r\n aria-describedby={onTaskChange ? `${uid}-help` : undefined}\r\n tabIndex={0}\r\n onKeyDown={onKeyDown}\r\n onScroll={onScroll}\r\n >\r\n {onTaskChange ? (\r\n <span id={`${uid}-help`} className=\"nf-g-sr\">\r\n Alt plus arrow keys move the selected task, Alt plus Shift plus arrows change its end, Alt plus up or down changes progress.\r\n L links tasks. Control plus Z undoes.{canAdd ? ' Insert adds a task, Control plus Insert adds a subtask.' : ''}\r\n </span>\r\n ) : null}\r\n <div style={{ width: gridWidth + scale.width }}>\r\n <div className=\"nf-g-head\" role=\"row\" aria-rowindex={1}>\r\n <div className=\"nf-g-cols\" role=\"columnheader\" dir={rtl ? 'rtl' : undefined} style={{ width: gridWidth, gridTemplateColumns: gridCols }}>\r\n <div>Task</div>\r\n {cols.map((c) => (\r\n <div key={c} className=\"nf-g-colhead\">\r\n {COLUMN_LABEL[c]}\r\n <span className=\"nf-g-colresize\" aria-hidden=\"true\" title={`Resize ${COLUMN_LABEL[c]}`} {...resizeHandlers(c)} />\r\n </div>\r\n ))}\r\n <div\r\n className=\"nf-g-divider\"\r\n role=\"separator\"\r\n aria-orientation=\"vertical\"\r\n aria-label=\"Resize the task list\"\r\n aria-valuenow={gridWidth}\r\n aria-valuemin={minGrid}\r\n aria-valuemax={MAX_GRID_WIDTH}\r\n tabIndex={0}\r\n title=\"Drag to resize the task list\"\r\n onKeyDown={(e) => {\r\n if (e.key === 'ArrowLeft' || e.key === 'ArrowRight') {\r\n setGridWidth(gridWidth + (e.key === 'ArrowRight' ? DIVIDER_STEP : -DIVIDER_STEP));\r\n e.preventDefault();\r\n }\r\n }}\r\n {...resizeHandlers('grid')}\r\n />\r\n </div>\r\n <div className=\"nf-g-axis\" aria-hidden=\"true\" style={{ width: scale.width, height: headerH }}>\r\n {scale.major.map((t) => (\r\n <div key={`M${t.day}`} className=\"nf-g-major\" style={{ left: t.x, width: t.width }}>\r\n {/* Sticks to the task list's edge, so a month that starts off screen still shows its name. */}\r\n <span className=\"nf-g-major-label\" style={{ left: gridWidth + 10 }}>\r\n {t.label}\r\n </span>\r\n </div>\r\n ))}\r\n {scale.minor.map((t, i) =>\r\n // Skip a week label the today tag would half-cover (unreadable, and an axe contrast failure).\r\n underTag.has(i) ? null : (\r\n <div key={`m${t.day}`} className=\"nf-g-minor\" style={{ left: t.x }}>\r\n {t.label}\r\n </div>\r\n )\r\n )}\r\n {todayInView ? (\r\n <div className=\"nf-g-today-tag\" style={{ left: todayX }}>\r\n {formatShort(todayDay)}\r\n </div>\r\n ) : null}\r\n </div>\r\n </div>\r\n\r\n {rows.length === 0 && filtering ? <div className=\"nf-g-empty\" style={emptyStyle}>No tasks match “{filter.trim()}”.</div> : null}\r\n {rows.length === 0 && !filtering && !props.loading && (!canAdd || props.emptyAction) ? (\r\n <div className=\"nf-g-empty\" style={emptyStyle}>\r\n No tasks to show yet.\r\n {props.emptyAction ? <EmptyActionView action={props.emptyAction} prefix=\"nf-g\" buttonClass=\"nf-g-btn\" /> : null}\r\n </div>\r\n ) : null}\r\n\r\n <div ref={bodyRef} className=\"nf-g-body\" style={{ height: bodyHeight }}>\r\n <div className=\"nf-g-layer\" aria-hidden=\"true\" style={{ left: gridWidth, width: scale.width, height: bodyHeight }}>\r\n {scale.weekends.map((w) => (\r\n <div key={`w${w.x}`} className=\"nf-g-weekend\" style={{ left: w.x, width: w.width }} />\r\n ))}\r\n {scale.minor.map((t) => (\r\n <div key={`v${t.day}`} className=\"nf-g-vline\" style={{ left: t.x }} />\r\n ))}\r\n {selectedIndex >= 0 ? <div className=\"nf-g-band\" style={{ top: selectedIndex * rowH }} /> : null}\r\n {todayInView ? <div className=\"nf-g-today\" style={{ left: todayX }} /> : null}\r\n </div>\r\n\r\n {renderRows.map((r) => {\r\n const t: GanttTask = r.task;\r\n const selected = t.id === selectedId;\r\n const g = barGeometry(r.start, r.end, scale);\r\n const base = showBaseline ? barGeometry(r.baselineStart, r.baselineEnd, scale) : null;\r\n const delay = { animationDelay: `${Math.min(r.index * 25, 400)}ms` };\r\n const movable = can('move', r);\r\n const roomy = !!g && g.width >= MIN_HANDLE_BAR;\r\n const resizable = roomy && can('resize', r);\r\n const progressable = roomy && can('progress', r);\r\n const linkable = can('link', r);\r\n const locked = !!onTaskChange && props.editable !== false && !!t.readOnly && r.kind !== 'phase';\r\n const isGhosted = preview?.id === t.id && preview.ghost;\r\n const isLinkTarget = linkDrag?.targetId === t.id || (linkMode !== null && selected && linkMode !== t.id);\r\n const editAttrs = movable || resizable || progressable || linkable ? editHandlers(r) : {};\r\n return (\r\n <div\r\n key={t.id}\r\n id={`${uid}-${t.id}`}\r\n className={`nf-g-row${isLinkTarget ? ' nf-g-linktarget' : ''}${linkMode === t.id ? ' nf-g-linksource' : ''}`}\r\n role=\"row\"\r\n style={{ top: r.index * rowH }}\r\n aria-rowindex={r.index + 2}\r\n aria-level={r.depth + 1}\r\n aria-selected={selected}\r\n aria-expanded={r.hasChildren ? !r.collapsed : undefined}\r\n aria-readonly={locked || undefined}\r\n data-kind={r.kind}\r\n data-dim={hl && !isHighlighted(t.id, hl) ? '' : undefined}\r\n onClick={(e) => {\r\n if (suppressClick.current) {\r\n suppressClick.current = false;\r\n return;\r\n }\r\n select(t.id, isMultiSelect(e));\r\n setSelectedLink(null);\r\n }}\r\n onDoubleClick={() => {\r\n cancelEditTimer();\r\n onOpen?.(t);\r\n }}\r\n onMouseDown={(e) => {\r\n // A double-click opens the task; don't let it select text in the grid.\r\n if (e.detail > 1) e.preventDefault();\r\n }}\r\n onContextMenu={(e) => {\r\n if (props.onContextMenu) {\r\n e.preventDefault();\r\n props.onContextMenu(t, { clientX: e.clientX, clientY: e.clientY, multi: isMultiSelect(e) });\r\n return;\r\n }\r\n if (!canAdd) return;\r\n e.preventDefault();\r\n select(t.id);\r\n openMenuAt(t.id, e.clientX, e.clientY);\r\n }}\r\n onMouseEnter={() => setHoverId(t.id)}\r\n onMouseMove={props.onHover ? (e) => hostHover(t, { clientX: e.clientX, clientY: e.clientY, multi: isMultiSelect(e) }) : undefined}\r\n onMouseLeave={(e) => {\r\n setHoverId((h) => (h === t.id ? null : h));\r\n hostHover(null, { clientX: e.clientX, clientY: e.clientY, multi: false });\r\n }}\r\n >\r\n <div className=\"nf-g-cell\" role=\"gridcell\" dir={rtl ? 'rtl' : undefined} style={{ width: gridWidth, gridTemplateColumns: gridCols }}>\r\n <div className=\"nf-g-name\" style={{ paddingInlineStart: r.depth * 18 }}>\r\n {r.hasChildren ? (\r\n <button\r\n type=\"button\"\r\n className=\"nf-g-chev\"\r\n tabIndex={-1}\r\n aria-expanded={!r.collapsed}\r\n aria-label={`${r.collapsed ? 'Expand' : 'Collapse'} ${t.name}`}\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n toggle(t.id);\r\n }}\r\n >\r\n <Chevron />\r\n </button>\r\n ) : r.kind === 'milestone' ? (\r\n <span className=\"nf-g-dot\" aria-hidden=\"true\" />\r\n ) : null}\r\n {inlineInput(r, 'name', 'Name') ?? (\r\n <span title={t.name} onClick={() => clickToEdit(r, 'name', selected)}>\r\n {t.name}\r\n </span>\r\n )}\r\n {canAdd ? (\r\n <button\r\n type=\"button\"\r\n className=\"nf-g-rowadd\"\r\n tabIndex={-1}\r\n aria-label={`Add a task near ${t.name}`}\r\n title=\"Add task\"\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n select(t.id);\r\n const rect = (e.currentTarget as HTMLElement).getBoundingClientRect();\r\n openMenuAt(t.id, rect.left, rect.bottom + 4);\r\n }}\r\n >\r\n +\r\n </button>\r\n ) : null}\r\n </div>\r\n {cols.map((c) => {\r\n if (c === 'owner') {\r\n return (\r\n <div key={c}>\r\n {t.owner ? (\r\n <span className=\"nf-g-avatar\" title={t.owner.name}>\r\n {t.owner.initials ?? initials(t.owner.name)}\r\n </span>\r\n ) : null}\r\n </div>\r\n );\r\n }\r\n if (c === 'progress') {\r\n return (\r\n <div key={c} className=\"nf-g-num\">\r\n {r.kind === 'milestone' ? (Number.isNaN(r.start) ? '' : fmtDay(r.start)) : fmtPct(r.progress, 'grid')}\r\n </div>\r\n );\r\n }\r\n if (c === 'duration') {\r\n return (\r\n <div key={c} className=\"nf-g-num\">\r\n {r.kind === 'milestone' || Number.isNaN(r.start) ? '' : workingWeek === 'all' ? r.end - r.start + 1 : workingDaysBetween(r.start, r.end, workingWeek)}\r\n </div>\r\n );\r\n }\r\n // Start / End: editable date cells.\r\n const day = c === 'start' ? r.start : r.end;\r\n const blank = Number.isNaN(day) || (c === 'end' && r.kind === 'milestone');\r\n return (\r\n <div\r\n key={c}\r\n className=\"nf-g-num nf-g-datecell\"\r\n data-editable={canInline(r, c) || undefined}\r\n onClick={() => clickToEdit(r, c, selected)}\r\n >\r\n {inlineInput(r, c, COLUMN_LABEL[c]) ?? (blank ? '' : fmtDay(day))}\r\n </div>\r\n );\r\n })}\r\n </div>\r\n <div className=\"nf-g-lane\" role=\"gridcell\" style={{ width: scale.width }}>\r\n {base && r.kind === 'task' ? <div className=\"nf-g-base\" style={{ left: base.left, width: base.width }} /> : null}\r\n {isGhosted ? <div className=\"nf-g-ghost\" style={{ left: preview!.ghost!.left, width: preview!.ghost!.width }} /> : null}\r\n {g && r.kind === 'phase' ? <div className=\"nf-g-phase\" style={{ left: g.left, width: g.width, ...delay }} /> : null}\r\n {g && r.kind === 'task' ? (\r\n <div\r\n className=\"nf-g-bar\"\r\n data-editable={movable || undefined}\r\n data-locked={locked || undefined}\r\n style={{ left: g.left, width: g.width, ...delay }}\r\n {...editAttrs}\r\n >\r\n <div className=\"nf-g-fill\" style={{ width: `${r.progress}%` }} />\r\n {resizable ? (\r\n <>\r\n <div className=\"nf-g-h nf-g-h-l\" data-drag=\"start\" />\r\n <div className=\"nf-g-h nf-g-h-r\" data-drag=\"end\" />\r\n </>\r\n ) : null}\r\n {progressable ? (\r\n <div className=\"nf-g-knob\" data-drag=\"progress\" style={{ left: `calc(${r.progress}% - 4px)` }} />\r\n ) : null}\r\n </div>\r\n ) : null}\r\n {r.kind === 'milestone' && !Number.isNaN(r.start) ? (\r\n <div\r\n className=\"nf-g-ms\"\r\n data-editable={movable || undefined}\r\n data-locked={locked || undefined}\r\n style={{ left: scale.x(r.start) + scale.dayWidth / 2, ...delay }}\r\n {...editAttrs}\r\n />\r\n ) : null}\r\n {linkable && (g || r.kind === 'milestone') && !Number.isNaN(r.start) ? (\r\n <div\r\n className=\"nf-g-linkdot\"\r\n data-drag=\"link\"\r\n title=\"Drag onto another task to link\"\r\n style={{\r\n left:\r\n r.kind === 'milestone' ? scale.x(r.start) + scale.dayWidth / 2 + 12 : (g?.left ?? 0) + (g?.width ?? 0) + 6\r\n }}\r\n {...editAttrs}\r\n />\r\n ) : null}\r\n </div>\r\n </div>\r\n );\r\n })}\r\n\r\n {canAdd ? (\r\n <div className=\"nf-g-addwrap\" role=\"row\" aria-rowindex={rows.length + 2} style={{ top: rows.length * rowH, width: gridWidth + scale.width }}>\r\n <div className=\"nf-g-addrow\" role=\"gridcell\" style={{ width: gridWidth }}>\r\n <button\r\n type=\"button\"\r\n className=\"nf-g-addbtn\"\r\n onClick={() => requestAdd({ parentId: null, start: suggestStart(rows[rows.length - 1]) }, 'Adding a task')}\r\n title={editing.locked && license ? lockedMessage('Plan') : undefined}\r\n >\r\n + Add task\r\n {editing.locked && license ? <KeyIcon /> : null}\r\n </button>\r\n </div>\r\n </div>\r\n ) : null}\r\n\r\n {/* Connectors sit above the bars so they can be selected; hit strokes avoid the bar handles. */}\r\n <svg\r\n className=\"nf-g-deps\"\r\n aria-hidden=\"true\"\r\n width={scale.width}\r\n height={bodyHeight}\r\n style={{ position: 'absolute', left: gridWidth, top: 0 }}\r\n >\r\n {visLinks.map((l) => {\r\n const cls = `${broken.has(l.key) ? 'is-broken' : ''}${selectedLink === l.key ? ' is-selected' : ''}`;\r\n return (\r\n <g key={l.key} className={cls || undefined}>\r\n <path d={l.d} />\r\n <path className=\"nf-g-arrow\" d={l.arrow} />\r\n {linksEditable ? (\r\n <path\r\n className=\"nf-g-hit\"\r\n d={l.hit}\r\n onClick={(e) => {\r\n e.stopPropagation();\r\n setSelectedLink(l.key);\r\n editing.announce(\r\n `Link from '${nameOf(l.from)}' to '${nameOf(l.to)}' selected. Press Delete to remove it.`\r\n );\r\n focusGrid();\r\n }}\r\n />\r\n ) : null}\r\n </g>\r\n );\r\n })}\r\n {linkDrag && linkStart ? (\r\n <path className=\"nf-g-linkline\" d={`M${linkStart.x} ${linkStart.y}L${linkDrag.x} ${linkDrag.y}`} />\r\n ) : null}\r\n </svg>\r\n\r\n {dragLabel ? (\r\n <div className=\"nf-g-draglabel\" style={{ left: dragLabel.left, top: dragLabel.top }}>\r\n {dragLabel.text}\r\n </div>\r\n ) : null}\r\n </div>\r\n </div>\r\n </div>\r\n\r\n {confirm && confirmAt ? (\r\n <div className=\"nf-g-confirm\" role=\"dialog\" dir={rtl ? 'rtl' : undefined} aria-label={confirm.text} style={{ left: confirmAt.left, top: confirmAt.top }}>\r\n <span>{confirm.text}</span>\r\n <button\r\n ref={confirmRef}\r\n type=\"button\"\r\n className=\"nf-g-btn nf-g-primary\"\r\n onClick={() => {\r\n commitEdit(confirm.change);\r\n setConfirm(null);\r\n focusGrid();\r\n }}\r\n >\r\n Replace\r\n </button>\r\n <button\r\n type=\"button\"\r\n className=\"nf-g-btn\"\r\n onKeyDown={(e) => e.key === 'Escape' && (setConfirm(null), focusGrid())}\r\n onClick={() => {\r\n setConfirm(null);\r\n focusGrid();\r\n }}\r\n >\r\n Cancel\r\n </button>\r\n </div>\r\n ) : null}\r\n\r\n {menu ? (\r\n <div\r\n ref={menuRef}\r\n className=\"nf-g-menu\"\r\n dir={rtl ? 'rtl' : undefined}\r\n role=\"menu\"\r\n style={{ left: menu.x, top: menu.y }}\r\n onKeyDown={(e) => {\r\n const items = Array.from(menuRef.current?.querySelectorAll<HTMLButtonElement>('button') ?? []);\r\n const at = items.indexOf(document.activeElement as HTMLButtonElement);\r\n if (e.key === 'Escape') {\r\n setMenu(null);\r\n focusGrid();\r\n } else if (e.key === 'ArrowDown') items[(at + 1) % items.length]?.focus();\r\n else if (e.key === 'ArrowUp') items[(at - 1 + items.length) % items.length]?.focus();\r\n else return;\r\n e.preventDefault();\r\n }}\r\n >\r\n {(() => {\r\n const r = rows.find((x) => x.task.id === menu.id);\r\n if (!r) return null;\r\n return (\r\n <>\r\n <button type=\"button\" role=\"menuitem\" onClick={() => (addBelow(r), focusGrid())}>\r\n Add task below <kbd>Insert</kbd>\r\n </button>\r\n <button type=\"button\" role=\"menuitem\" onClick={() => (addSubtask(r), focusGrid())}>\r\n Add subtask <kbd>Ctrl+Insert</kbd>\r\n </button>\r\n </>\r\n );\r\n })()}\r\n </div>\r\n ) : null}\r\n\r\n {tip && tipAt ? (\r\n <Tooltip row={tip.row} left={tipAt.left} top={tipAt.top} fmtDay={fmtDay} fmtPct={fmtPct} brokenFrom={tipBroken} locked={!!tip.row.task.readOnly && !!onTaskChange} week={workingWeek} rtl={rtl} />\r\n ) : null}\r\n {loadingShown ? (\r\n <div className=\"nf-g-empty nf-g-loading\" style={{ top: scrollTop0 + headerH }} role=\"status\" aria-busy=\"true\">\r\n Loading tasks…\r\n </div>\r\n ) : null}\r\n\r\n <div style={{ display: 'contents' }} dir={rtl ? 'rtl' : undefined}>\r\n <Toasts items={editing.toasts} announcement={editing.announcement} onDismiss={editing.dismiss} />\r\n </div>\r\n\r\n {license && !license.licensed && props.watermark !== false ? (\r\n <Watermark prefix=\"nf-g\" 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-g-locknote\" role=\"status\">\r\n {editing.lockNote}\r\n </p>\r\n ) : null}\r\n {renewal ? (\r\n <a className=\"nf-g-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","/** 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","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","/**\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","/**\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","/**\n * Editing logic for the Gantt — pure functions, no React, so node:test covers them.\n * Days are day numbers (see @ninthforge/core); tasks carry ISO `YYYY-MM-DD` strings.\n */\nimport { addWorkingDays, fromDay, MONTHS, parts, snapToWorkingDay, WEEKDAYS, weekday, workingDaysBetween, type WorkingWeek } from '@ninthforge/core';\nimport * as E from '@ninthforge/editing';\nimport { defaultEqual, sameStringSet, type ChangeSpec } from '@ninthforge/editing';\nimport type { Row } from './layout';\nimport type { EditAction, Editable, GanttChange, GanttTask } from './types';\n\n/* ---------- Permissions ---------- */\n\n/**\n * Whether an action is allowed. Editing needs a handler (`onTaskChange`, or `onAddTask`\n * for add), must not be switched off by `editable`, and bars must not be read-only.\n * Phases are derived from their children, so they can't be moved, resized or linked.\n */\nexport function canEdit(\n editable: Editable | undefined,\n hasHandler: boolean,\n action: EditAction,\n row?: Pick<Row, 'kind'> & { task: Pick<GanttTask, 'readOnly'> }\n): boolean {\n if (!hasHandler || editable === false) return false;\n if (typeof editable === 'object' && editable[action] === false) return false;\n if (!row || action === 'add') return true;\n if (row.task.readOnly) return false;\n // Phases and milestones can be renamed; phase dates are derived from their children.\n if (action === 'rename') return true;\n if (row.kind === 'phase') return false;\n if (row.kind === 'milestone' && (action === 'resize' || action === 'progress')) return false;\n return true;\n}\n\n/* ---------- Drag and keyboard maths ---------- */\n\nexport type SpanMode = 'move' | 'start' | 'end';\n\nexport interface Span {\n start: number;\n end: number;\n}\n\n/** Pixels → whole days (snaps to the nearest day). */\nexport function pxToDays(dx: number, dayWidth: number): number {\n return Math.round(dx / dayWidth) || 0; // avoid -0\n}\n\n/**\n * Shift a span by whole days. End never goes before start (minimum length one day).\n * With a Mon–Fri week, an edge that lands on a weekend snaps to the nearest working day in the\n * direction of travel, and a move keeps the task's working-day length.\n */\nexport function shiftSpan(mode: SpanMode, span: Span, days: number, week: WorkingWeek = 'all'): Span {\n if (week === 'all' || days === 0) {\n if (mode === 'move') return { start: span.start + days, end: span.end + days };\n if (mode === 'start') return { start: Math.min(span.start + days, span.end), end: span.end };\n return { start: span.start, end: Math.max(span.end + days, span.start) };\n }\n const snap = (d: number) => snapToWorkingDay(d, days, week);\n if (mode === 'move') {\n const start = snap(span.start + days);\n const len = workingDaysBetween(span.start, span.end, week);\n // A span with no working days (all weekend) just moves by calendar days.\n const end = len > 0 ? addWorkingDays(start, len - 1, week) : start + (span.end - span.start);\n return { start, end };\n }\n if (mode === 'start') return { start: Math.min(snap(span.start + days), span.end), end: span.end };\n return { start: span.start, end: Math.max(snap(span.end + days), span.start) };\n}\n\n/** \"12 days\", or \"10 working days\" with a Mon–Fri week. */\nexport function durationLabel(start: number, end: number, week: WorkingWeek = 'all'): string {\n const n = week === 'all' ? end - start + 1 : workingDaysBetween(start, end, week);\n const unit = week === 'all' ? 'day' : 'working day';\n return `${n} ${unit}${n === 1 ? '' : 's'}`;\n}\n\n/** Progress after dragging the knob `dx` pixels along a bar `barWidth` pixels wide. */\nexport function dragProgress(progress: number, dx: number, barWidth: number): number {\n if (barWidth <= 0) return clampProgress(progress);\n return clampProgress(Math.round(progress + (dx / barWidth) * 100));\n}\n\nexport function clampProgress(p: number): number {\n return Math.max(0, Math.min(100, Math.round(p)));\n}\n\n/* ---------- Keyboard ---------- */\n\nexport type KeyEdit = { kind: 'span'; mode: SpanMode; days: number } | { kind: 'progress'; delta: number };\n\n/**\n * The one place edit shortcuts are defined, so the scheme can change without hunting.\n * Spec v0.2: Alt+←/→ move, Alt+Shift+←/→ end, Alt+↑/↓ progress ±10%.\n */\nexport function keyEdit(e: { key: string; altKey: boolean; shiftKey: boolean; ctrlKey: boolean; metaKey: boolean }): KeyEdit | null {\n if (!e.altKey || e.ctrlKey || e.metaKey) return null;\n switch (e.key) {\n case 'ArrowLeft':\n return { kind: 'span', mode: e.shiftKey ? 'end' : 'move', days: -1 };\n case 'ArrowRight':\n return { kind: 'span', mode: e.shiftKey ? 'end' : 'move', days: 1 };\n case 'ArrowUp':\n return e.shiftKey ? null : { kind: 'progress', delta: 10 };\n case 'ArrowDown':\n return e.shiftKey ? null : { kind: 'progress', delta: -10 };\n default:\n return null;\n }\n}\n\n/* ---------- Building changes ---------- */\n\n/*\n * The generic machinery (fields, overrides, undo/redo) lives in @ninthforge/editing and is\n * shared with the Scheduler. These wrappers keep the Gantt's own names and signatures and\n * supply its rules: which fields are editable, that dependsOn compares as a set, and what\n * \"no value\" means when undoing.\n */\nexport type GanttFields = Pick<GanttTask, 'name' | 'start' | 'end' | 'date' | 'progress' | 'parentId' | 'dependsOn'>;\ntype Fields = Pick<GanttChange, 'name' | 'start' | 'end' | 'date' | 'progress' | 'parentId' | 'dependsOn'>;\n\nexport const GANTT_FIELDS: ChangeSpec<GanttFields> = {\n keys: ['name', 'start', 'end', 'date', 'progress', 'parentId', 'dependsOn'],\n equal: (key, a, b) => (key === 'dependsOn' ? sameStringSet(a, b) : defaultEqual(key, a, b)),\n blank: (key) => (key === 'dependsOn' ? [] : key === 'parentId' ? null : undefined)\n};\n\n/** The forward values of a change (everything except id/previous/source). */\nexport function fieldsOf(change: GanttChange): Partial<GanttTask> {\n return E.fieldsOf(change, GANTT_FIELDS);\n}\n\nfunction previousOf(task: GanttTask, next: Fields): Partial<GanttTask> {\n return E.previousOf(task, next, GANTT_FIELDS);\n}\n\n/** A change that moves/resizes a task, or moves a milestone (writes `date` if the task uses it, else `start`). */\nexport function spanChange(task: GanttTask, isMilestone: boolean, span: Span, source: GanttChange['source']): GanttChange {\n let next: Fields;\n if (isMilestone) next = task.date !== undefined ? { date: fromDay(span.start) } : { start: fromDay(span.start) };\n else next = { start: fromDay(span.start), end: fromDay(span.end) };\n return { id: task.id, ...next, previous: previousOf(task, next), source };\n}\n\nexport function progressChange(task: GanttTask, progress: number, source: GanttChange['source']): GanttChange {\n const next = { progress: clampProgress(progress) };\n return { id: task.id, ...next, previous: previousOf(task, next), source };\n}\n\n/** A rename, or null when the new name is blank or unchanged. */\nexport function renameChange(task: GanttTask, name: string, source: GanttChange['source']): GanttChange | null {\n const next = name.trim();\n if (!next || next === task.name) return null;\n return { id: task.id, name: next, previous: { name: task.name }, source };\n}\n\n/**\n * A change from typing a date into the Start or End cell. End never ends up before start:\n * a start after the end pulls the end along, and an end before the start is clamped to it.\n */\nexport function dateCellChange(\n task: GanttTask,\n isMilestone: boolean,\n span: Span,\n field: 'start' | 'end',\n day: number,\n source: GanttChange['source']\n): GanttChange {\n if (isMilestone) return spanChange(task, true, { start: day, end: day }, source);\n const next: Span = field === 'start' ? { start: day, end: Math.max(span.end, day) } : { start: span.start, end: Math.max(day, span.start) };\n return spanChange(task, false, next, source);\n}\n\n/** True when the change would not alter the task (e.g. a drag that snapped back to where it started). */\nexport function isNoop(task: GanttTask, change: GanttChange): boolean {\n return E.isNoop(task, change, GANTT_FIELDS);\n}\n\n/* ---------- Optimistic overrides ---------- */\n\nexport type Override = E.Override<GanttFields>;\nexport type Overrides = E.Overrides<GanttFields>;\n\n/** Tasks as the user sees them: host data with pending edits laid over it. */\nexport function applyOverrides(tasks: GanttTask[], overrides: Overrides): GanttTask[] {\n return E.applyOverrides(tasks, overrides);\n}\n\n/** Drop overrides that the host data now matches (the save landed and the data refreshed). */\nexport function reconcileOverrides(tasks: GanttTask[], overrides: Overrides): Map<string, Override> {\n return E.reconcileOverrides(tasks, overrides, GANTT_FIELDS);\n}\n\n/* ---------- Undo / redo ---------- */\n\nexport const HISTORY_LIMIT = E.HISTORY_LIMIT;\n\nexport type History = E.History<GanttChange>;\n\nexport const emptyHistory: History = E.emptyHistory<GanttChange>();\n\n/** A new edit: push it, cap at 20, and clear redo. */\nexport function pushHistory(h: History, change: GanttChange): History {\n return E.pushHistory(h, change);\n}\n\n/** Remove a change from both stacks (its save was rejected). */\nexport function dropFromHistory(h: History, change: GanttChange): History {\n return E.dropFromHistory(h, change);\n}\n\n/** The change that reverses `change`, ready to commit like any other edit. */\nexport function invertChange(change: GanttChange, source: 'undo' | 'redo'): GanttChange {\n return E.invertChange<GanttFields, GanttChange['source']>(change, source, GANTT_FIELDS) as GanttChange;\n}\n\n/** Undo: the change to commit, and the history with the entry moved to redo. */\nexport function undoStep(h: History): { commit: GanttChange; entry: GanttChange; history: History } | null {\n return E.undoStep<GanttFields, GanttChange['source']>(h, 'undo', GANTT_FIELDS) as { commit: GanttChange; entry: GanttChange; history: History } | null;\n}\n\n/** Redo: re-apply the most recently undone change. */\nexport function redoStep(h: History): { commit: GanttChange; entry: GanttChange; history: History } | null {\n return E.redoStep<GanttFields, GanttChange['source']>(h, 'redo') as { commit: GanttChange; entry: GanttChange; history: History } | null;\n}\n\n/* ---------- Dependencies ---------- */\n\n/**\n * Finish-to-start links whose successor starts too early, keyed `from>to`.\n * After a task, the successor must start the next day or later. A milestone is a point\n * in time, so its successor may start on the milestone's date.\n */\nexport function brokenLinks(rows: Row[]): Set<string> {\n const byId = new Map(rows.map((r) => [r.task.id, r] as const));\n const out = new Set<string>();\n for (const to of rows) {\n for (const fromId of to.task.dependsOn ?? []) {\n const from = byId.get(fromId);\n if (!from || Number.isNaN(from.end) || Number.isNaN(to.start)) continue;\n const broken = from.kind === 'milestone' ? to.start < from.end : to.start <= from.end;\n if (broken) out.add(`${fromId}>${to.task.id}`);\n }\n }\n return out;\n}\n\n/** Broken links among an already-built set (e.g. the on-screen ones); same rule as `brokenLinks`. */\nexport function brokenAmong(links: readonly { key: string; fromIndex: number; toIndex: number }[], rows: Row[]): Set<string> {\n const out = new Set<string>();\n for (const l of links) {\n const from = rows[l.fromIndex];\n const to = rows[l.toIndex];\n if (!from || !to || Number.isNaN(from.end) || Number.isNaN(to.start)) continue;\n const broken = from.kind === 'milestone' ? to.start < from.end : to.start <= from.end;\n if (broken) out.add(l.key);\n }\n return out;\n}\n\nexport type LinkCheck = 'ok' | 'self' | 'cycle' | 'exists';\n\n/** Can `toId` be made to depend on `fromId`? Refuses self-links and cycles. */\nexport function validateLink(tasks: GanttTask[], fromId: string, toId: string): LinkCheck {\n if (fromId === toId) return 'self';\n const byId = new Map(tasks.map((t) => [t.id, t] as const));\n if (byId.get(toId)?.dependsOn?.includes(fromId)) return 'exists';\n // A cycle exists if `from` already depends (transitively) on `to`.\n const seen = new Set<string>();\n const stack = [fromId];\n while (stack.length) {\n const id = stack.pop()!;\n if (id === toId) return 'cycle';\n if (seen.has(id)) continue;\n seen.add(id);\n for (const p of byId.get(id)?.dependsOn ?? []) stack.push(p);\n }\n return 'ok';\n}\n\n/**\n * The change that links `from` → `to`. With `single`, it replaces any existing predecessor\n * and reports which one, so the UI can confirm first.\n */\nexport function linkChange(\n to: GanttTask,\n fromId: string,\n single: boolean,\n source: GanttChange['source']\n): { change: GanttChange; replaces: string | null } {\n const existing = to.dependsOn ?? [];\n const dependsOn = single ? [fromId] : [...existing, fromId];\n const replaces = single && existing.length > 0 && existing[0] !== fromId ? existing[0]! : null;\n return { change: { id: to.id, dependsOn, previous: { dependsOn: existing }, source }, replaces };\n}\n\n/** Remove one predecessor (or all, when `fromId` is omitted). */\nexport function unlinkChange(to: GanttTask, fromId: string | undefined, source: GanttChange['source']): GanttChange {\n const existing = to.dependsOn ?? [];\n const dependsOn = fromId === undefined ? [] : existing.filter((d) => d !== fromId);\n return { id: to.id, dependsOn, previous: { dependsOn: existing }, source };\n}\n\n/* ---------- Words ---------- */\n\n/** \"Mon 5 Oct\" */\nexport function dayLabel(day: number): string {\n const p = parts(day);\n return `${WEEKDAYS[weekday(day)]} ${p.date} ${MONTHS[p.month]}`;\n}\n\n/** Live drag label: \"Mon 5 Oct → Fri 16 Oct · 12 days\" (or one date for milestones). */\nexport function spanLabel(span: Span, isMilestone: boolean, week: WorkingWeek = 'all'): string {\n if (isMilestone) return dayLabel(span.start);\n return `${dayLabel(span.start)} → ${dayLabel(span.end)} · ${durationLabel(span.start, span.end, week)}`;\n}\n\n/** Screen-reader announcement for a committed change. */\nexport function describeChange(change: GanttChange, name: (id: string) => string): string {\n const who = `'${name(change.id)}'`;\n const prefix = change.source === 'undo' ? 'Undone: ' : change.source === 'redo' ? 'Redone: ' : '';\n const d = (iso?: string) => (iso ? dayLabel(Math.round(Date.parse(iso) / 86_400_000)) : '');\n let text: string;\n if (change.name !== undefined) {\n const before = change.previous.name;\n text = before ? `'${before}' renamed to '${change.name}'` : `Renamed to '${change.name}'`;\n } else if (change.dependsOn !== undefined) {\n text = change.dependsOn.length\n ? `${who} now follows ${change.dependsOn.map((id) => `'${name(id)}'`).join(', ')}`\n : `${who} no longer depends on another task`;\n } else if (change.progress !== undefined) {\n text = `${who} is ${change.progress}% done`;\n } else if (change.date !== undefined || (change.start !== undefined && change.end === undefined)) {\n text = `${who} moved to ${d(change.date ?? change.start)}`;\n } else {\n text = `${who} now runs ${d(change.start)} to ${d(change.end)}`;\n }\n return prefix + text;\n}\n","import { toDay, type TimeScale } from '@ninthforge/core';\r\nimport type { GanttTask, GanttTaskKind } from './types';\r\n\r\nexport const ROW_H = 40;\r\nexport const HEADER_H = 52;\r\n\r\nexport interface Row {\r\n task: GanttTask;\r\n kind: GanttTaskKind;\r\n depth: number;\r\n index: number;\r\n hasChildren: boolean;\r\n collapsed: boolean;\r\n /** Day numbers; NaN when the task has no usable dates. */\r\n start: number;\r\n end: number;\r\n baselineStart: number;\r\n baselineEnd: number;\r\n /** 0–100; phases roll up their children weighted by duration. */\r\n progress: number;\r\n}\r\n\r\nfunction kindOf(t: GanttTask, hasChildren: boolean): GanttTaskKind {\r\n if (t.kind) return t.kind;\r\n if (hasChildren) return 'phase';\r\n if (t.date || (t.start && !t.end)) return 'milestone';\r\n return 'task';\r\n}\r\n\r\n/** Flatten the task tree into visible rows, deriving phase spans from their children. */\r\nexport function buildRows(tasks: GanttTask[], collapsed: ReadonlySet<string> = new Set()): Row[] {\r\n const byId = new Map(tasks.map((t) => [t.id, t] as const));\r\n const children = new Map<string, GanttTask[]>();\r\n const roots: GanttTask[] = [];\r\n // A task whose parent chain loops back to itself (A → B → A) would never reach a root and\r\n // silently disappear: show it at the top level instead (QA question 2026-10-31).\r\n const inCycle = (id: string): boolean => {\r\n const seen = new Set<string>();\r\n for (let cur = byId.get(id)?.parentId; cur && byId.has(cur); cur = byId.get(cur)!.parentId) {\r\n if (cur === id) return true;\r\n if (seen.has(cur)) return false; // a loop above this task, not through it\r\n seen.add(cur);\r\n }\r\n return false;\r\n };\r\n for (const t of tasks) {\r\n const p = t.parentId && byId.has(t.parentId) && t.parentId !== t.id && !inCycle(t.id) ? t.parentId : null;\r\n if (p) {\r\n const list = children.get(p) ?? [];\r\n list.push(t);\r\n children.set(p, list);\r\n } else {\r\n roots.push(t);\r\n }\r\n }\r\n\r\n // Own span of each task, then roll phase spans up from descendants.\r\n const spanCache = new Map<string, [number, number]>();\r\n const visiting = new Set<string>();\r\n const span = (t: GanttTask): [number, number] => {\r\n const cached = spanCache.get(t.id);\r\n if (cached) return cached;\r\n if (visiting.has(t.id)) return [NaN, NaN]; // cycle guard\r\n visiting.add(t.id);\r\n let s = toDay(t.date ?? t.start);\r\n let e = toDay(t.date ?? t.end ?? t.start);\r\n for (const c of children.get(t.id) ?? []) {\r\n const [cs, ce] = span(c);\r\n if (!Number.isNaN(cs)) s = Number.isNaN(s) ? cs : Math.min(s, cs);\r\n if (!Number.isNaN(ce)) e = Number.isNaN(e) ? ce : Math.max(e, ce);\r\n }\r\n visiting.delete(t.id);\r\n const result: [number, number] = [s, e];\r\n spanCache.set(t.id, result);\r\n return result;\r\n };\r\n\r\n // Progress roll-up: [weighted progress sum, total days].\r\n const progCache = new Map<string, [number, number]>();\r\n const prog = (t: GanttTask, guard: Set<string>): [number, number] => {\r\n const cached = progCache.get(t.id);\r\n if (cached) return cached;\r\n if (guard.has(t.id)) return [0, 0];\r\n guard.add(t.id);\r\n const kids = children.get(t.id) ?? [];\r\n let result: [number, number];\r\n if (kids.length === 0) {\r\n const [s, e] = span(t);\r\n const days = Number.isNaN(s) || Number.isNaN(e) || t.date ? 0 : e - s + 1;\r\n result = [pct(t.progress) * days, days];\r\n } else {\r\n result = kids.reduce<[number, number]>(\r\n (acc, c) => {\r\n const [p, d] = prog(c, guard);\r\n return [acc[0] + p, acc[1] + d];\r\n },\r\n [0, 0]\r\n );\r\n }\r\n guard.delete(t.id);\r\n progCache.set(t.id, result);\r\n return result;\r\n };\r\n const progressOf = (t: GanttTask): number => {\r\n if (!children.has(t.id)) return pct(t.progress);\r\n const [p, d] = prog(t, new Set());\r\n return d > 0 ? Math.round(p / d) : 0;\r\n };\r\n\r\n const rows: Row[] = [];\r\n const walk =(list: GanttTask[], depth: number, seen: Set<string>) => {\r\n for (const t of list) {\r\n if (seen.has(t.id)) continue;\r\n seen.add(t.id);\r\n const kids = children.get(t.id) ?? [];\r\n const [s, e] = span(t);\r\n const isCollapsed = collapsed.has(t.id);\r\n rows.push({\r\n task: t,\r\n kind: kindOf(t, kids.length > 0),\r\n depth,\r\n index: rows.length,\r\n hasChildren: kids.length > 0,\r\n collapsed: isCollapsed,\r\n start: s,\r\n end: e,\r\n baselineStart: toDay(t.baselineStart),\r\n baselineEnd: toDay(t.baselineEnd),\r\n progress: progressOf(t)\r\n });\r\n if (!isCollapsed) walk(kids, depth + 1, seen);\r\n }\r\n };\r\n walk(roots, 0, new Set());\r\n return rows;\r\n}\r\n\r\n/* ---------- Virtualisation ---------- */\r\n\r\n/** Rows rendered above and below the viewport, so fast scrolling never shows blanks. */\r\nexport const OVERSCAN = 8;\r\n\r\n/**\r\n * Inclusive range of row indexes to render for a scroll position. `scrollTop` is the scroll\r\n * container's, which includes the sticky header above the rows.\r\n */\r\nexport function visibleRange(\r\n scrollTop: number,\r\n viewportHeight: number,\r\n rowCount: number,\r\n rowH = ROW_H,\r\n headerH = HEADER_H,\r\n overscan = OVERSCAN\r\n): { first: number; last: number } {\r\n if (rowCount <= 0) return { first: 0, last: -1 };\r\n const top = Math.max(0, scrollTop - headerH);\r\n const last = Math.min(rowCount - 1, Math.ceil((top + Math.max(viewportHeight, rowH)) / rowH) + overscan);\r\n const first = Math.min(last, Math.max(0, Math.floor(top / rowH) - overscan));\r\n return { first, last };\r\n}\r\n\r\n/** Links whose vertical span crosses the rendered range (a link can pass through it). */\r\nexport function cullLinks(links: DependencyLink[], first: number, last: number): DependencyLink[] {\r\n return links.filter((l) => Math.min(l.fromIndex, l.toIndex) <= last && Math.max(l.fromIndex, l.toIndex) >= first);\r\n}\r\n\r\n/** Every row id that has children, for \"Collapse all\". */\r\nexport function parentIds(rows: Row[]): Set<string> {\r\n return new Set(rows.filter((r) => r.hasChildren).map((r) => r.task.id));\r\n}\r\n\r\n/* ---------- Grid columns ---------- */\r\n\r\nexport const COLUMN_ORDER = ['owner', 'progress', 'start', 'end', 'duration'] as const;\r\nexport type ColumnKey = (typeof COLUMN_ORDER)[number];\r\n\r\nexport const COLUMN_LABEL: Record<ColumnKey, string> = {\r\n owner: 'Owner',\r\n progress: 'Done',\r\n start: 'Start',\r\n end: 'End',\r\n duration: 'Days'\r\n};\r\n\r\n/** Default widths in px; the name column takes what's left of the grid. */\r\nexport const COLUMN_WIDTH: Record<ColumnKey, number> = { owner: 56, progress: 56, start: 92, end: 92, duration: 56 };\r\nexport const MIN_COLUMN_WIDTH = 40;\r\n\r\n/** Default column widths at a text scale: text columns grow with the text so values aren't clipped. */\r\nexport function scaleColumns(widths: Record<ColumnKey, number>, textScale = 1): Record<ColumnKey, number> {\r\n const out = { ...widths };\r\n if (textScale === 1) return out;\r\n (Object.keys(out) as ColumnKey[]).forEach((k) => {\r\n out[k] = Math.max(MIN_COLUMN_WIDTH, Math.round(out[k] * textScale));\r\n });\r\n return out;\r\n}\r\nexport const MIN_NAME_WIDTH = 120;\r\n\r\n/** Visible columns in display order. Owner and Done default on; the rest default off. */\r\nexport function visibleColumns(columns: Partial<Record<ColumnKey, boolean>> = {}): ColumnKey[] {\r\n const defaults: Record<ColumnKey, boolean> = { owner: true, progress: true, start: false, end: false, duration: false };\r\n return COLUMN_ORDER.filter((c) => columns[c] ?? defaults[c]);\r\n}\r\n\r\n/* ---------- Filter ---------- */\r\n\r\n/**\r\n * Rows matching `query` (name or owner, case-insensitive), plus every ancestor of a match so\r\n * matches keep their phase context. Indexes are renumbered for layout. Empty query → all rows.\r\n */\r\n/** Does a row match the filter box (task name or owner, ignoring case)? */\r\nexport function rowMatches(r: Row, query: string): boolean {\r\n const q = query.trim().toLowerCase();\r\n return !!q && (r.task.name.toLowerCase().includes(q) || (r.task.owner?.name.toLowerCase().includes(q) ?? false));\r\n}\r\n\r\nexport function filterRows(rows: Row[], query: string): Row[] {\r\n const q = query.trim().toLowerCase();\r\n if (!q) return rows;\r\n const byId = new Map(rows.map((r) => [r.task.id, r] as const));\r\n const keep = new Set<string>();\r\n for (const r of rows) {\r\n if (!rowMatches(r, q)) continue;\r\n let cur: Row | undefined = r;\r\n const guard = new Set<string>();\r\n while (cur && !guard.has(cur.task.id)) {\r\n guard.add(cur.task.id);\r\n keep.add(cur.task.id);\r\n cur = cur.task.parentId ? byId.get(cur.task.parentId) : undefined;\r\n }\r\n }\r\n return rows.filter((r) => keep.has(r.task.id)).map((r, index) => ({ ...r, index }));\r\n}\r\n\r\n/** First and last day that any row (or baseline) touches. */\r\nexport function rowsRange(rows: Row[]): { from: number; to: number } | null {\r\n const days: number[] = [];\r\n for (const r of rows) {\r\n for (const d of [r.start, r.end, r.baselineStart, r.baselineEnd]) {\r\n if (Number.isFinite(d)) days.push(d);\r\n }\r\n }\r\n if (!days.length) return null;\r\n days.sort((a, b) => a - b);\r\n const from = days[0]!;\r\n const to = days[days.length - 1]!;\r\n if (to - from <= MAX_SPAN_DAYS) return { from, to };\r\n // One stray date (a typo'd year, Dataverse's 9999-12-31) mustn't stretch the axis over\r\n // centuries: keep the window around the middle of the plan. Tasks outside sit off-scale.\r\n const mid = days[Math.floor(days.length / 2)]!;\r\n const lo = Math.max(from, Math.min(mid - MAX_SPAN_DAYS / 2, to - MAX_SPAN_DAYS));\r\n return { from: lo, to: lo + MAX_SPAN_DAYS };\r\n}\r\n\r\n/** The longest axis the chart draws: 15 years. */\r\nexport const MAX_SPAN_DAYS = Math.round(15 * 365.25);\r\n\r\n/** Progress as 0–100 whatever the host sends (null, text, NaN, 1e9). */\r\nexport function pct(v: unknown): number {\r\n const n = typeof v === 'number' ? v : Number(v);\r\n return Number.isFinite(n) ? Math.max(0, Math.min(100, n)) : 0;\r\n}\r\n\r\nexport interface BarGeometry {\r\n left: number;\r\n width: number;\r\n}\r\n\r\n/** Inclusive end: a task from day 4 to day 15 covers 12 day-widths. */\r\nexport function barGeometry(start: number, end: number, scale: TimeScale): BarGeometry | null {\r\n if (Number.isNaN(start) || Number.isNaN(end)) return null;\r\n const left = scale.x(start);\r\n return { left, width: Math.max(scale.x(end + 1) - left, scale.dayWidth) };\r\n}\r\n\r\nexport interface DependencyLink {\r\n /** `from>to` */\r\n key: string;\r\n from: string;\r\n to: string;\r\n /** Row indexes of the two ends (for culling to the visible range). */\r\n fromIndex: number;\r\n toIndex: number;\r\n /** Connector line. */\r\n d: string;\r\n /** Arrowhead. */\r\n arrow: string;\r\n /** Same route minus both ends, for a wide invisible hit stroke that never covers bar handles. */\r\n hit: string;\r\n}\r\n\r\n/**\r\n * Finish-to-start connectors, one per link. With `window` (row indexes), only links whose\r\n * vertical span crosses it are built — big plans then pay for what's on screen, not 5,000 paths.\r\n */\r\nexport function dependencyLinks(rows: Row[], scale: TimeScale, rowH = ROW_H, window?: { first: number; last: number }): DependencyLink[] {\r\n const index = new Map(rows.map((r) => [r.task.id, r] as const));\r\n const out: DependencyLink[] = [];\r\n const r1 = (n: number) => Math.round(n * 10) / 10;\r\n for (const to of rows) {\r\n for (const fromId of to.task.dependsOn ?? []) {\r\n const from = index.get(fromId);\r\n if (!from || Number.isNaN(from.end) || Number.isNaN(to.start)) continue;\r\n if (window && (Math.min(from.index, to.index) > window.last || Math.max(from.index, to.index) < window.first)) continue;\r\n const fromMs = from.kind === 'milestone';\r\n const toMs = to.kind === 'milestone';\r\n const x1 = r1(fromMs ? scale.x(from.end) + scale.dayWidth / 2 + 7 : scale.x(from.end + 1));\r\n const x2 = r1(toMs ? scale.x(to.start) + scale.dayWidth / 2 - 8 : scale.x(to.start));\r\n const y1 = from.index * rowH + rowH / 2;\r\n const y2 = to.index * rowH + rowH / 2;\r\n let d: string;\r\n let hit: string;\r\n // The hit stroke starts 10px away from the source row so the source's link dot stays reachable.\r\n const away = y1 + (y2 >= y1 ? 10 : -10);\r\n if (x2 >= x1 + 12) {\r\n d = `M${x1} ${y1}H${r1(x1 + 6)}V${y2}H${x2}`;\r\n hit = `M${r1(x1 + 6)} ${away}V${y2}H${r1(Math.max(x1 + 6, x2 - 10))}`;\r\n } else {\r\n const ym = y1 + (y2 > y1 ? rowH / 2 : -rowH / 2);\r\n d = `M${x1} ${y1}H${r1(x1 + 6)}V${ym}H${r1(x2 - 8)}V${y2}H${x2}`;\r\n hit = `M${r1(x1 + 6)} ${away}V${ym}H${r1(x2 - 8)}V${y2}`;\r\n }\r\n out.push({\r\n key: `${fromId}>${to.task.id}`,\r\n from: fromId,\r\n to: to.task.id,\r\n fromIndex: from.index,\r\n toIndex: to.index,\r\n d,\r\n arrow: `M${x2} ${y2}l-6 -4v8z`,\r\n hit\r\n });\r\n }\r\n }\r\n return out;\r\n}\r\n\r\n/** Finish-to-start connectors as one SVG path plus arrowheads. */\r\nexport function dependencyPaths(rows: Row[], scale: TimeScale, rowH = ROW_H): { lines: string; arrows: string } {\r\n const links = dependencyLinks(rows, scale, rowH);\r\n return { lines: links.map((l) => l.d).join(' '), arrows: links.map((l) => l.arrow).join(' ') };\r\n}\r\n\r\n/* ---------- Tooltip placement ---------- */\r\n\r\nconst TIP_GAP = 14;\r\n\r\n/**\r\n * Where a `w`×`h` tooltip goes for a bar spanning `bar` (x in content px) on a row at `rowTop`,\r\n * kept inside `view` (the visible timeline, in the same coordinates). Prefers the right of the\r\n * bar, flips to the left, and otherwise sits inside the view's edge. Prefers aligning with the\r\n * row's top, and flips up so its bottom meets the row's bottom near the end of the view.\r\n */\r\nexport function placeTip(\r\n bar: { left: number; width: number },\r\n rowTop: number,\r\n view: { left: number; right: number; top: number; bottom: number },\r\n w: number,\r\n h: number,\r\n rowH = ROW_H\r\n): { left: number; top: number } {\r\n let left = bar.left + bar.width + TIP_GAP;\r\n if (left + w > view.right - 8) {\r\n const before = bar.left - w - TIP_GAP;\r\n left = before >= view.left + 8 ? before : Math.max(view.left + 8, view.right - 8 - w);\r\n }\r\n if (left < view.left + 8) left = view.left + 8;\r\n let top = rowTop - 4;\r\n if (top + h > view.bottom - 4) top = rowTop + rowH - h;\r\n top = Math.max(top, view.top + 4);\r\n return { left, top };\r\n}\r\n\r\n/* ---------- Header labels under the today tag ---------- */\r\n\r\n/** Monospace header text: about 6.6 px per character at 11 px, 6 px at the tag's 10 px. */\r\nconst MINOR_CHAR_W = 6.6;\r\nconst TAG_CHAR_W = 6;\r\nconst TAG_PAD_X = 6;\r\n\r\n/** Width of the today tag for its label (padding included), at a text scale. */\r\nexport function todayTagWidth(label: string, textScale = 1): number {\r\n return label.length * TAG_CHAR_W * textScale + TAG_PAD_X * 2;\r\n}\r\n\r\n/**\r\n * Indexes of minor tick labels the today tag (centred on `centerX`) would cover, so they can be\r\n * skipped instead of drawn half-hidden behind it (the site's axe run flagged that as contrast).\r\n * A label starts 4 px right of its tick.\r\n */\r\nexport function ticksUnderTag(ticks: readonly { x: number; label: string }[], centerX: number, tagWidth: number, gap = 2, textScale = 1): Set<number> {\r\n const left = centerX - tagWidth / 2 - gap;\r\n const right = centerX + tagWidth / 2 + gap;\r\n const out = new Set<number>();\r\n ticks.forEach((t, i) => {\r\n const a = t.x + 4;\r\n const b = a + t.label.length * MINOR_CHAR_W * textScale;\r\n if (b > left && a < right) out.add(i);\r\n });\r\n return out;\r\n}\r\n","import { HOST_DIM } from '@ninthforge/core';\r\nimport { motion } from '@ninthforge/tokens';\r\nimport { HEADER_H, ROW_H } 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-gantt{--nf-row:${ROW_H}px;--nf-head:${HEADER_H}px;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-gantt *,.nf-gantt *::before,.nf-gantt *::after{box-sizing:border-box}\r\n.nf-g-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-g-title{font-weight:600;font-size:var(--nf-text-title);letter-spacing:-0.01em;margin-inline-end:6px}\r\n.nf-g-seg{display:inline-flex;gap:2px;padding:3px;border-radius:9px;background:var(--nf-line-soft)}\r\n.nf-g-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}\r\n.nf-g-btn:hover{background:var(--nf-line-soft)}\r\n.nf-g-seg .nf-g-btn{border:0;background:transparent;color:var(--nf-muted);padding:6px 12px;border-radius:7px}\r\n.nf-g-seg .nf-g-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-g-btn:focus-visible,.nf-g-scroll:focus-visible,.nf-g-chev:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-g-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-g-switch-track{position:relative;width:30px;height:18px;border-radius:9px;background:var(--nf-line)}\r\n.nf-g-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-g-switch[aria-pressed=\"true\"] .nf-g-switch-track{background:var(--nf-accent)}\r\n.nf-g-switch[aria-pressed=\"true\"] .nf-g-switch-track::after{transform:translateX(12px)}\r\n.nf-g-spacer{flex:1}\r\n.nf-g-scroll{position:relative;flex:1;min-height:0;overflow:auto;outline:none}\r\n.nf-g-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-g-cols{position:sticky;left:0;z-index:5;flex:none;display:grid;align-items: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-g-cols>:not(:first-child){text-align:end}\r\n.nf-g-colhead{position:relative}\r\n.nf-g-colresize{position:absolute;z-index:1;top:-8px;bottom:-10px;inset-inline-end:-6px;width:8px;cursor:col-resize;touch-action:none}\r\n.nf-g-colresize:hover{background:var(--nf-line)}\r\n.nf-g-divider{position:absolute;top:0;bottom:0;right:-4px;width:8px;cursor:col-resize;touch-action:none;z-index:1;outline:none}\r\n.nf-g-divider:hover,.nf-g-divider:focus-visible{background:var(--nf-accent);opacity:.5}\r\n.nf-g-filter{font:inherit;font-size:var(--nf-text-body);min-height:32px;width:calc(var(--nf-text-body) * 180 / 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-g-filter:focus-visible{outline:2px solid var(--nf-focus);outline-offset:1px}\r\n.nf-g-menuwrap{position:relative}\r\n.nf-g-colmenu{top:calc(100% + 6px);inset-inline-end:0;inset-inline-start:auto}\r\n.nf-g-check{display:inline-block;width:16px;color:var(--nf-accent);font-weight:700}\r\n.nf-g-inline{font:inherit;font-size:var(--nf-text-body);width:100%;min-width:0;padding:3px 6px;border-radius:6px;border:1.5px solid var(--nf-accent);background:var(--nf-surface);color:var(--nf-ink);outline:none}\r\n.nf-g-datecell .nf-g-inline{font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);padding:2px 2px}\r\n.nf-g-datecell[data-editable]{cursor:text}\r\n.nf-g-row[aria-selected=\"true\"] .nf-g-name>span[title]{cursor:text}\r\n.nf-g-axis{position:relative;flex:none}\r\n.nf-g-major{position:absolute;top:0;height:calc(var(--nf-text-label) * 26 / 12);padding:8px 10px 0;border-left:1px solid var(--nf-line);font-size:var(--nf-text-label);font-weight:600;white-space:nowrap;clip-path:inset(0)}\r\n.nf-g-major-label{position:sticky;display:inline-block}\r\n.nf-g-minor{position:absolute;top:calc(var(--nf-text-label) * 30 / 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-g-today-tag{position:absolute;top:calc(var(--nf-text-label) * 30 / 12);transform:translateX(-50%);padding:2px 6px;border-radius:5px;background:var(--nf-accent-solid,var(--nf-accent));color:var(--nf-accent-text);font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);font-weight:500;white-space:nowrap;z-index:1}\r\n.nf-g-body{position:relative}\r\n.nf-g-layer{position:absolute;top:0;pointer-events:none}\r\n.nf-g-vline{position:absolute;top:0;bottom:0;width:1px;background:var(--nf-line-soft)}\r\n.nf-g-weekend{position:absolute;top:0;bottom:0;background:var(--nf-surface-alt);opacity:.6}\r\n.nf-g-band{position:absolute;left:0;right:0;height:var(--nf-row);background:var(--nf-selected)}\r\n.nf-g-today{position:absolute;top:0;bottom:0;width:2px;margin-left:-1px;background:var(--nf-accent)}\r\n.nf-g-row{position:absolute;left:0;display:flex;height:var(--nf-row);cursor:default}\r\n.nf-g-addwrap{position:absolute;left:0;height:var(--nf-row)}\r\n.nf-g-settled .nf-g-bar,.nf-g-settled .nf-g-phase,.nf-g-settled .nf-g-ms{animation:none}\r\n.nf-g-row[data-dim] .nf-g-lane>*,.nf-g-row[data-dim] .nf-g-name{opacity:${HOST_DIM}}\r\n.nf-g-cell{position:sticky;left:0;z-index:3;flex:none;display:grid;align-items:center;padding:0 16px;background:var(--nf-surface);border-right:1px solid var(--nf-line);border-bottom:1px solid var(--nf-line-soft)}\r\n.nf-g-row[aria-selected=\"true\"] .nf-g-cell{background:var(--nf-selected)}\r\n.nf-g-name{display:flex;align-items:center;gap:8px;min-width:0;white-space:nowrap}\r\n.nf-g-name span{overflow:hidden;text-overflow:ellipsis}\r\n.nf-g-row[data-kind=\"phase\"] .nf-g-name{font-weight:600}\r\n.nf-g-chev{flex:none;display:inline-flex;align-items:center;justify-content:center;width:20px;height:20px;margin-inline-start:-4px;border:0;border-radius:5px;background:none;color:var(--nf-ink);cursor:pointer;padding:0}\r\n.nf-g-chev:hover{background:var(--nf-line-soft)}\r\n.nf-g-chev svg{transition:transform ${motion.fast} ${motion.ease}}\r\n.nf-g-chev[aria-expanded=\"false\"] svg{transform:rotate(-90deg)}\r\n.nf-g-dot{flex:none;width:8px;height:8px;border-radius:2px;background:var(--nf-milestone);transform:rotate(45deg)}\r\n.nf-gantt[data-hc] .nf-g-avatar{background:var(--nf-surface);color:var(--nf-ink);box-shadow:inset 0 0 0 1px var(--nf-ink)}\r\n.nf-g-avatar{justify-self:end;width:calc(var(--nf-text-caption) * 26 / 11);height:calc(var(--nf-text-caption) * 26 / 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)}\r\n.nf-g-num{text-align:end;font-family:var(--nf-mono-font);font-size:var(--nf-text-value);color:var(--nf-muted)}\r\n.nf-g-lane{position:relative;flex:none;border-bottom:1px solid var(--nf-line-soft);user-select:none}\r\n.nf-g-bar{position:absolute;top:calc((var(--nf-row) - 20px) / 2);height:20px;border-radius:6px;background:var(--nf-accent-tint);overflow:hidden;transform-origin:left center;animation:nf-g-grow ${motion.base} ${motion.ease} both;cursor:pointer}\r\n.nf-g-bar::after{content:\"\";position:absolute;inset:0;border-radius:inherit;border:1px solid var(--nf-accent);pointer-events:none}\r\n.nf-g-fill{height:100%;background:var(--nf-accent)}\r\n.nf-g-row[aria-selected=\"true\"] .nf-g-bar{box-shadow:0 0 0 2px var(--nf-surface),0 0 0 4px var(--nf-accent)}\r\n.nf-g-phase{position:absolute;top:calc((var(--nf-row) - 8px) / 2);height:8px;border-radius:2px;background:var(--nf-phase);transform-origin:left center;animation:nf-g-grow ${motion.base} ${motion.ease} both}\r\n.nf-g-ms{position:absolute;top:calc((var(--nf-row) - 14px) / 2);width:14px;height:14px;margin-left:-7px;border-radius:3px;background:var(--nf-milestone);border:1.5px solid var(--nf-milestone-edge,color-mix(in srgb,var(--nf-milestone) 55%,var(--nf-ink)));box-sizing:border-box;transform:rotate(45deg);animation:nf-g-pop ${motion.base} ${motion.ease} both;cursor:pointer}\r\n.nf-g-base{position:absolute;top:calc((var(--nf-row) - 20px) / 2 + 22px);height:3px;border-radius:2px;background:var(--nf-baseline)}\r\n.nf-g-deps{z-index:2;pointer-events:none;overflow:visible}\r\n.nf-g-deps path{fill:none;stroke:var(--nf-muted);stroke-width:1.4;opacity:.75}\r\n.nf-g-deps path.nf-g-arrow{fill:var(--nf-muted);stroke:none}\r\n.nf-g-deps .is-broken path{stroke:var(--nf-negative);opacity:1}\r\n.nf-g-deps .is-broken path.nf-g-arrow{fill:var(--nf-negative);stroke:none}\r\n.nf-g-deps .is-selected path{stroke:var(--nf-accent);stroke-width:2.4;opacity:1}\r\n.nf-g-deps .is-selected path.nf-g-arrow{fill:var(--nf-accent);stroke:none}\r\n.nf-g-deps path.nf-g-hit{stroke:transparent;stroke-width:10;opacity:1;pointer-events:stroke;cursor:pointer}\r\n.nf-g-deps path.nf-g-linkline{stroke:var(--nf-accent);stroke-width:2;stroke-dasharray:4 3;opacity:1}\r\n.nf-g-bar[data-editable]{cursor:grab}\r\n.nf-g-ms[data-editable]{cursor:grab}\r\n.nf-g-bar[data-locked],.nf-g-ms[data-locked]{cursor:not-allowed}\r\n.nf-g-dragging,.nf-g-dragging *{cursor:grabbing!important;user-select:none}\r\n.nf-g-h{position:absolute;top:0;bottom:0;width:8px;cursor:ew-resize;touch-action:none}\r\n.nf-g-h-l{left:0}\r\n.nf-g-h-r{right:0}\r\n.nf-g-knob{position:absolute;top:2px;bottom:2px;width:8px;border-radius:4px;background:var(--nf-surface);box-shadow:0 0 0 1.5px var(--nf-accent);cursor:ew-resize;opacity:0;transition:opacity ${motion.fast} ${motion.ease};touch-action:none}\r\n.nf-g-row:hover .nf-g-knob,.nf-g-row[aria-selected=\"true\"] .nf-g-knob{opacity:1}\r\n.nf-g-bar,.nf-g-ms,.nf-g-linkdot{touch-action:none}\r\n.nf-g-linkdot{position:absolute;top:calc((var(--nf-row) - 10px) / 2);width:10px;height:10px;border-radius:5px;background:var(--nf-surface);box-shadow:0 0 0 2px var(--nf-accent);cursor:crosshair;opacity:0;transition:opacity ${motion.fast} ${motion.ease};z-index:1}\r\n.nf-g-row:hover .nf-g-linkdot,.nf-g-row[aria-selected=\"true\"] .nf-g-linkdot{opacity:1}\r\n.nf-g-linktarget .nf-g-bar,.nf-g-linktarget .nf-g-ms{box-shadow:0 0 0 2px var(--nf-surface),0 0 0 4px var(--nf-accent)}\r\n.nf-g-linksource .nf-g-bar{box-shadow:0 0 0 2px var(--nf-surface),0 0 0 4px var(--nf-milestone)}\r\n.nf-g-ghost{position:absolute;top:calc((var(--nf-row) - 20px) / 2);height:20px;border-radius:6px;border:1.5px dashed var(--nf-muted);opacity:.6;pointer-events:none}\r\n.nf-g-draglabel{position:absolute;z-index:6;padding:4px 8px;border-radius:7px;background:var(--nf-tooltip-bg);color:var(--nf-tooltip-ink);font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);white-space:nowrap;pointer-events:none}\r\n.nf-g-rowadd{flex:none;margin-left:auto;width:22px;height:22px;border:0;border-radius:6px;background:none;color:var(--nf-muted);font:inherit;font-size:var(--nf-text-title);line-height:1;cursor:pointer;opacity:0;transition:opacity ${motion.fast} ${motion.ease}}\r\n.nf-g-row:hover .nf-g-rowadd,.nf-g-row[aria-selected=\"true\"] .nf-g-rowadd{opacity:1}\r\n.nf-g-rowadd:hover{background:var(--nf-line-soft);color:var(--nf-ink)}\r\n.nf-g-addrow{position:sticky;left:0;z-index:3;height:var(--nf-row);display:flex;align-items:center;padding:0 10px;background:var(--nf-surface);border-right:1px solid var(--nf-line)}\r\n.nf-g-addbtn{font:inherit;font-size:var(--nf-text-body);color:var(--nf-muted);background:none;border:0;padding:6px 8px;border-radius:7px;cursor:pointer}\r\n.nf-g-addbtn:hover{background:var(--nf-line-soft);color:var(--nf-ink)}\r\n.nf-g-addbtn:focus-visible,.nf-g-rowadd:focus-visible,.nf-g-menu button:focus-visible,.nf-g-toast button:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-g-menu{position:absolute;z-index:9;min-width:200px;padding:4px;border-radius:10px;background:var(--nf-surface);border:1px solid var(--nf-line);box-shadow:0 12px 28px rgba(14,17,22,.18);display:flex;flex-direction:column;animation:nf-g-fade ${motion.fast} ${motion.ease} both}\r\n.nf-g-menu button{display:flex;justify-content:space-between;gap:16px;font:inherit;font-size:var(--nf-text-body);text-align:left;color:var(--nf-ink);background:none;border:0;padding:8px 10px;border-radius:7px;cursor:pointer}\r\n.nf-g-menu button:hover{background:var(--nf-line-soft)}\r\n.nf-g-menu kbd{font-family:var(--nf-mono-font);font-size:var(--nf-text-caption);color:var(--nf-muted)}\r\n.nf-g-confirm{position:absolute;z-index:8;display:flex;align-items:center;gap:8px;padding:8px 8px 8px 12px;border-radius:10px;background:var(--nf-surface);border:1px solid var(--nf-line);box-shadow:0 12px 28px rgba(14,17,22,.18);font-size:var(--nf-text-body);white-space:nowrap;animation:nf-g-fade ${motion.fast} ${motion.ease} both}\r\n.nf-g-primary{background:var(--nf-accent-solid,var(--nf-accent));border-color:var(--nf-accent-solid,var(--nf-accent));color:var(--nf-accent-text)}\r\n.nf-g-primary:hover{background:var(--nf-accent);opacity:.9}\r\n.nf-g-toasts{position:absolute;left:12px;bottom:12px;z-index:9;display:flex;flex-direction:column;gap:8px;max-width:min(440px,calc(100% - 200px))}\r\n.nf-g-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-g-fade ${motion.fast} ${motion.ease} both}\r\n.nf-g-toast[data-tone=\"error\"]{background:var(--nf-negative);color:#fff}\r\n.nf-g-toast-text{flex:1;min-width:0}\r\n.nf-g-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-g-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-g-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);clip-path:inset(50%);white-space:nowrap}\r\n\r\n.nf-g-tip{position:absolute;z-index:6;width:var(--nf-tip-w,264px);padding:12px 14px;border-radius:12px;background:var(--nf-tooltip-bg);color:var(--nf-tooltip-ink);box-shadow:0 12px 28px rgba(14,17,22,.24);display:flex;flex-direction:column;gap:6px;pointer-events:none;animation:nf-g-fade ${motion.fast} ${motion.ease} both}\r\n.nf-g-tip b{font-size:var(--nf-text-body);font-weight:600}\r\n.nf-g-tip .nf-g-meta{font-family:var(--nf-mono-font);font-size:var(--nf-text-value);opacity:.75}\r\n.nf-g-tip .nf-g-warn{display:flex;align-items:center;gap:6px;font-size:var(--nf-text-callout);color:var(--nf-tip-warn,var(--nf-milestone))}\r\n.nf-g-tip .nf-g-warn::before{content:\"\";width:6px;height:6px;border-radius:3px;background:currentColor}\r\n.nf-g-tip .nf-g-warn[data-tone=\"danger\"]{color:var(--nf-tip-danger,var(--nf-negative))}\r\n.nf-g-empty{padding:48px 16px;text-align:center;color:var(--nf-muted)}.nf-g-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-g-emptyaction small{font-size:var(--nf-text-callout);max-width:360px}.nf-g-emptyaction [role=\"alert\"]{color:var(--nf-negative,#D93F2B)}\r\n.nf-g-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-g-mark:hover{color:var(--nf-ink)}\r\n.nf-g-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-g-renew:hover{color:var(--nf-ink)}\r\n.nf-g-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-g-key{margin-inline-start:6px;vertical-align:-1px;opacity:.7}\r\n.nf-g-loading{position:absolute;left:0;right:0;z-index:3;pointer-events:none}\r\n.nf-g-renew:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-g-renew::before{content:\"\";width:6px;height:6px;border-radius:3px;background:var(--nf-milestone)}\r\n@keyframes nf-g-grow{from{transform:scaleX(0);opacity:0}to{transform:scaleX(1);opacity:1}}\r\n@keyframes nf-g-pop{from{transform:rotate(45deg) scale(0)}to{transform:rotate(45deg) scale(1)}}\r\n@keyframes nf-g-fade{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}\r\n@media (prefers-reduced-motion: reduce){.nf-gantt *{animation:none!important;transition:none!important}}\r\n.nf-gantt[data-static] *{animation:none!important;transition:none!important}\r\n.nf-g-switch:focus-visible,.nf-g-mark:focus-visible,.nf-g-primary:focus-visible,.nf-g-divider:focus-visible{outline:2px solid var(--nf-focus);outline-offset:2px}\r\n.nf-gantt[data-rtl] .nf-g-chev[aria-expanded=\"false\"] svg{transform:rotate(90deg)}\r\n@media (forced-colors: active){\r\n.nf-gantt{border-color:CanvasText}\r\n.nf-g-bar,.nf-g-ms,.nf-g-phase,.nf-g-fill,.nf-g-base,.nf-g-today,.nf-g-today-tag,.nf-g-knob,.nf-g-linkdot,.nf-g-switch-track,.nf-g-switch-track::after,.nf-g-avatar,.nf-g-dot,.nf-g-ghost,.nf-g-band,.nf-g-weekend{forced-color-adjust:none}\r\n.nf-g-bar{background:Canvas;border:1px solid CanvasText}\r\n.nf-g-bar::after{display:none}\r\n.nf-g-ms{border-color:CanvasText}\r\n.nf-g-fill{background:Highlight}\r\n.nf-g-phase,.nf-g-ms,.nf-g-base{background:CanvasText}\r\n.nf-g-today{background:Highlight}\r\n.nf-g-today-tag{background:Highlight;color:HighlightText}\r\n.nf-g-knob,.nf-g-linkdot{background:Canvas;border-color:CanvasText}\r\n.nf-g-avatar,.nf-g-dot{background:Canvas;color:CanvasText;border:1px solid CanvasText}\r\n.nf-g-ghost{background:transparent;border:1px dashed CanvasText}\r\n.nf-g-band{background:transparent}\r\n.nf-g-weekend{background:transparent}\r\n.nf-g-switch-track{background:Canvas;border:1px solid CanvasText}\r\n.nf-g-switch-track::after{background:CanvasText}\r\n.nf-g-switch[aria-pressed=\"true\"] .nf-g-switch-track{background:Highlight}\r\n.nf-g-row[aria-selected=\"true\"] .nf-g-cell{outline:2px solid Highlight;outline-offset:-2px}\r\n.nf-g-row[aria-selected=\"true\"] .nf-g-bar,.nf-g-linktarget .nf-g-bar,.nf-g-linktarget .nf-g-ms,.nf-g-linksource .nf-g-bar{box-shadow:none;outline:2px solid Highlight;outline-offset:2px}\r\n.nf-g-deps path{stroke:CanvasText}\r\n.nf-g-deps path.nf-g-arrow{fill:CanvasText}\r\n.nf-g-deps .is-broken path{stroke:CanvasText;stroke-dasharray:4 3}\r\n.nf-g-deps .is-broken path.nf-g-arrow{fill:CanvasText;stroke:none}\r\n.nf-g-deps .is-selected path{stroke:Highlight}\r\n.nf-g-deps .is-selected path.nf-g-arrow{fill:Highlight}\r\n.nf-g-deps path.nf-g-hit{stroke:transparent}\r\n.nf-gantt :focus-visible{outline:2px solid Highlight}\r\n.nf-g-tip,.nf-g-toast,.nf-g-menu,.nf-g-confirm,.nf-g-draglabel,.nf-g-renew{border:1px solid CanvasText}\r\n.nf-g-seg .nf-g-btn[aria-pressed=\"true\"]{forced-color-adjust:none;background:Highlight;color:HighlightText}\r\n}\r\n`;\r\n","import { exportFileName, formatShort, type TimeScale } from '@ninthforge/core';\nimport { accentForText, type ComponentTheme } from '@ninthforge/tokens';\nimport { barGeometry, dependencyLinks, HEADER_H, ROW_H, type Row } from './layout';\n\n/* PNG export: the chart redrawn on a <canvas> (no DOM screenshotting, no dependencies). */\n\n/** Browsers cap canvases around 16k px a side (and ~268M px in area); stay under both. */\nexport const MAX_CANVAS_SIDE = 16_000;\nconst MAX_CANVAS_AREA = 120_000_000;\nconst TITLE_H = 44;\n\nexport interface PngLayout {\n width: number;\n height: number;\n /** Device pixels per CSS px. */\n ratio: number;\n}\n\n/** Size of the export, or null when the chart is too big even at half resolution. */\nexport function pngLayout(rowCount: number, gridWidth: number, timelineWidth: number, preferredRatio = 2): PngLayout | null {\n const width = Math.ceil(gridWidth + timelineWidth);\n const height = TITLE_H + HEADER_H + Math.max(1, rowCount) * ROW_H;\n const ratio = Math.min(preferredRatio, MAX_CANVAS_SIDE / width, MAX_CANVAS_SIDE / height, Math.sqrt(MAX_CANVAS_AREA / (width * height)));\n return ratio >= 0.5 ? { width, height, ratio } : null;\n}\n\n/** \"Capital works program\" → \"capital-works-program-2026-10-21.png\" */\nexport function pngFileName(title: string, isoDay: string): string {\n return exportFileName(title, isoDay);\n}\n\n/** Any CSS colour → a canvas-safe string (resolves names, rgb(), and color-mix() where supported). */\nfunction solid(ctx: CanvasRenderingContext2D, color: string, fallback: string): string {\n ctx.fillStyle = fallback;\n ctx.fillStyle = color;\n return String(ctx.fillStyle);\n}\n\nexport interface DrawOptions {\n title: string;\n rows: Row[];\n scale: TimeScale;\n gridWidth: number;\n theme: ComponentTheme;\n today: number;\n showBaseline: boolean;\n /** Owner column text etc. is skipped; the PNG shows names and the timeline. */\n broken: ReadonlySet<string>;\n}\n\nexport function drawChart(ctx: CanvasRenderingContext2D, layout: PngLayout, o: DrawOptions): void {\n const c = (k: keyof ComponentTheme, fb = '#888') => solid(ctx, o.theme[k], fb);\n const T = {\n surface: c('surface', '#fff'),\n ink: c('ink', '#111'),\n muted: c('muted', '#666'),\n line: c('line', '#ddd'),\n lineSoft: c('lineSoft', '#eee'),\n accent: c('accent', '#2E54FF'),\n phase: c('phase', '#111'),\n milestone: c('milestone', '#d90'),\n baseline: c('baseline', '#bbb'),\n negative: c('negative', '#d33')\n };\n const font = 'Geist, system-ui, -apple-system, \"Segoe UI\", sans-serif';\n const mono = '\"Geist Mono\", ui-monospace, monospace';\n const { width, height, ratio } = layout;\n const { scale, gridWidth } = o;\n ctx.save();\n ctx.scale(ratio, ratio);\n ctx.fillStyle = T.surface;\n ctx.fillRect(0, 0, width, height);\n ctx.textBaseline = 'middle';\n\n // Title.\n ctx.fillStyle = T.ink;\n ctx.font = `600 16px ${font}`;\n ctx.fillText(o.title, 16, TITLE_H / 2);\n line(ctx, 0, TITLE_H - 0.5, width, TITLE_H - 0.5, T.line);\n\n // Header: grid label + time axis.\n const top = TITLE_H;\n ctx.font = `500 11px ${mono}`;\n ctx.fillStyle = T.muted;\n ctx.fillText('TASK', 16, top + HEADER_H - 16);\n for (const tk of scale.major) {\n ctx.fillStyle = T.ink;\n ctx.font = `500 12px ${font}`;\n ctx.fillText(tk.label, gridWidth + tk.x + 8, top + 16);\n line(ctx, gridWidth + tk.x + 0.5, top, gridWidth + tk.x + 0.5, height, T.line);\n }\n ctx.font = `11px ${mono}`;\n ctx.fillStyle = T.muted;\n for (const tk of scale.minor) {\n if (tk.width >= 24) ctx.fillText(tk.label, gridWidth + tk.x + 4, top + HEADER_H - 14);\n }\n line(ctx, 0, top + HEADER_H - 0.5, width, top + HEADER_H - 0.5, T.line);\n line(ctx, gridWidth - 0.5, top, gridWidth - 0.5, height, T.line);\n\n const body = top + HEADER_H;\n // Weekend shading.\n ctx.fillStyle = T.lineSoft;\n for (const w of scale.weekends) ctx.fillRect(gridWidth + w.x, body, w.width, height - body);\n\n // Rows.\n const tint = (alpha: number) => {\n ctx.globalAlpha = alpha;\n };\n for (const r of o.rows) {\n const y = body + r.index * ROW_H;\n line(ctx, 0, y + ROW_H - 0.5, width, y + ROW_H - 0.5, T.lineSoft);\n // Name, clipped to the grid.\n ctx.save();\n ctx.beginPath();\n ctx.rect(0, y, gridWidth - 8, ROW_H);\n ctx.clip();\n ctx.fillStyle = T.ink;\n ctx.font = `${r.kind === 'phase' ? 600 : 400} 13px ${font}`;\n ctx.fillText(r.task.name, 16 + r.depth * 18 + (r.hasChildren || r.depth > 0 ? 18 : 0), y + ROW_H / 2);\n ctx.restore();\n\n ctx.save();\n ctx.translate(gridWidth, y);\n if (o.showBaseline && !Number.isNaN(r.baselineStart) && !Number.isNaN(r.baselineEnd)) {\n const g = barGeometry(r.baselineStart, r.baselineEnd, scale);\n if (g) roundRect(ctx, g.left, 32, g.width, 3, 1.5, T.baseline);\n }\n if (r.kind === 'milestone') {\n if (!Number.isNaN(r.start)) {\n const cx = scale.x(r.start) + scale.dayWidth / 2;\n ctx.save();\n ctx.translate(cx, ROW_H / 2);\n ctx.rotate(Math.PI / 4);\n roundRect(ctx, -7, -7, 14, 14, 3, T.milestone);\n ctx.restore();\n }\n } else {\n const g = barGeometry(r.start, r.end, scale);\n if (g && r.kind === 'phase') roundRect(ctx, g.left, 16, g.width, 8, 2, T.phase);\n else if (g) {\n tint(0.22);\n roundRect(ctx, g.left, 10, g.width, 20, 6, T.accent);\n tint(1);\n if (r.progress > 0) {\n ctx.save();\n ctx.beginPath();\n ctx.rect(g.left, 10, (g.width * r.progress) / 100, 20);\n ctx.clip();\n roundRect(ctx, g.left, 10, g.width, 20, 6, T.accent);\n ctx.restore();\n }\n }\n }\n ctx.restore();\n }\n\n // Connectors (Path2D understands the same SVG path strings the chart uses).\n if (typeof Path2D !== 'undefined') {\n ctx.save();\n ctx.translate(gridWidth, body);\n ctx.lineWidth = 1.5;\n for (const l of dependencyLinks(o.rows, scale)) {\n const bad = o.broken.has(l.key);\n ctx.strokeStyle = bad ? T.negative : T.muted;\n ctx.fillStyle = bad ? T.negative : T.muted;\n ctx.stroke(new Path2D(l.d));\n ctx.fill(new Path2D(l.arrow));\n }\n ctx.restore();\n }\n\n // Today.\n if (!Number.isNaN(o.today) && o.today >= scale.startDay && o.today < scale.endDay) {\n const x = gridWidth + scale.x(o.today);\n ctx.fillStyle = T.accent;\n ctx.fillRect(x - 1, top, 2, height - top);\n ctx.font = `500 10px ${mono}`;\n const label = formatShort(o.today);\n const w = ctx.measureText(label).width + 12;\n // Same 4.5:1 rule as the live chart: the accent shaded just enough under its text.\n roundRect(ctx, x - w / 2, top + HEADER_H - 22, w, 16, 5, solid(ctx, accentForText(T.accent).background, T.accent));\n ctx.fillStyle = solid(ctx, o.theme.accentText, '#fff');\n ctx.fillText(label, x - w / 2 + 6, top + HEADER_H - 14);\n }\n ctx.restore();\n}\n\nfunction line(ctx: CanvasRenderingContext2D, x1: number, y1: number, x2: number, y2: number, color: string) {\n ctx.strokeStyle = color;\n ctx.lineWidth = 1;\n ctx.beginPath();\n ctx.moveTo(x1, y1);\n ctx.lineTo(x2, y2);\n ctx.stroke();\n}\n\nfunction roundRect(ctx: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number, color: string) {\n const rr = Math.min(r, w / 2, h / 2);\n ctx.fillStyle = color;\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 ctx.fill();\n}\n\n/** Draw and download. Returns false when the chart is too large for one image. */\nexport function exportPng(o: DrawOptions, fileName: string, doc: Document = document): boolean {\n const layout = pngLayout(o.rows.length, o.gridWidth, o.scale.width);\n if (!layout) return false;\n const canvas = doc.createElement('canvas');\n canvas.width = Math.round(layout.width * layout.ratio);\n canvas.height = Math.round(layout.height * layout.ratio);\n const ctx = canvas.getContext('2d');\n if (!ctx) return false;\n drawChart(ctx, layout, o);\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 return true;\n}\n","import {\n addMonths,\n escapeHtml,\n printDocument,\n formatLong,\n formatShort,\n MONTHS,\n parts,\n startOfMonth,\n startOfWeek,\n type WorkingWeek\n} from '@ninthforge/core';\nimport type { ComponentTheme } from '@ninthforge/tokens';\nimport { durationLabel } from './edit';\nimport { rowsRange, type Row } from './layout';\n\n/* Print: a self-contained HTML document (no scripts, no network) printed from a same-origin\n about:blank iframe, so the host page is never re-laid out or printed by accident. */\n\nexport interface PrintOptions {\n title: string;\n rows: Row[];\n theme: ComponentTheme;\n /** Day number for the today line; NaN hides it. */\n today: number;\n /** Day number shown as \"Printed on …\". */\n printedOn: number;\n week: WorkingWeek;\n showBaseline: boolean;\n /** Timeline width in CSS px (A4/Letter landscape leaves ~1040px; the rest goes to the task list). */\n timelineWidth?: number;\n}\n\nexport const PRINT_TIMELINE_W = 560;\nconst PRINT_ROW_H = 26;\n\n\n\nexport interface PrintAxis {\n from: number;\n /** Exclusive. */\n to: number;\n x: (day: number) => number;\n ticks: { x: number; label: string }[];\n}\n\n/** Fit the rows' date range to `width` px, with month ticks (week ticks for short plans). */\nexport function printAxis(rows: Row[], width: number, today = NaN): PrintAxis | null {\n const range = rowsRange(rows);\n if (!range) return null;\n let from = range.from;\n let to = range.to;\n if (!Number.isNaN(today) && today >= from - 14 && today <= to + 14) {\n from = Math.min(from, today);\n to = Math.max(to, today);\n }\n const short = to - from < 70;\n from = short ? startOfWeek(from) : startOfMonth(from);\n const end = short ? startOfWeek(to) + 7 : addMonths(startOfMonth(to), 1);\n const per = width / (end - from);\n const x = (d: number) => Math.round((d - from) * per * 10) / 10;\n const ticks: { x: number; label: string }[] = [];\n for (let d = from; d < end; d = short ? d + 7 : addMonths(d, 1)) {\n const p = parts(d);\n ticks.push({ x: x(d), label: short ? formatShort(d) : p.month === 0 || ticks.length === 0 ? `${MONTHS[p.month]} ${p.year}` : MONTHS[p.month]! });\n }\n return { from, to: end, x, ticks };\n}\n\nfunction barSvg(r: Row, axis: PrintAxis, w: number, o: PrintOptions): string {\n const t = o.theme;\n const h = PRINT_ROW_H;\n const parts: string[] = [];\n for (const tk of axis.ticks) parts.push(`<line x1=\"${tk.x}\" x2=\"${tk.x}\" y1=\"0\" y2=\"${h}\" stroke=\"${t.lineSoft}\"/>`);\n if (!Number.isNaN(o.today) && o.today >= axis.from && o.today < axis.to) {\n const tx = axis.x(o.today) + 0.5;\n parts.push(`<line x1=\"${tx}\" x2=\"${tx}\" y1=\"0\" y2=\"${h}\" stroke=\"${t.accent}\" stroke-width=\"1.5\"/>`);\n }\n if (o.showBaseline && !Number.isNaN(r.baselineStart) && !Number.isNaN(r.baselineEnd)) {\n const bx = axis.x(r.baselineStart);\n parts.push(`<rect x=\"${bx}\" y=\"${h - 5}\" width=\"${Math.max(2, axis.x(r.baselineEnd + 1) - bx)}\" height=\"2\" rx=\"1\" fill=\"${t.baseline}\"/>`);\n }\n if (!Number.isNaN(r.start)) {\n const x = axis.x(r.start);\n const bw = Math.max(2, axis.x(r.end + 1) - x);\n if (r.kind === 'milestone') {\n const cx = axis.x(r.start) + (axis.x(r.start + 1) - axis.x(r.start)) / 2;\n parts.push(`<rect x=\"${cx - 5}\" y=\"${h / 2 - 5}\" width=\"10\" height=\"10\" rx=\"2\" fill=\"${t.milestone}\" transform=\"rotate(45 ${cx} ${h / 2})\"/>`);\n } else if (r.kind === 'phase') {\n parts.push(`<rect x=\"${x}\" y=\"${h / 2 - 3}\" width=\"${bw}\" height=\"6\" rx=\"2\" fill=\"${t.phase}\"/>`);\n } else {\n parts.push(`<rect x=\"${x}\" y=\"${h / 2 - 7}\" width=\"${bw}\" height=\"14\" rx=\"4\" fill=\"${t.accent}\" fill-opacity=\".22\"/>`);\n if (r.progress > 0)\n parts.push(`<rect x=\"${x}\" y=\"${h / 2 - 7}\" width=\"${Math.round((bw * r.progress) / 10) / 10}\" height=\"14\" rx=\"4\" fill=\"${t.accent}\"/>`);\n }\n }\n return `<svg width=\"${w}\" height=\"${h}\" viewBox=\"0 0 ${w} ${h}\" aria-hidden=\"true\">${parts.join('')}</svg>`;\n}\n\n/** The print document. Pure: same inputs, same HTML. */\nexport function printHtml(o: PrintOptions): string {\n const t = o.theme;\n const w = o.timelineWidth ?? PRINT_TIMELINE_W;\n const axis = printAxis(o.rows, w, o.today);\n const e = escapeHtml;\n const head = axis\n ? `<svg width=\"${w}\" height=\"22\" viewBox=\"0 0 ${w} 22\" aria-hidden=\"true\">${axis.ticks\n .map((tk) => `<line x1=\"${tk.x}\" x2=\"${tk.x}\" y1=\"0\" y2=\"22\" stroke=\"${t.line}\"/><text x=\"${tk.x + 4}\" y=\"15\">${e(tk.label)}</text>`)\n .join('')}</svg>`\n : '';\n const body = o.rows\n .map((r) => {\n const isMs = r.kind === 'milestone';\n const dates = Number.isNaN(r.start)\n ? ''\n : isMs\n ? formatShort(r.start)\n : `${formatShort(r.start)} – ${formatShort(r.end)}`;\n const dur = Number.isNaN(r.start) || isMs ? '' : durationLabel(r.start, r.end, o.week).replace(' working', '').replace(/ days?$/, 'd');\n return `<tr class=\"${r.kind}\"><td class=\"name\" style=\"padding-left:${8 + r.depth * 14}px\">${e(r.task.name)}</td><td>${e(\n r.task.owner?.name ?? ''\n )}</td><td class=\"num\">${dates}</td><td class=\"num\">${dur}</td><td class=\"num\">${isMs ? '' : `${r.progress}%`}</td><td class=\"tl\">${\n axis ? barSvg(r, axis, w, o) : ''\n }</td></tr>`;\n })\n .join('');\n const legend = [\n `<span><i style=\"border-radius:3px\"><u style=\"background:${t.accent}\"></u><b style=\"background:${t.accent}\"></b></i>Task (dark part done)</span>`,\n `<span><i style=\"background:${t.phase};height:5px\"></i>Phase</span>`,\n `<span><i style=\"background:${t.milestone};width:9px;height:9px;transform:rotate(45deg);border-radius:2px\"></i>Milestone</span>`,\n Number.isNaN(o.today) ? '' : `<span><i style=\"background:${t.accent};width:2px;height:12px\"></i>Today</span>`,\n o.showBaseline ? `<span><i style=\"background:${t.baseline};height:2px\"></i>Baseline</span>` : ''\n ].join('');\n const unit = o.week === 'mon-fri' ? 'Working days' : 'Days';\n return `<!doctype html><html><head><meta charset=\"utf-8\"><title>${e(o.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;gap:12px;margin-bottom:8px}\nh1{font-size:16px;margin:0;letter-spacing:-0.01em}\n.meta{color:${t.muted};font-size:10px}\n.legend{display:flex;flex-wrap:wrap;gap:14px;margin-left:auto;color:${t.muted}}\n.legend span{display:inline-flex;align-items:center;gap:6px}\n.legend i{display:inline-block;position:relative;width:22px;height:9px;overflow:hidden}\n.legend u{position:absolute;inset:0;opacity:.22}\n.legend b{position:absolute;inset:0 50% 0 0}\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}\nth.tl text{font-family:inherit;font-size:8.5px;fill:${t.muted}}\ntd{height:${PRINT_ROW_H}px;padding:0 6px;border-bottom:1px solid ${t.lineSoft};white-space:nowrap;overflow:hidden;text-overflow:ellipsis;vertical-align:middle}\ntd.tl{padding:0;width:${w}px}\ntd.tl svg{display:block}\n.num{text-align:right;font-variant-numeric:tabular-nums}\nth.num{text-align:right}\ntr.phase td.name{font-weight:600}\nfooter{margin-top:8px;color:${t.muted}}\n</style></head><body>\n<header><h1>${e(o.title)}</h1><span class=\"meta\">Printed on ${formatLong(o.printedOn)} · ${o.rows.length} ${o.rows.length === 1 ? 'row' : 'rows'}</span><div class=\"legend\">${legend}</div></header>\n<table><colgroup><col><col style=\"width:90px\"><col style=\"width:124px\"><col style=\"width:48px\"><col style=\"width:44px\"><col style=\"width:${w}px\"></colgroup>\n<thead><tr><th>Task</th><th>Owner</th><th class=\"num\">Dates</th><th class=\"num\" title=\"${unit}\">${o.week === 'mon-fri' ? 'Wkdays' : 'Days'}</th><th class=\"num\">Done</th><th class=\"tl\">${head}</th></tr></thead>\n<tbody>${body}</tbody></table>\n</body></html>`;\n}\n\n/** Shared with the Scheduler; kept here so existing imports work. */\nexport { escapeHtml, printDocument };\n","import { useEditing as useSharedEditing, type EditingSpec } from '@ninthforge/editing';\r\nimport { describeChange, GANTT_FIELDS, type GanttFields } from './edit';\r\nimport type { GanttChange, GanttTask } from './types';\r\n\r\nconst SPEC: EditingSpec<GanttTask, GanttFields> = {\r\n ...GANTT_FIELDS,\r\n describe: (change, nameOf) => describeChange(change as GanttChange, nameOf),\r\n noun: 'task'\r\n};\r\n\r\n/**\r\n * Optimistic edits for the Gantt: the shared hook from @ninthforge/editing with the Gantt's\r\n * fields, comparison rules and words. See that package for how overrides and undo work.\r\n */\r\nexport function useEditing(\r\n tasks: GanttTask[],\r\n onTaskChange: ((c: GanttChange) => void | Promise<void>) | undefined,\r\n lock: Pick<EditingSpec<GanttTask, GanttFields>, 'locked' | 'lockedMessage' | 'lockedTrialUrl'> = {}\r\n) {\r\n return useSharedEditing<GanttTask, GanttFields, GanttChange>(tasks, onTaskChange, { ...SPEC, ...lock });\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","import type { GanttTask } from './types';\n\n/** Realistic sample plan used by the demo, docs and tests. */\nexport const sampleTasks: GanttTask[] = [\n { id: 'design', name: 'Design', kind: 'phase' },\n { id: 'survey', parentId: 'design', name: 'Site survey', start: '2026-10-05', end: '2026-10-16', progress: 100, owner: { name: 'Jess Lee' }, baselineStart: '2026-10-05', baselineEnd: '2026-10-16' },\n { id: 'concept', parentId: 'design', name: 'Concept design', start: '2026-10-12', end: '2026-10-30', progress: 70, owner: { name: 'Alex Morgan' }, baselineStart: '2026-10-12', baselineEnd: '2026-10-26', dependsOn: ['survey'] },\n { id: 'review', parentId: 'design', name: 'Design review', kind: 'milestone', date: '2026-11-02', dependsOn: ['concept'] },\n { id: 'procure', name: 'Procurement', kind: 'phase' },\n { id: 'tender', parentId: 'procure', name: 'Tender package', start: '2026-11-02', end: '2026-11-20', progress: 25, owner: { name: 'Sam Kaur' }, baselineStart: '2026-10-30', baselineEnd: '2026-11-18', dependsOn: ['review'] },\n { id: 'eval', parentId: 'procure', name: 'Supplier evaluation', start: '2026-11-16', end: '2026-12-04', progress: 0, owner: { name: 'Jess Lee' } },\n { id: 'award', parentId: 'procure', name: 'Contract award', kind: 'milestone', date: '2026-12-07', dependsOn: ['eval'] },\n { id: 'deliver', name: 'Delivery', kind: 'phase' },\n { id: 'mobilise', parentId: 'deliver', name: 'Mobilisation', start: '2026-12-07', end: '2026-12-18', progress: 0, owner: { name: 'Ravi Tan' }, dependsOn: ['award'] },\n { id: 'build1', parentId: 'deliver', name: 'Construction stage 1', start: '2026-12-14', end: '2027-01-29', progress: 0, owner: { name: 'Alex Morgan' } },\n { id: 'handover', parentId: 'deliver', name: 'Stage 1 handover', kind: 'milestone', date: '2027-02-05', dependsOn: ['build1'] }\n];\n"],"mappings":"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","names":["React","import_react","brand","next","parts","ratio","next","next","fieldsOf","previousOf","next","isNoop","emptyHistory","window","ratio","font","today","parts","useEditing","import_react","next","useEditing","isNoop","fieldsOf"]}