@danxbot/ui 20.23.3 → 21.1.0

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.
Files changed (106) hide show
  1. package/dist/index.js +23607 -22540
  2. package/dist/index.js.map +1 -1
  3. package/dist/pure.js +290 -210
  4. package/dist/pure.js.map +1 -1
  5. package/dist/types/components/AppShell.d.ts +1 -1
  6. package/dist/types/components/Board.d.ts +4 -6
  7. package/dist/types/components/Card.d.ts +25 -1
  8. package/dist/types/components/Combobox.d.ts +2 -1
  9. package/dist/types/components/CommandPalette.d.ts +2 -0
  10. package/dist/types/components/DateField.d.ts +2 -2
  11. package/dist/types/components/DragDrop.d.ts +2 -4
  12. package/dist/types/components/InlineEdit.d.ts +4 -4
  13. package/dist/types/components/InlineMarkdownEdit.d.ts +2 -2
  14. package/dist/types/components/PagedWindowList.d.ts +1 -9
  15. package/dist/types/components/Progress.d.ts +4 -4
  16. package/dist/types/components/ReadMore.d.ts +2 -2
  17. package/dist/types/components/SecretField.d.ts +1 -1
  18. package/dist/types/components/Slider.d.ts +1 -1
  19. package/dist/types/components/SortMenu.d.ts +1 -1
  20. package/dist/types/components/Stat.d.ts +1 -1
  21. package/dist/types/components/StatusSelect.d.ts +1 -1
  22. package/dist/types/components/TriStateSwitch.d.ts +1 -7
  23. package/dist/types/components/board/context.d.ts +2 -4
  24. package/dist/types/components/calendar/CalendarMonth.d.ts +2 -3
  25. package/dist/types/components/calendar/CalendarTimeGrid.d.ts +1 -2
  26. package/dist/types/components/calendar/EventActions.d.ts +1 -2
  27. package/dist/types/components/calendar/format.d.ts +10 -9
  28. package/dist/types/components/calendar/use-calendar-drag.d.ts +0 -1
  29. package/dist/types/components/calendar/use-event-keyboard.d.ts +0 -1
  30. package/dist/types/components/chart/BarList.d.ts +1 -1
  31. package/dist/types/components/chart/ShareBar.d.ts +1 -1
  32. package/dist/types/components/chart/TimeSeriesChart.d.ts +2 -4
  33. package/dist/types/components/chart/shared.d.ts +2 -2
  34. package/dist/types/components/chart/time-series-scale.d.ts +1 -1
  35. package/dist/types/components/chat/EffortControl.d.ts +2 -2
  36. package/dist/types/components/chat/MessageScroller.d.ts +1 -1
  37. package/dist/types/components/checklist/StatusChecklist.d.ts +1 -1
  38. package/dist/types/components/edit/outcome.d.ts +0 -12
  39. package/dist/types/components/file-viewer/frames.d.ts +2 -1
  40. package/dist/types/components/kanban/KanbanBoard.d.ts +2 -21
  41. package/dist/types/components/kanban/types.d.ts +1 -1
  42. package/dist/types/components/markdown/Affordances.d.ts +3 -1
  43. package/dist/types/components/openapi/resolve.d.ts +7 -3
  44. package/dist/types/components/openapi/types.d.ts +2 -1
  45. package/dist/types/components/provenance/ConfidenceBar.d.ts +1 -1
  46. package/dist/types/components/provenance/ConfidenceReadout.d.ts +2 -2
  47. package/dist/types/components/provenance/ProvenanceBadge.d.ts +4 -3
  48. package/dist/types/components/provenance/ProvenanceDetail.d.ts +4 -6
  49. package/dist/types/components/provenance/ProvenanceDisclosure.d.ts +1 -1
  50. package/dist/types/components/provenance/absence.d.ts +4 -3
  51. package/dist/types/components/rbac/RoleKeyPicker.d.ts +1 -1
  52. package/dist/types/components/rbac/permissionSections.d.ts +1 -1
  53. package/dist/types/components/rbac/stagedForm.d.ts +4 -4
  54. package/dist/types/components/roadmap/RoadmapItemCard.d.ts +0 -1
  55. package/dist/types/index.d.ts +9 -7
  56. package/dist/types/lib/board/index.d.ts +2 -1
  57. package/dist/types/lib/board/move.d.ts +2 -2
  58. package/dist/types/lib/board/rejection.d.ts +8 -0
  59. package/dist/types/lib/board/types.d.ts +46 -0
  60. package/dist/types/lib/board/use-board.d.ts +2 -2
  61. package/dist/types/lib/calendar/drag.d.ts +1 -1
  62. package/dist/types/lib/calendar/index.d.ts +1 -1
  63. package/dist/types/lib/calendar/types.d.ts +3 -3
  64. package/dist/types/lib/confidence.d.ts +5 -0
  65. package/dist/types/lib/dnd/announce.d.ts +5 -23
  66. package/dist/types/lib/dnd/index.d.ts +1 -1
  67. package/dist/types/lib/format.d.ts +19 -18
  68. package/dist/types/lib/i18n/areas/auth-provenance-flows.d.ts +258 -0
  69. package/dist/types/lib/i18n/areas/board.d.ts +51 -0
  70. package/dist/types/lib/i18n/areas/chat-chart.d.ts +164 -0
  71. package/dist/types/lib/i18n/areas/code-editor.d.ts +40 -0
  72. package/dist/types/lib/i18n/areas/dates.d.ts +103 -0
  73. package/dist/types/lib/i18n/areas/docs-views.d.ts +268 -0
  74. package/dist/types/lib/i18n/areas/feedback.d.ts +104 -0
  75. package/dist/types/lib/i18n/areas/formats.d.ts +30 -0
  76. package/dist/types/lib/i18n/areas/forms.d.ts +130 -0
  77. package/dist/types/lib/i18n/areas/markdown.d.ts +139 -0
  78. package/dist/types/lib/i18n/areas/move-rejected.d.ts +16 -0
  79. package/dist/types/lib/i18n/areas/navigation.d.ts +144 -0
  80. package/dist/types/lib/i18n/areas/rbac.d.ts +584 -0
  81. package/dist/types/lib/i18n/emphasis.d.ts +3 -0
  82. package/dist/types/lib/i18n/index.d.ts +8 -0
  83. package/dist/types/lib/i18n/number-format.d.ts +5 -0
  84. package/dist/types/lib/i18n/plural.d.ts +7 -0
  85. package/dist/types/lib/i18n/provider.d.ts +16 -0
  86. package/dist/types/lib/i18n/strings.d.ts +15 -0
  87. package/dist/types/lib/intl-cache.d.ts +31 -0
  88. package/dist/types/lib/markdown/commands.d.ts +4 -2
  89. package/dist/types/lib/markdown/dom.d.ts +27 -21
  90. package/dist/types/lib/markdown/hotkeys.d.ts +12 -5
  91. package/dist/types/lib/markdown/image-decoration.d.ts +22 -1
  92. package/dist/types/lib/markdown/image-pipeline.d.ts +2 -0
  93. package/dist/types/lib/markdown/images.d.ts +1 -1
  94. package/dist/types/lib/markdown/reconcile.d.ts +4 -2
  95. package/dist/types/lib/markdown/surface.d.ts +4 -2
  96. package/dist/types/lib/reduced-motion-help.d.ts +2 -1
  97. package/dist/types/lib/time/relativeAge.d.ts +3 -2
  98. package/dist/types/lib/time/zone.d.ts +9 -0
  99. package/dist/types/lib/values/boolean.d.ts +11 -10
  100. package/dist/types/lib/values/currency.d.ts +5 -5
  101. package/dist/types/lib/values/date.d.ts +4 -4
  102. package/dist/types/lib/values/datetime.d.ts +3 -3
  103. package/dist/types/lib/values/locale-date.d.ts +34 -0
  104. package/dist/types/lib/values/number.d.ts +5 -5
  105. package/dist/types/pure.d.ts +1 -0
  106. package/package.json +1 -1
package/dist/pure.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pure.js","sources":["../src/lib/format.ts","../src/lib/pad.ts","../src/lib/time/types.ts","../src/lib/time/plain.ts","../src/lib/values/absent.ts","../src/lib/values/date.ts","../src/lib/time/zone.ts","../src/lib/values/datetime.ts","../src/lib/values/currency.ts","../src/lib/values/number.ts","../src/lib/values/boolean.ts","../src/lib/confidence.ts","../src/lib/chart-palette.ts","../src/lib/preference-values.ts","../src/lib/design-frame/protocol.ts","../src/lib/design-frame/client.ts"],"sourcesContent":["/* ----------------------------------------------------------------------------\n NUMBERS, TURNED INTO THE STRINGS PEOPLE READ.\n\n These lived inside the components that needed them first — `formatBytes` in\n `chat/Attachment.tsx`, `formatTokens` in `chat/ContextMeter.tsx` — which was\n fine right up until somebody outside a component wanted one. The package\n exports a single entry, so importing `formatBytes` into an application's own\n formatting layer dragged React and every component in this library into a\n layer that is deliberately framework-free.\n\n They are here because they are DECISIONS, not utilities: how much precision\n a reader can act on, and where a rounded figure stops telling the truth. Two\n places rendering the same quantity to different precision is the defect this\n file prevents — see each function for the call it makes.\n\n NOTHING IN THIS FILE MAY IMPORT REACT, directly or transitively. It is the\n whole content of the `@danxbot/ui/pure` entry; `src/pure.ts` states the\n contract and `pure-entry.test.ts` enforces it against both the source graph\n and the built artifact.\n -------------------------------------------------------------------------- */\n\n/** Binary units, because these are file sizes and every OS reports them this way. */\nexport function formatBytes(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return \"\";\n if (bytes < 1024) return `${bytes} B`;\n const units = [\"KB\", \"MB\", \"GB\", \"TB\"];\n let value = bytes / 1024;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n /* One decimal below 10 and none above: \"1.4 MB\" is useful precision,\n \"847.3 KB\" is noise on a number nobody acts on. */\n return `${value < 10 ? value.toFixed(1) : Math.round(value)} ${units[unit]}`;\n}\n\n/**\n * 241_600 → \"241.6k\". A thin, token-domain-named wrapper — see\n * `formatCompactNumber` below for the shared algorithm and its own reasoning.\n * Kept as its own function (rather than every call site switching to the\n * generic one directly) because \"tokens\" is a real vocabulary word in this\n * package's chat components, and renaming every call site to the generic name\n * would cost real diff for zero behavioral gain.\n */\nexport function formatTokens(tokens: number): string {\n return formatCompactNumber(tokens);\n}\n\n/**\n * A GENERIC large count, abbreviated the way a person reads it aloud:\n * `850` → `\"850\"`, `1_100` → `\"1.1k\"`, `3_400_000` → `\"3.4M\"`,\n * `4_400_000_000` → `\"4.4B\"`. `null` / `undefined` / non-finite → `\"—\"`.\n *\n * One decimal per tier (k/M/B/T), trimmed of a trailing \".0\" (`1_000_000` →\n * `\"1M\"`, not `\"1.0M\"`), and exact below 1,000 — a count small enough to read\n * digit-by-digit gets no abbreviation at all, since \"850\" is not improved by\n * becoming \"0.9k\". This is the same one-decimal-per-tier shape `formatTokens`\n * already used for a token budget; the difference that justified a SEPARATE,\n * generic export rather than just widening that one is scope: `formatTokens`\n * is a token-budget's own vocabulary (a live context meter), while this\n * function is for any large count a screen needs to abbreviate — a field\n * tally, a donut's fraction, a record count — with no token-specific meaning\n * attached to its name.\n *\n * NEGATIVE VALUES keep their sign (`-1_100` → `\"-1.1k\"`) rather than being\n * treated as absent — a generic count utility cannot assume its caller's\n * domain never has one, the way `formatTokens`'s own callers do.\n */\nexport function formatCompactNumber(value: number | null | undefined): string {\n if (value == null || !Number.isFinite(value)) return \"—\";\n const sign = value < 0 ? \"-\" : \"\";\n const abs = Math.abs(value);\n const TIERS: readonly [threshold: number, suffix: string][] = [\n [1_000_000_000_000, \"T\"],\n [1_000_000_000, \"B\"],\n [1_000_000, \"M\"],\n [1_000, \"k\"],\n ];\n for (const [threshold, suffix] of TIERS) {\n if (abs >= threshold) return `${sign}${Number((abs / threshold).toFixed(1))}${suffix}`;\n }\n return `${sign}${Math.round(abs)}`;\n}\n","/** Zero-pad a non-negative integer to `width` digits — `pad(9)` → `\"09\"`,\n * `pad(7, 3)` → `\"007\"`. Internal only: shared by the time and value\n * formatters that print fixed-width calendar/clock fields, not part of the\n * public package surface (DX-3254 — this one-liner used to be redefined\n * independently in four places). */\nexport function pad(n: number, width = 2): string {\n return String(n).padStart(width, \"0\");\n}\n","/* ============================================================================\n THE TIME LAYER'S VOCABULARY — deliberately `Temporal`'s, minus what we do\n not need.\n\n WHY WE HAVE OUR OWN AT ALL. `Temporal` is the right answer and it is not\n available: Chrome 144+, Firefox 139+, Node 26+, and **Safari has not shipped\n it in any stable version, desktop or iOS**. Every iOS browser is WebKit, so\n \"modern browsers only\" does not rescue us — global support is ~69% and the\n entire gap is Apple. The polyfill is 20.4 kB gzip and is a real option; it is\n priced on the tracker (CAL-TIME) so reversing this decision is one sentence.\n\n WHY THE SHAPES MIRROR `Temporal` ANYWAY. The day Safari ships it, the swap\n should be mechanical rather than a rewrite. So `PlainDate` has `year` /\n `month` / `day` with a ONE-BASED month, `PlainDateTime` extends it,\n `Instant` is a fixed point on the timeline, and the disambiguation modes are\n spelled exactly as `Temporal.ZonedDateTime.from` spells them. Anywhere a name\n here differs from `Temporal`'s, that is a bug in this file.\n\n WHAT IS DELIBERATELY ABSENT: calendars other than ISO 8601, nanosecond\n precision, `Temporal.Duration`'s full field set, and any notion of a\n `ZonedDateTime` object. A calendar needs an instant, a wall-clock reading, a\n calendar day, and a way to move between them; carrying more would be carrying\n a date library we did not choose to write.\n ========================================================================= */\n\nimport { pad } from \"../pad\";\n\n/**\n * A point on the timeline, as milliseconds since the epoch.\n *\n * NOT a `Date`. A `Date` is a mutable object that prints itself in the host's\n * local zone, and every calendar bug in the survey behind this component\n * involved someone reading a `Date`'s local fields when they meant a specific\n * zone's. A number cannot be misread that way — it has no fields to read.\n */\nexport type Instant = number;\n\n/** An IANA time zone identifier, e.g. `\"America/New_York\"`. */\nexport type TimeZone = string;\n\n/**\n * A calendar day. No time, no zone, no offset — just which square on the wall.\n *\n * `month` IS ONE-BASED, matching `Temporal` and matching how every human writes\n * a date. `Date`'s zero-based month is the single most common off-by-one in\n * date code and it is not reproduced here.\n */\nexport interface PlainDate {\n readonly year: number;\n /** 1–12. */\n readonly month: number;\n /** 1–31. */\n readonly day: number;\n}\n\n/**\n * A wall-clock reading. What a clock on the wall says, with no claim about\n * which wall.\n *\n * This is the type that makes the ambiguity explicit rather than accidental:\n * `2026-11-01T01:30` in New York happened TWICE, and `2026-03-08T02:30` never\n * happened at all. A `PlainDateTime` can represent both, which is precisely why\n * turning one into an `Instant` requires saying what to do about it.\n */\nexport interface PlainDateTime extends PlainDate {\n /** 0–23. */\n readonly hour: number;\n /** 0–59. */\n readonly minute: number;\n /** 0–59. */\n readonly second: number;\n /** 0–999. */\n readonly millisecond: number;\n}\n\n/**\n * How much to move, in CALENDAR units rather than elapsed time.\n *\n * THE DISTINCTION IS THE WHOLE POINT, and getting it wrong is the bug in every\n * library surveyed. \"One day later\" is not \"86,400,000 ms later\": across a\n * spring-forward boundary it is 23 hours, and across a fall-back it is 25. A\n * user dragging an event from Saturday to Sunday expects it to keep its\n * wall-clock time, so the move has to be expressed as `{ days: 1 }` and\n * resolved against the zone — never added as a number.\n *\n * `hours` and `minutes` ARE exact elapsed time, and that is correct: dragging\n * an event down by two hours means two hours of real duration.\n */\nexport interface CalendarDuration {\n readonly days?: number;\n readonly hours?: number;\n readonly minutes?: number;\n}\n\n/**\n * What to do when a wall-clock reading does not name exactly one instant.\n *\n * Spelled as `Temporal.ZonedDateTime.from`'s option of the same name, with the\n * same meanings, so this is one more thing that becomes a deletion later.\n *\n * - `compatible` — what every other date library does, and the default.\n * In a GAP (spring forward) move forward by the size of the gap; in an\n * OVERLAP (fall back) take the first of the two.\n * - `earlier` / `later` — take that side explicitly.\n * - `reject` — throw. For a parser or an import, where a time that never\n * existed is a fact about bad input rather than something to paper over.\n */\nexport type Disambiguation = \"compatible\" | \"earlier\" | \"later\" | \"reject\";\n\n/** Thrown by `reject` disambiguation, and never by anything else. */\nexport class AmbiguousTimeError extends Error {\n constructor(\n readonly wall: PlainDateTime,\n readonly zone: TimeZone,\n readonly kind: \"gap\" | \"overlap\",\n ) {\n super(\n kind === \"gap\"\n ? `${isoDateTimeString(wall)} does not exist in ${zone} — the clock skipped it.`\n : `${isoDateTimeString(wall)} happens twice in ${zone}.`,\n );\n this.name = \"AmbiguousTimeError\";\n }\n}\n\n/** `2026-03-08T02:30:00.000` — used in errors, and by the ISO helpers. */\nexport function isoDateTimeString(wall: PlainDateTime): string {\n return (\n `${pad(wall.year, 4)}-${pad(wall.month)}-${pad(wall.day)}` +\n `T${pad(wall.hour)}:${pad(wall.minute)}:${pad(wall.second)}.${pad(wall.millisecond, 3)}`\n );\n}\n\n/** `2026-03-08`. */\nexport function isoDateString(date: PlainDate): string {\n return `${pad(date.year, 4)}-${pad(date.month)}-${pad(date.day)}`;\n}\n","import { isoDateString, type PlainDate, type PlainDateTime } from \"./types\";\n\n/* ============================================================================\n CALENDAR ARITHMETIC — no zones, no instants, no offsets.\n\n A month grid is a question about the calendar, not about time: which squares\n does September have, which weekday does the 1st fall on, how many rows. None\n of that depends on a zone, and none of it should reach for one. Keeping this\n file zone-free is what stops a month grid quietly acquiring a dependency on\n the viewer's location.\n\n Every function here goes through UTC internally — NOT because these values\n are UTC (they are not; they have no zone at all) but because UTC is the only\n mode of `Date` with no transitions in it, so the arithmetic cannot be\n disturbed by one. A local-mode `Date` doing the same work would lose an hour\n twice a year and, at midnight-transition zones, land on the wrong DAY.\n ========================================================================= */\n\n/** Days in `month` of `year`, leap years included. */\nexport function daysInMonth(year: number, month: number): number {\n const d = new Date(0);\n d.setUTCFullYear(year, month, 0);\n return d.getUTCDate();\n}\n\nfunction fromUtc(d: Date): PlainDate {\n return { year: d.getUTCFullYear(), month: d.getUTCMonth() + 1, day: d.getUTCDate() };\n}\n\nfunction toUtc(date: PlainDate): Date {\n const d = new Date(0);\n d.setUTCFullYear(date.year, date.month - 1, date.day);\n return d;\n}\n\n/** `date` moved by `days`, which may be negative, rolling months and years. */\nexport function addDays(date: PlainDate, days: number): PlainDate {\n const d = toUtc(date);\n d.setUTCDate(d.getUTCDate() + days);\n return fromUtc(d);\n}\n\n/**\n * `date` moved by `months`, clamping the day to the target month's length.\n *\n * CLAMPING, NOT ROLLING: one month after the 31st of January is the 28th or\n * 29th of February, never the 3rd of March. Rolling is what `Date.setMonth`\n * does natively and it is why \"next month\" buttons skip February.\n */\nexport function addMonths(date: PlainDate, months: number): PlainDate {\n const total = date.year * 12 + (date.month - 1) + months;\n const year = Math.floor(total / 12);\n const month = (total % 12) + 1;\n return { year, month, day: Math.min(date.day, daysInMonth(year, month)) };\n}\n\n/** Whole days from `a` to `b`; negative when `b` is earlier. */\nexport function daysBetween(a: PlainDate, b: PlainDate): number {\n return Math.round((toUtc(b).getTime() - toUtc(a).getTime()) / 86_400_000);\n}\n\n/** Negative when `a` is earlier, 0 when equal, positive when later. */\nexport function comparePlainDate(a: PlainDate, b: PlainDate): number {\n if (a.year !== b.year) return a.year - b.year;\n if (a.month !== b.month) return a.month - b.month;\n return a.day - b.day;\n}\n\nexport function isSameDate(a: PlainDate, b: PlainDate): boolean {\n return comparePlainDate(a, b) === 0;\n}\n\n/** 0 = Sunday … 6 = Saturday, matching `Date.getUTCDay` and `Intl`'s weekday order. */\nexport function dayOfWeek(date: PlainDate): number {\n return toUtc(date).getUTCDay();\n}\n\n/**\n * The first day of the week `date` falls in.\n *\n * `weekStartsOn` IS A PARAMETER WITH NO DEFAULT ANYWHERE UP THE STACK, because\n * there is no correct global answer: Sunday in the US and Japan, Monday across\n * Europe and by ISO 8601, Saturday in much of the Middle East. A component that\n * picks one silently is wrong for most of the world, so the calendar surfaces\n * it and this function simply obeys.\n */\nexport function startOfWeek(date: PlainDate, weekStartsOn: number): PlainDate {\n const shift = (dayOfWeek(date) - weekStartsOn + 7) % 7;\n return addDays(date, -shift);\n}\n\n/** The 1st of `date`'s month. */\nexport function startOfMonth(date: PlainDate): PlainDate {\n return { year: date.year, month: date.month, day: 1 };\n}\n\n/** The last day of `date`'s month. */\nexport function endOfMonth(date: PlainDate): PlainDate {\n return { year: date.year, month: date.month, day: daysInMonth(date.year, date.month) };\n}\n\n/**\n * Every day a month grid shows, INCLUDING the leading and trailing days that\n * belong to the neighbouring months.\n *\n * Always whole weeks, so the result is a multiple of seven and the grid never\n * has a ragged edge. The count varies — a 28-day February beginning on the\n * week's first day needs four rows, and a 31-day month beginning on its last\n * day needs six. Callers that assume six rows draw an empty one eleven months\n * a year; callers that assume five are simply wrong twice a year.\n */\nexport function monthGridDays(date: PlainDate, weekStartsOn: number): PlainDate[] {\n const first = startOfWeek(startOfMonth(date), weekStartsOn);\n const lastOfMonth = endOfMonth(date);\n const last = addDays(startOfWeek(lastOfMonth, weekStartsOn), 6);\n const out: PlainDate[] = [];\n for (let d = first; comparePlainDate(d, last) <= 0; d = addDays(d, 1)) out.push(d);\n return out;\n}\n\n/** The seven days of the week `date` falls in. */\nexport function weekDays(date: PlainDate, weekStartsOn: number): PlainDate[] {\n const first = startOfWeek(date, weekStartsOn);\n return Array.from({ length: 7 }, (_, i) => addDays(first, i));\n}\n\n/**\n * Parse `YYYY-MM-DD`.\n *\n * FAILS LOUDLY on anything else, including the shapes `Date` accepts and\n * silently reinterprets. `new Date(\"2026-2-30\")` yields the 2nd of March, which\n * is a data error becoming a rendered event on the wrong day with nothing\n * anywhere reporting a problem.\n */\nexport function parsePlainDate(iso: string): PlainDate {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(iso);\n if (!m) throw new Error(`not an ISO date: ${JSON.stringify(iso)} — expected YYYY-MM-DD`);\n const year = Number(m[1]);\n const month = Number(m[2]);\n const day = Number(m[3]);\n if (month < 1 || month > 12) throw new Error(`month out of range in ${iso}`);\n if (day < 1 || day > daysInMonth(year, month)) {\n throw new Error(`${iso} is not a real date — ${year}-${month} has ${daysInMonth(year, month)} days`);\n }\n return { year, month, day };\n}\n\nexport { isoDateString };\n\n/** A `PlainDateTime` at midnight on `date` — the wall reading, not an instant. */\nexport function atMidnight(date: PlainDate): PlainDateTime {\n return { ...date, hour: 0, minute: 0, second: 0, millisecond: 0 };\n}\n\n/** A `PlainDateTime` at `hour`:`minute` on `date`. */\nexport function atTime(date: PlainDate, hour: number, minute = 0): PlainDateTime {\n return { ...date, hour, minute, second: 0, millisecond: 0 };\n}\n","/** The shared \"no value\" reading for a blank field — a date, a number, a\n * phone number, a currency amount: nothing recorded. Shared by every\n * formatter that reads absence this way, so the glyph itself never drifts\n * between them (DX-3254). `formatBoolean` does NOT use this — see its own\n * module doc for why a tri-state boolean reads absence as \"Unknown\"\n * instead. */\nexport const ABSENT = \"—\";\n","import { daysInMonth, isoDateString, parsePlainDate } from \"../time/plain\";\nimport type { PlainDate } from \"../time/types\";\nimport { pad } from \"../pad\";\nimport { ABSENT } from \"./absent\";\n\n/* ----------------------------------------------------------------------------\n A CALENDAR DATE — no time, no zone, just which square on the wall.\n\n Storage form is `YYYY-MM-DD`, the same ISO shape `lib/time` already parses\n and prints (`parsePlainDate` / `isoDateString`). That is not a coincidence —\n an accident date, a policy effective date, a date of birth are all exactly\n the `PlainDate` the calendar layer already models, and reusing its\n leap-year-correct arithmetic here is the whole point of `lib/time` existing\n as a shared layer instead of a calendar-component implementation detail.\n\n THE BUG THIS FILE CANNOT HAVE, BY CONSTRUCTION. The classic defect —\n `new Date(\"2026-09-11\")` parsed as UTC midnight, then printed in a reader\n west of Greenwich, silently showing \"09/10/2026\" — requires a `Date` object\n and an implicit zone somewhere in the path from string to string. Neither\n function below constructs one: `formatDate` reads `year`/`month`/`day` off\n the parsed `PlainDate` and pads them into a string by hand, and `parseDate`\n builds one the same way. There is no step at which a zone could enter.\n\n DISPLAY FORMAT: `MM/DD/YYYY`. Not `Intl`'s locale-formatted `\"Sep 11, 2026\"`\n — this is a fixed, non-localised convention, deliberately: the consuming\n application is a US legal/insurance workflow (demand letters, claim dates,\n policy dates) where every reader is the same audience and a 4-digit year\n with no ambiguity about which field is the day matters more than adapting\n to the viewer's own locale. `MM/DD/YYYY` is the format that convention\n already expects on a claim file, a police report, an EOB. It is also the\n one `parseDate` accepts back, so a value a person types after seeing this\n library's own display round-trips without translation.\n -------------------------------------------------------------------------- */\n\n\n/**\n * `\"2026-09-11\"` → `\"09/11/2026\"`.\n *\n * `null` / `undefined` / `\"\"` render as the em dash — no date entered, the\n * convention the rest of this package's formatters share (`formatTokens`,\n * `formatBoolean`'s \"Unknown\" is the one deliberate exception; see its own\n * docblock for why absence needs a different word there).\n *\n * A non-empty value that is NOT a valid `YYYY-MM-DD` date is returned\n * UNCHANGED rather than folded into the same em dash. Absence and corruption\n * are different facts — silently rendering a malformed stored value as \"no\n * date\" hides a data problem behind a blank field; showing the raw string\n * puts it in front of whoever is looking at the row.\n */\nexport function formatDate(value: string | null | undefined): string {\n if (value == null || value === \"\") return ABSENT;\n let date: PlainDate;\n try {\n date = parsePlainDate(value);\n } catch {\n return value;\n }\n return `${pad(date.month)}/${pad(date.day)}/${date.year}`;\n}\n\nconst US_FORMAT = /^(\\d{1,2})\\/(\\d{1,2})\\/(\\d{4})$/;\nconst ISO_FORMAT = /^(\\d{4})-(\\d{1,2})-(\\d{1,2})$/;\n\n/**\n * A human-typed date → the stored `YYYY-MM-DD`, or `null` if it is not one.\n *\n * Accepts `MM/DD/YYYY` (what `formatDate` prints, and what a person types\n * into a text field) and bare ISO `YYYY-MM-DD` (what a paste from another\n * system, or this library's own storage form, looks like) — nothing looser.\n * `Date`-style leniency (`new Date(\"2026-2-30\")` silently becoming the 2nd of\n * March) is exactly the failure mode `parsePlainDate`'s calendar-length check\n * exists to refuse, so this function refuses it too: `daysInMonth` bounds the\n * day before anything is accepted.\n *\n * Returns `null`, never throws — a form validating a field checks the return\n * value; it should not have to wrap every keystroke in `try`/`catch`.\n */\nexport function parseDate(input: string): string | null {\n const trimmed = input.trim();\n if (trimmed === \"\") return null;\n\n let year: number;\n let month: number;\n let day: number;\n\n const iso = ISO_FORMAT.exec(trimmed);\n if (iso) {\n year = Number(iso[1]);\n month = Number(iso[2]);\n day = Number(iso[3]);\n } else {\n const us = US_FORMAT.exec(trimmed);\n if (!us) return null;\n month = Number(us[1]);\n day = Number(us[2]);\n year = Number(us[3]);\n }\n\n if (month < 1 || month > 12) return null;\n if (day < 1 || day > daysInMonth(year, month)) return null;\n return isoDateString({ year, month, day });\n}\n","import {\n AmbiguousTimeError,\n type Disambiguation,\n type Instant,\n type PlainDate,\n type PlainDateTime,\n type TimeZone,\n} from \"./types\";\nimport { addDays } from \"./plain\";\n\n/* ============================================================================\n MOVING BETWEEN A WALL CLOCK AND THE TIMELINE.\n\n Everything here rests on ONE primitive — `offsetAt(zone, instant)` — read out\n of `Intl.DateTimeFormat`, which is the only IANA database a browser exposes.\n Everything else is arithmetic on top of it, so there is exactly one place\n where the rules of a zone enter this package.\n\n THE RULE THIS FILE EXISTS TO ENFORCE: a day is not 24 hours. It is 23 in\n New York on 2026-03-08, 25 on 2026-11-01, 23.5 in Lord Howe on 2026-10-04\n (its shift is THIRTY MINUTES, so any branch on \"is the delta 3600000\" is\n already wrong), and in Santiago on 2026-09-06 it is 23 hours that begin at\n 01:00 — `startOfDay` is NOT always midnight. Every one of those was measured\n before this file was written, and all five are in the test corpus.\n\n Kendo's scheduler crashed because two of its own methods disagreed about how\n long a day was; Mozilla's calendar drew an event an hour out plus a phantom\n row; react-big-calendar still shifts an entire week's columns because it\n derives them by adding `n * 86400000`. The shape of all three is the same:\n somewhere, a day was assumed.\n ========================================================================= */\n\nconst HOUR_MS = 3_600_000;\nconst MINUTE_MS = 60_000;\n\n/* Constructing an `Intl.DateTimeFormat` is expensive — tens of microseconds —\n and a month view resolves thousands of instants. The formatter is stateless\n once built, so one per zone is safe to keep forever. */\nconst formatters = new Map<TimeZone, Intl.DateTimeFormat>();\n\nfunction formatterFor(zone: TimeZone): Intl.DateTimeFormat {\n let f = formatters.get(zone);\n if (!f) {\n f = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: zone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n /* `h23` EXPLICITLY, not `hour12: false`. Several ICU versions render\n midnight as hour \"24\" under `hour12: false`, which parses as a valid\n number and silently lands the reading on the wrong day. */\n hourCycle: \"h23\",\n });\n formatters.set(zone, f);\n }\n return f;\n}\n\n/** The wall-clock reading in `zone` at `instant`. */\nexport function instantToPlainDateTime(zone: TimeZone, instant: Instant): PlainDateTime {\n const parts = formatterFor(zone).formatToParts(new Date(instant));\n let year = 0;\n let month = 1;\n let day = 1;\n let hour = 0;\n let minute = 0;\n let second = 0;\n for (const part of parts) {\n switch (part.type) {\n case \"year\":\n year = Number(part.value);\n break;\n case \"month\":\n month = Number(part.value);\n break;\n case \"day\":\n day = Number(part.value);\n break;\n case \"hour\":\n hour = Number(part.value);\n break;\n case \"minute\":\n minute = Number(part.value);\n break;\n case \"second\":\n second = Number(part.value);\n break;\n }\n }\n /* Sub-second precision passes straight through: every offset in the IANA\n database since 1972 is a whole number of minutes, so a zone shift can never\n move the millisecond field. */\n const millisecond = ((instant % 1000) + 1000) % 1000;\n return { year, month, day, hour, minute, second, millisecond };\n}\n\n/**\n * The same wall-clock fields, but as though they were UTC.\n *\n * `Date.UTC` is NOT used directly anywhere in this file: it maps years 0–99\n * onto 1900–1999, so a year-40 date silently becomes 1940. `setUTCFullYear` has\n * no such rule. The calendar will never render year 40, but a helper that is\n * wrong for an input it never receives is a trap left for whoever reuses it.\n */\nfunction wallAsUtcMs(wall: PlainDateTime): number {\n const d = new Date(0);\n d.setUTCFullYear(wall.year, wall.month - 1, wall.day);\n d.setUTCHours(wall.hour, wall.minute, wall.second, wall.millisecond);\n return d.getTime();\n}\n\n/**\n * `zone`'s offset from UTC at `instant`, in milliseconds. East is positive.\n *\n * THE ONE PRIMITIVE. Read by asking the browser what the wall clock says at\n * that instant and subtracting: if it is 07:00 UTC and the clock in New York\n * reads 02:00, the offset is −5 hours. No table, no parsing of a zone name, no\n * assumption that an offset is a whole number of hours — `Pacific/Chatham` sits\n * at +12:45, and `Asia/Kathmandu` at +5:45.\n */\nexport function offsetAt(zone: TimeZone, instant: Instant): number {\n return wallAsUtcMs(instantToPlainDateTime(zone, instant)) - instant;\n}\n\nfunction sameWall(a: PlainDateTime, b: PlainDateTime): boolean {\n return (\n a.year === b.year &&\n a.month === b.month &&\n a.day === b.day &&\n a.hour === b.hour &&\n a.minute === b.minute &&\n a.second === b.second &&\n a.millisecond === b.millisecond\n );\n}\n\n/**\n * Every instant at which `zone`'s clock reads exactly `wall` — zero, one, or\n * two of them, in ascending order.\n *\n * TWO CANDIDATES, BOTH VERIFIED, WHICH IS THE PART THAT IS EASY TO GET WRONG.\n * The naive method — take the offset at a guessed instant and subtract it — is\n * correct in the ordinary case and wrong at exactly the boundaries this\n * component exists to handle. So both plausible offsets are tried, and each\n * candidate is accepted ONLY if reading the clock back at it reproduces the\n * requested wall time. That round-trip is what distinguishes a real reading\n * from one the clock skipped.\n *\n * two results → an OVERLAP; the clock struck this time twice (fall back)\n * one result → the ordinary case\n * none → a GAP; the clock never struck it at all (spring forward)\n */\nfunction instantsFor(zone: TimeZone, wall: PlainDateTime): Instant[] {\n const asUtc = wallAsUtcMs(wall);\n const offsetBefore = offsetAt(zone, asUtc - 24 * HOUR_MS);\n const offsetAfter = offsetAt(zone, asUtc + 24 * HOUR_MS);\n\n const found: Instant[] = [];\n for (const offset of offsetBefore === offsetAfter ? [offsetBefore] : [offsetBefore, offsetAfter]) {\n const candidate = asUtc - offset;\n if (sameWall(instantToPlainDateTime(zone, candidate), wall)) found.push(candidate);\n }\n found.sort((a, b) => a - b);\n /* A zone can in principle be sampled twice at the same offset either side of\n a transition that returns to where it started; de-duplicate rather than\n report one instant as two. */\n return found.filter((t, i) => i === 0 || t !== found[i - 1]);\n}\n\n/**\n * The instant at which `zone`'s clock reads `wall`.\n *\n * See `Disambiguation` for what happens when that is not exactly one instant.\n * The default is `compatible`, which is what `Temporal` and every other date\n * library do, so the surprising cases are the ones you have to ask for.\n */\nexport function plainDateTimeToInstant(\n zone: TimeZone,\n wall: PlainDateTime,\n disambiguation: Disambiguation = \"compatible\",\n): Instant {\n const candidates = instantsFor(zone, wall);\n\n if (candidates.length === 1) return candidates[0];\n\n if (candidates.length > 1) {\n /* OVERLAP — the hour ran twice. */\n if (disambiguation === \"reject\") throw new AmbiguousTimeError(wall, zone, \"overlap\");\n if (disambiguation === \"later\") return candidates[candidates.length - 1];\n /* `compatible` takes the earlier, matching Temporal. */\n return candidates[0];\n }\n\n /* GAP — the clock skipped this reading entirely. */\n if (disambiguation === \"reject\") throw new AmbiguousTimeError(wall, zone, \"gap\");\n\n const asUtc = wallAsUtcMs(wall);\n const offsetBefore = offsetAt(zone, asUtc - 24 * HOUR_MS);\n const offsetAfter = offsetAt(zone, asUtc + 24 * HOUR_MS);\n const gap = offsetAfter - offsetBefore;\n\n /* Both of these are real instants; they differ by the size of the gap. Using\n the PRE-transition offset lands before the jump, the post-transition offset\n lands after it. `compatible` shifts FORWARD by the gap — the same choice\n Temporal makes, and the one that matches what a person means by \"07:30\" on\n a morning when 07:30 did not happen. */\n const earlier = asUtc - offsetBefore;\n const later = asUtc - offsetAfter;\n if (disambiguation === \"earlier\") return Math.min(earlier, later);\n return gap > 0 ? Math.max(earlier, later) : Math.min(earlier, later);\n}\n\n/** The calendar day `instant` falls on, in `zone`. */\nexport function instantToPlainDate(zone: TimeZone, instant: Instant): PlainDate {\n const wall = instantToPlainDateTime(zone, instant);\n return { year: wall.year, month: wall.month, day: wall.day };\n}\n\n/**\n * The first instant of `date` in `zone`.\n *\n * NOT \"midnight\". In `America/Santiago` on 2026-09-06 the clock jumps from\n * 00:00 to 01:00, so the day's first instant reads 01:00 — and a grid that\n * assumes its own origin is midnight puts every event on that day an hour out.\n * `America/Havana` and `Asia/Beirut` do the same thing on their own dates.\n *\n * Implemented by asking for midnight and, when the clock skipped it, walking\n * forward a minute at a time until a reading exists. The walk is bounded: no\n * transition in the IANA database exceeds two hours, so 180 iterations cannot\n * be reached by real data, and the bound exists to fail loudly rather than spin\n * if that ever stops being true.\n */\nexport function startOfDay(zone: TimeZone, date: PlainDate): Instant {\n const midnight: PlainDateTime = { ...date, hour: 0, minute: 0, second: 0, millisecond: 0 };\n const exact = instantsFor(zone, midnight);\n /* An OVERLAP at midnight means the day started twice; the day begins at the\n first of them, not the second. */\n if (exact.length > 0) return exact[0];\n\n for (let minutes = 1; minutes <= 180; minutes++) {\n const candidate = instantsFor(zone, {\n ...date,\n hour: Math.floor(minutes / 60),\n minute: minutes % 60,\n second: 0,\n millisecond: 0,\n });\n if (candidate.length > 0) return candidate[0];\n }\n throw new Error(\n `startOfDay: no instant exists in the first three hours of ${date.year}-${date.month}-${date.day} in ${zone}`,\n );\n}\n\n/** The day after `date` on the calendar, ignoring zones entirely. */\nexport function nextDay(date: PlainDate): PlainDate {\n return addDays(date, 1);\n}\n\n/**\n * How long `date` actually lasts in `zone`, in milliseconds.\n *\n * Measured as the distance between one day's first instant and the next day's,\n * which is true by construction for every case — 23 hours, 25 hours, the 30\n * minutes of Lord Howe, and a day that starts at 01:00 — with no special case\n * for any of them. That is the entire reason positions in the grid are a RATIO\n * of this number rather than `hour × rowHeight`.\n */\nexport function dayLengthMs(zone: TimeZone, date: PlainDate): number {\n return startOfDay(zone, nextDay(date)) - startOfDay(zone, date);\n}\n\n/**\n * How many hours `date` lasts in `zone` — 23, 24, 25, or 23.5.\n *\n * Fractional by design. `Australia/Lord_Howe` shifts by thirty minutes, so a\n * signature returning an integer would be lying about a real zone, and any\n * caller branching on `=== 23` is already broken.\n */\nexport function hoursInDay(zone: TimeZone, date: PlainDate): number {\n return dayLengthMs(zone, date) / HOUR_MS;\n}\n\n/**\n * Where `instant` sits within `date`, as a fraction from 0 to 1.\n *\n * THE LOAD-BEARING FUNCTION. A 23-hour day compresses and a 25-hour day\n * stretches with no branch anywhere, because the divisor is the day's real\n * length. Values outside 0–1 mean the instant is not on this day and are\n * returned rather than clamped — the layout core needs to know by how much in\n * order to clip a multi-day span.\n */\nexport function dayFraction(zone: TimeZone, date: PlainDate, instant: Instant): number {\n const from = startOfDay(zone, date);\n return (instant - from) / dayLengthMs(zone, date);\n}\n\n/**\n * The exact inverse of `dayFraction`.\n *\n * IT MUST BE EXACT, and that is not a stylistic preference. Rendering maps an\n * instant to a position and hit-testing maps a position back to an instant; if\n * the two disagree by even a rounding step, an event lands one slot away from\n * where it was dropped. FullCalendar's Australia selection bug is exactly this\n * pair drifting apart. `dayFraction(z, d, instantAtFraction(z, d, f)) === f`\n * for every zone in the corpus, and there is a test that says so.\n */\nexport function instantAtFraction(zone: TimeZone, date: PlainDate, fraction: number): Instant {\n return startOfDay(zone, date) + fraction * dayLengthMs(zone, date);\n}\n\n/**\n * Move `instant` by a CALENDAR duration.\n *\n * Days are applied on the calendar and then re-anchored to the same wall-clock\n * time, so an event dragged from Saturday to Sunday across a transition keeps\n * reading 09:00 rather than becoming 08:00 or 10:00. Hours and minutes are\n * applied afterwards as exact elapsed time, because dragging an event down the\n * grid by two hours does mean two hours.\n *\n * ORDER MATTERS AND IS FIXED: days first, then time. The other order makes\n * \"one day and two hours later\" depend on which side of a transition the\n * intermediate value happened to land on.\n */\nexport function addDuration(\n zone: TimeZone,\n instant: Instant,\n duration: { days?: number; hours?: number; minutes?: number },\n disambiguation: Disambiguation = \"compatible\",\n): Instant {\n const { days = 0, hours = 0, minutes = 0 } = duration;\n\n let result = instant;\n if (days !== 0) {\n const wall = instantToPlainDateTime(zone, instant);\n const shifted = new Date(0);\n shifted.setUTCFullYear(wall.year, wall.month - 1, wall.day + days);\n result = plainDateTimeToInstant(\n zone,\n {\n year: shifted.getUTCFullYear(),\n month: shifted.getUTCMonth() + 1,\n day: shifted.getUTCDate(),\n hour: wall.hour,\n minute: wall.minute,\n second: wall.second,\n millisecond: wall.millisecond,\n },\n disambiguation,\n );\n }\n return result + hours * HOUR_MS + minutes * MINUTE_MS;\n}\n","import { instantToPlainDateTime, plainDateTimeToInstant } from \"../time/zone\";\nimport { daysInMonth } from \"../time/plain\";\nimport type { PlainDateTime } from \"../time/types\";\nimport { pad } from \"../pad\";\nimport { ABSENT } from \"./absent\";\n\n/* ----------------------------------------------------------------------------\n AN INSTANT — a fixed point on the timeline, rendered through a zone.\n\n THE DISTINCTION FROM `date.ts`, AND WHY IT IS A SEPARATE FILE RATHER THAN AN\n OPTION ON THE SAME ONE. An accident date is a calendar square: everyone who\n reads a claim file agrees it happened on the 11th, regardless of where they\n are sitting. \"When this record was created\" is not — it is one instant, and\n a reader in Los Angeles and a reader in New York see it at different clock\n times on (near a midnight boundary) potentially different DAYS. Collapsing\n the two into one formatter with a \"is this a date-time\" flag is how a\n calendar date ends up accidentally zone-shifted, which is the exact defect\n `date.ts`'s docblock explains `formatDate` cannot have. Keeping them as two\n files with two contracts is what keeps that true.\n\n STORAGE FORM is an ISO instant string (`\"2026-09-11T19:18:43.000Z\"`,\n `\"...43.000000Z\"` — microsecond precision, as a Laravel/Carbon backend\n emits, is accepted and truncated to milliseconds, see `toInstant` below) or\n a raw epoch-millisecond number — `lib/time`'s own `Instant`. Either is\n accepted so a consumer does not have to convert before handing a value in.\n\n DISPLAY FORMAT is `MM/DD/YYYY, h:mm AM/PM` — the SAME `MM/DD/YYYY` `date.ts`\n uses, not `Intl`'s localised `\"Sep 11, 2026, 1:18 PM\"`. Two reasons, not\n one: it keeps a date and a date-time reading consistently across a table\n that shows both, and it means `parseDateTime` can invert exactly what\n `formatDateTime` prints without parsing a localised month name back out of\n a string — the format was chosen so the two functions agree with each\n other, not just with a plausible-looking example.\n\n THE ZONE CONVERSION ITSELF IS NOT REIMPLEMENTED HERE. `instantToPlainDateTime`\n and `plainDateTimeToInstant` are `lib/time/zone.ts`'s own primitives, proven\n against a five-zone DST corpus (`time-dst.test.ts`) that includes half-hour\n offsets, midnight-skipping transitions and hours that happen twice. Writing\n a second, ad-hoc offset calculation here — the way a naive datetime\n formatter usually does — is exactly how the bugs that corpus documents get\n reintroduced a second time in the same codebase.\n -------------------------------------------------------------------------- */\n\n/** The reader's own zone, per the host `Intl` implementation — the correct\n * default for an INSTANT, which (unlike a `PlainDate`) has no zone of its\n * own until someone reads it. */\nfunction localZone(): string {\n return Intl.DateTimeFormat().resolvedOptions().timeZone;\n}\n\n/**\n * A stored instant (ISO string or epoch ms) → epoch milliseconds, or `null`.\n *\n * Microsecond-precision ISO strings (`.043210Z`, six fractional digits — what\n * Carbon's default JSON cast emits) are truncated to the three digits\n * `Date.parse` is spec-guaranteed to accept; V8 happens to be lenient with\n * more, but nothing requires every engine this bundles into to be.\n */\nfunction toInstant(value: string | number): number | null {\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n const normalized = value.replace(/(\\.\\d{3})\\d+/, \"$1\");\n const ms = Date.parse(normalized);\n return Number.isNaN(ms) ? null : ms;\n}\n\nexport interface FormatDateTimeOptions {\n /**\n * IANA zone to render in, e.g. `\"America/New_York\"`. Omit to use the\n * reader's own zone — the right default for an instant, and the reason\n * this is a parameter at all rather than always-local: a report emailed to\n * someone in a different zone, or a server rendering a PDF, needs to say\n * WHOSE clock the printed time is on.\n */\n zone?: string;\n}\n\n/**\n * A stored instant → `\"09/11/2026, 1:18 PM\"` in `options.zone` (default: the\n * reader's own).\n *\n * `null` / `undefined` / `\"\"` render as the em dash. A non-empty value that\n * does not parse as an instant is returned UNCHANGED — see `date.ts`'s\n * docblock for why absence and corruption get different treatment.\n */\nexport function formatDateTime(value: string | number | null | undefined, options: FormatDateTimeOptions = {}): string {\n if (value == null || value === \"\") return ABSENT;\n const instant = toInstant(value);\n if (instant === null) return String(value);\n\n const zone = options.zone ?? localZone();\n const wall = instantToPlainDateTime(zone, instant);\n const hour12 = ((wall.hour + 11) % 12) + 1;\n const meridiem = wall.hour < 12 ? \"AM\" : \"PM\";\n return `${pad(wall.month)}/${pad(wall.day)}/${wall.year}, ${hour12}:${pad(wall.minute)} ${meridiem}`;\n}\n\n/* `MM/DD/YYYY, h:mm[:ss] AM/PM` — what `formatDateTime` prints, plus optional\n seconds for a value pasted from somewhere more precise. */\nconst TYPED_FORMAT =\n /^(\\d{1,2})\\/(\\d{1,2})\\/(\\d{4})(?:,?\\s+(\\d{1,2}):(\\d{2})(?::(\\d{2}))?\\s*([AaPp][Mm]))?$/;\n\n/* An ISO instant carrying its own offset or `Z` — unambiguous on its own, so\n it never needs a `zone` argument to resolve. */\nconst ISO_WITH_ZONE = /^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?\\s*(Z|[+-]\\d{2}:?\\d{2})$/;\n\nexport interface ParseDateTimeOptions {\n /**\n * The zone a bare (no AM/PM-less midnight, no offset) typed value's\n * wall-clock reading is IN. Omit to use the reader's own zone — matching\n * `formatDateTime`'s default, so parsing a value typed against that display\n * uses the same zone it was rendered in.\n */\n zone?: string;\n}\n\n/**\n * A human-typed date-time → the stored ISO instant, or `null`.\n *\n * TWO PATHS, DELIBERATELY NOT ONE. An ISO string carrying its own offset or\n * `Z` is unambiguous — `Date.parse` resolves it correctly regardless of what\n * zone this function is running in, and is used directly. Everything else is\n * a WALL-CLOCK reading with no zone of its own (`\"09/11/2026, 1:18 PM\"`), and\n * `Date.parse`'s native handling of that shape resolves it against the\n * HOST's zone — which is wrong the instant `options.zone` names a different\n * one, e.g. a server in UTC parsing a value entered by a reader in\n * `America/New_York`. So the wall-clock path is hand-parsed into fields and\n * handed to `plainDateTimeToInstant`, which resolves it against the\n * REQUESTED zone rather than whatever zone the code happens to be running in.\n *\n * A wall-clock reading that lands in a DST gap or overlap does not throw —\n * `plainDateTimeToInstant`'s default (`\"compatible\"`) disambiguation applies,\n * matching every other date library's default and `Temporal`'s own. See\n * `time-dst.test.ts` for what that resolves to at a transition.\n */\nexport function parseDateTime(input: string, options: ParseDateTimeOptions = {}): string | null {\n const trimmed = input.trim();\n if (trimmed === \"\") return null;\n\n if (ISO_WITH_ZONE.test(trimmed)) {\n const ms = toInstant(trimmed);\n return ms === null ? null : new Date(ms).toISOString();\n }\n\n const m = TYPED_FORMAT.exec(trimmed);\n if (!m) return null;\n\n const month = Number(m[1]);\n const day = Number(m[2]);\n const year = Number(m[3]);\n if (month < 1 || month > 12) return null;\n if (day < 1 || day > daysInMonth(year, month)) return null;\n\n let hour = 0;\n let minute = 0;\n let second = 0;\n if (m[4] !== undefined) {\n const rawHour = Number(m[4]);\n minute = Number(m[5]);\n second = m[6] !== undefined ? Number(m[6]) : 0;\n const meridiem = m[7]!.toUpperCase();\n if (rawHour < 1 || rawHour > 12 || minute > 59 || second > 59) return null;\n hour = meridiem === \"AM\" ? (rawHour === 12 ? 0 : rawHour) : rawHour === 12 ? 12 : rawHour + 12;\n }\n\n const wall: PlainDateTime = { year, month, day, hour, minute, second, millisecond: 0 };\n const zone = options.zone ?? localZone();\n return new Date(plainDateTimeToInstant(zone, wall)).toISOString();\n}\n","/* ----------------------------------------------------------------------------\n USD MONEY.\n\n ALWAYS TWO DECIMAL PLACES, regardless of magnitude — the opposite choice\n from `formatBytes`/`formatTokens`, and deliberately so. Those two round MORE\n aggressively as a number grows because the tenth of a KB or the exact token\n count stop being digits anyone acts on at scale. A dollar figure never\n reaches that point: the gap between $1,234,567.89 and $1,234,568.00 is\n eighty-nine real cents on an invoice or a settlement figure, not noise, no\n matter how many digits come before the decimal point. So precision here is\n fixed, not magnitude-scaled.\n\n ZERO IS NOT ABSENCE. `formatCurrency(0)` is `\"$0.00\"` — a real, recorded\n amount, e.g. a fully-waived fee or a $0 deductible. `null`/`undefined` is\n \"no figure was entered\", rendered as the em dash this package's other\n formatters use for the same fact. Collapsing the two (rendering 0 as \"—\",\n or treating \"—\" as 0 in a sum) is the classic spreadsheet bug this\n distinction exists to prevent.\n -------------------------------------------------------------------------- */\n\nimport { ABSENT } from \"./absent\";\n\nconst USD = new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: \"USD\",\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n});\n\n/**\n * `1234.5` → `\"$1,234.50\"`. `null` / `undefined` → `\"—\"`. `0` → `\"$0.00\"`.\n *\n * `NaN` / `Infinity` are not real amounts either and render as the em dash —\n * grouped with absence rather than with a malformed string, because unlike\n * `formatDate`'s malformed-input case, there is no ORIGINAL\n * user-facing string to show back: the caller already has a `number`, and a\n * non-finite one is a computation defect upstream, not a typo to surface.\n */\nexport function formatCurrency(value: number | null | undefined): string {\n if (value == null || !Number.isFinite(value)) return ABSENT;\n return USD.format(value);\n}\n\n/**\n * A human-typed amount → a `number` in dollars, or `null`.\n *\n * Tolerates the punctuation someone actually types or pastes: a leading `$`,\n * thousands commas, surrounding whitespace, and ACCOUNTING-style negatives —\n * `\"($1,234.56)\"` for `-1234.56`, the convention every US invoice and EOB\n * already uses for a credit or a refund. A leading `-` is accepted too.\n * Returns `null` — never `NaN`, never a silent `0` — for anything that is not\n * recognizably a number once that punctuation is stripped, so a caller can\n * reject a bad edit instead of storing garbage.\n */\nexport function parseCurrency(input: string): number | null {\n const trimmed = input.trim();\n if (trimmed === \"\") return null;\n\n const accounting = /^\\((.*)\\)$/.exec(trimmed);\n const negative = accounting !== null;\n const body = accounting ? accounting[1] : trimmed;\n\n const stripped = body.replace(/[$,\\s]/g, \"\");\n if (!/^-?\\d+(\\.\\d+)?$/.test(stripped)) return null;\n\n const magnitude = Number(stripped);\n if (!Number.isFinite(magnitude)) return null;\n return negative ? -Math.abs(magnitude) : magnitude;\n}\n","/* ----------------------------------------------------------------------------\n A GENERIC NUMBER — a float or an integer, with thousands separators.\n\n NO FIXED PRECISION BY DEFAULT, unlike `formatCurrency`'s always-two-decimals\n rule. Money has a currency-defined minor unit, so \"how many decimals\" is\n never a real question; a generic number's field might be a whole count\n of pages, a percentage to one decimal, or a computed rate to four — there is\n no single right answer, so the caller states it (`decimals`) and the\n default preserves whatever precision the value actually carries (up to 6\n fraction digits, trimmed of trailing zeros) rather than inventing digits\n that were not there or discarding real ones.\n -------------------------------------------------------------------------- */\n\nimport { ABSENT } from \"./absent\";\n\nexport interface FormatNumberOptions {\n /**\n * Fixed fraction digits, e.g. `1` for a percentage rendered as `\"42.0\"`.\n * Omit to show the value's own precision, up to 6 fraction digits, with no\n * trailing zeros invented.\n */\n decimals?: number;\n}\n\n/**\n * `1234.5` → `\"1,234.5\"`. `null` / `undefined` → `\"—\"`.\n *\n * `NaN` / `Infinity` render as the em dash too, for the same reason\n * `formatCurrency` groups them with absence rather than showing them back:\n * the caller holds a `number`, not a string a person typed, so there is no\n * original text to preserve.\n */\nexport function formatNumber(value: number | null | undefined, options: FormatNumberOptions = {}): string {\n if (value == null || !Number.isFinite(value)) return ABSENT;\n const { decimals } = options;\n const intlOptions: Intl.NumberFormatOptions =\n decimals === undefined\n ? { maximumFractionDigits: 6 }\n : { minimumFractionDigits: decimals, maximumFractionDigits: decimals };\n return new Intl.NumberFormat(\"en-US\", intlOptions).format(value);\n}\n\n/**\n * A human-typed number → a `number`, or `null`.\n *\n * Strips thousands-separator commas (`\"1,234.5\"` → `1234.5`) before parsing,\n * and accepts a leading `-` for a negative value. Rejects, rather than\n * silently truncating, anything `parseFloat` would otherwise accept a PREFIX\n * of — `\"12abc\"` is not twelve, it is a typo, and `parseFloat` alone would\n * return `12` and hide that.\n */\nexport function parseNumber(input: string): number | null {\n const trimmed = input.trim();\n if (trimmed === \"\") return null;\n const stripped = trimmed.replace(/,/g, \"\");\n if (!/^-?\\d+(\\.\\d+)?$/.test(stripped)) return null;\n const value = Number(stripped);\n return Number.isFinite(value) ? value : null;\n}\n","/* ----------------------------------------------------------------------------\n A TRI-STATE BOOLEAN — `true`, `false`, or nothing recorded.\n\n `\"Unknown\"`, NOT THE EM DASH, is the default for `null`/`undefined` here —\n the one deliberate departure from the convention every other formatter in\n this file follows. The em dash reads correctly as \"no value\" for a date, a\n number, a phone number: a blank field on a form. A boolean field usually\n means something was actually ASKED — \"was the patient transported by\n ambulance,\" \"is there a police report\" — and `null` there means the\n question has not been answered yet, which is a materially different fact\n from \"answered no.\" An em dash sits visually closer to \"false-ish\" than\n \"unanswered\" does; \"Unknown\" does not. `unknownLabel` exists to opt back\n into the em dash (or anything else) for a dense table where every column\n already uses it and a differently-worded cell would stand out for the\n wrong reason — the default is chosen for a single tri-state field read on\n its own, not for a grid.\n -------------------------------------------------------------------------- */\n\nexport interface FormatBooleanOptions {\n /** Word for `true`. Default `\"Yes\"`. */\n trueLabel?: string;\n /** Word for `false`. Default `\"No\"`. */\n falseLabel?: string;\n /** Word for `null` / `undefined`. Default `\"Unknown\"` — see the module doc\n * for why this is not the em dash the rest of this package uses for\n * absence. */\n unknownLabel?: string;\n}\n\n/**\n * `true` → `\"Yes\"`, `false` → `\"No\"`, `null` / `undefined` → `\"Unknown\"`\n * (all three overridable via `options`).\n */\nexport function formatBoolean(value: boolean | null | undefined, options: FormatBooleanOptions = {}): string {\n const { trueLabel = \"Yes\", falseLabel = \"No\", unknownLabel = \"Unknown\" } = options;\n if (value === true) return trueLabel;\n if (value === false) return falseLabel;\n return unknownLabel;\n}\n\nconst TRUE_WORDS = new Set([\"yes\", \"y\", \"true\", \"t\", \"1\"]);\nconst FALSE_WORDS = new Set([\"no\", \"n\", \"false\", \"f\", \"0\"]);\n\n/**\n * A human-typed word → `true`, `false`, or `null` for anything else\n * (including an empty string — a caller cannot distinguish \"typed nothing\"\n * from \"typed a real value\" if both return `false`).\n *\n * Matches case-insensitively against the common spellings a person actually\n * types into a Yes/No field: `yes`/`y`/`true`/`t`/`1` and `no`/`n`/`false`/\n * `f`/`0`. Deliberately does NOT guess at anything outside that list — a\n * typo like `\"yse\"` returning `null` for a caller to reject is correct;\n * returning `false` for it (or any other unrecognised text) would silently\n * record the opposite of an unreadable answer.\n */\nexport function parseBoolean(input: string): boolean | null {\n const normalized = input.trim().toLowerCase();\n if (TRUE_WORDS.has(normalized)) return true;\n if (FALSE_WORDS.has(normalized)) return false;\n return null;\n}\n","/* ----------------------------------------------------------------------------\n THE FIVE-STEP CONFIDENCE SCALE, AS DATA — how a 1-5 rating READS.\n\n React-free on purpose (it is exported from `@danxbot/ui/pure`), because not\n every surface that draws a rating is a React consumer of this library: the\n demand letter's verification overlay is a Vue package, and it bundles these\n from here rather than keeping its own copy of five words and a rounding rule.\n Its colours come the same way, from `dist/confidence.css`. One scale, one\n source — a second copy is how a letter and the screen beside it came to\n disagree about what a 4 is (SG-843 / SG-901).\n -------------------------------------------------------------------------- */\n\n/** One of the five steps of the confidence scale — see `ConfidenceBar` for how each is drawn. */\nexport type ConfidenceLevel = 1 | 2 | 3 | 4 | 5;\n\n/**\n * A RATING to one of the five confidence steps, or null when there is no\n * rating on a real scale to draw.\n *\n * A five-point rating maps one-to-one. Any other scale is mapped by its\n * fraction, so a colour and a length keep one meaning whatever scale a consumer\n * rates on. A rating below one step is \"not rated\", never level 1: a zero is\n * the absence of a rating, and drawing it as the lowest earned level would\n * claim a judgement nobody made.\n *\n * The fraction feeds a COLOUR and a LENGTH, never a printed figure — a step is\n * a judgement five values wide and survives the trip; a percentage is a figure\n * and does not.\n */\nexport function confidenceLevel(rating: number, scale: number): ConfidenceLevel | null {\n if (!Number.isFinite(rating) || !Number.isFinite(scale) || scale <= 0 || rating <= 0) return null;\n const step = Math.round((Math.min(rating, scale) / scale) * 5);\n return Math.max(1, Math.min(5, step)) as ConfidenceLevel;\n}\n\n/**\n * Each level's own word, the operator's scale (SG-843): 4 is \"Confident\" and 5\n * is \"Highly confident\", so the two readings of \"fine\" stay distinguishable in\n * words exactly as they are in colour and length.\n */\nexport const CONFIDENCE_LEVEL_LABELS: Readonly<Record<ConfidenceLevel, string>> = {\n 1: \"Just a guess\",\n 2: \"Somewhat unsure\",\n 3: \"Fairly confident\",\n 4: \"Confident\",\n 5: \"Highly confident\",\n};\n","/* ----------------------------------------------------------------------------\n THE CHART PALETTE, AS DATA (DX-3963) — the token names any chart reads.\n\n The values live in `styles/chart-palette.css`, one set per theme and per\n contrast level; this module is only the NAMES, so a chart component, a\n consumer's own SVG and a test all say `chartSeriesColor(2)` instead of each\n spelling `var(--chart-3)` by hand. React-free on purpose (exported from\n `@danxbot/ui/pure`): a server-rendered report or a non-React surface reads\n the same names.\n -------------------------------------------------------------------------- */\n\n/** How many categorical hues exist. A 9th series is never a 9th hue: it folds into \"Other\". */\nexport const CHART_SERIES_COUNT = 8;\n\n/** The eight categorical tokens, in the fixed order series are assigned. */\nexport const CHART_SERIES_TOKENS = [\n \"--chart-1\",\n \"--chart-2\",\n \"--chart-3\",\n \"--chart-4\",\n \"--chart-5\",\n \"--chart-6\",\n \"--chart-7\",\n \"--chart-8\",\n] as const;\n\n/** The one sequential hue: a single series of magnitudes (a ranked bar list). */\nexport const CHART_SEQUENTIAL_TOKEN = \"--chart-seq\" as const;\n\n/**\n * The CSS colour for the series at zero-based `index` — `var(--chart-1)` for\n * index 0. Throws past the eighth: a ninth series is folded into \"Other\" by\n * the caller, never given a generated hue.\n */\nexport function chartSeriesColor(index: number): string {\n const token = CHART_SERIES_TOKENS[index];\n if (!Number.isInteger(index) || token === undefined) {\n throw new Error(\n `chartSeriesColor: there are ${CHART_SERIES_COUNT} categorical hues (index 0-${CHART_SERIES_COUNT - 1}); got ${index}. Fold the rest into \"Other\".`,\n );\n }\n return `var(${token})`;\n}\n\n/** The CSS colour of the sequential hue. */\nexport const CHART_SEQUENTIAL_COLOR = `var(${CHART_SEQUENTIAL_TOKEN})`;\n","/**\n * The vocabulary of the document-level display preferences, and the one way a\n * value is written onto `<html>`.\n *\n * DX-4075: REACT-FREE ON PURPOSE. `preferences.ts` holds the hooks, so it imports\n * React; `@danxbot/ui/pure` may not reach React (`pure-entry.test.ts`). The framed\n * side of `SandboxedFrame` (`designFrameClient`) has to apply the host's\n * appearance with the very same write the host used and accept exactly the same\n * values, so the unions, the value lists and `applyPreference` live here, imported\n * by both. Two copies would be two chances for the host and the frame to drift.\n */\n\n/**\n * Writes one preference onto `<html>` with document-wide motion suspended for\n * the duration of the change.\n *\n * WHY THIS IS NOT JUST `setAttribute`. Colour transitions are a foundation in\n * this system: every element has one. That is right for an interaction — a\n * control easing as you hover it — and wrong for a preference, because\n * switching theme changes the computed colour of EVERY element at once.\n * Measured on this demo: 2,360 transitions started simultaneously and the page\n * took **957ms** to settle, 1,560ms on the chat route, for a token whose\n * duration is 110ms. A theme switch should be a cut, not a dissolve; what it\n * actually looked like was the whole document struggling.\n *\n * `--motion-scale: 0` is how it is suspended, rather than a blanket\n * `transition: none !important`. Every duration in the system is already\n * pre-multiplied by that scalar, so zeroing it turns them all off through the\n * layer that exists for the purpose, overrides nothing a component declared,\n * and cannot be forgotten by a component author.\n *\n * THE FORCED READ IS LOAD-BEARING. Style is normally recalculated once, after\n * this function has returned — by which time the attribute is gone and the\n * durations are back. Reading a computed value flushes the recalculation\n * synchronously, so the new colours are committed WHILE durations are still\n * zero, and no transition is ever started. Delete that line and the suspension\n * silently does nothing at all, which is why `audit:motion` measures the settle\n * time rather than the presence of the attribute.\n */\nexport function applyPreference(attribute: string, value: string) {\n const root = document.documentElement;\n root.setAttribute(\"data-motion-suspend\", \"\");\n root.setAttribute(`data-${attribute}`, value);\n void getComputedStyle(root).backgroundColor;\n root.removeAttribute(\"data-motion-suspend\");\n}\n\nexport type Theme = \"light\" | \"dark\";\nexport const THEMES = [\"light\", \"dark\"] as const;\n\nexport type UiScale = \"sm\" | \"md\" | \"lg\";\nexport const SCALES = [\"sm\", \"md\", \"lg\"] as const;\n\n/**\n * `design` is the palette as drawn; `aa` raises every token to the 4.5:1 floor.\n *\n * Opt-IN, not enforced, because enforcing the floor everywhere flattens the\n * hierarchy for everyone: de-emphasised text stops being de-emphasised, and in\n * this palette light-theme tertiary collapsed onto the same ramp step as\n * secondary. A floor is a floor — reachable in one click by anyone who needs\n * it, not a target that drags the whole design up to meet it.\n */\nexport type ContrastPreference = \"design\" | \"aa\";\nexport const CONTRAST = [\"design\", \"aa\"] as const;\n\n/**\n * `system` is a real third state, not a synonym for \"full\": it means defer to\n * the OS, which is what most people want and what the OS-level preference\n * exists to express. Collapsing it into a boolean would make an app-level\n * control that says \"full\" override a user who set reduced motion system-wide.\n */\nexport type MotionPreference = \"system\" | \"full\" | \"reduced\";\nexport const MOTION = [\"system\", \"full\", \"reduced\"] as const;\n","import {\n CONTRAST,\n MOTION,\n SCALES,\n THEMES,\n type ContrastPreference,\n type MotionPreference,\n type Theme,\n type UiScale,\n} from \"../preference-values\";\n\n/**\n * The messages a `SandboxedFrame` host and the document inside it exchange\n * (DX-4075). One definition, imported by both sides, so they cannot drift.\n *\n * REACT-FREE: this module is reachable from `@danxbot/ui/pure` (the framed side\n * is a harness of any framework), so nothing here may import React.\n *\n * NO SECRET TRAVELS THIS CHANNEL. The host posts the four appearance values and\n * nothing else, which is why it can post to targetOrigin \"*\": the frame's origin\n * is the string \"null\" and cannot be named anyway.\n */\n\nexport const DESIGN_FRAME_APPEARANCE = \"danx-design:appearance\";\nexport const DESIGN_FRAME_READY = \"danx-design:ready\";\nexport const DESIGN_FRAME_ERROR = \"danx-design:error\";\n\n/** The most characters of a frame's error message the host will accept. */\nexport const DESIGN_FRAME_ERROR_MAX_LENGTH = 500;\n\n/**\n * The host's Appearance. A field is absent when the host document has not set\n * that attribute (a host that never mounted the preference hooks); the frame then\n * leaves its own default alone rather than being told a guess.\n */\nexport interface DesignFrameAppearance {\n theme?: Theme;\n contrast?: ContrastPreference;\n uiScale?: UiScale;\n motion?: MotionPreference;\n}\n\n/** Host -> frame, on every change of the host's Appearance. */\nexport interface DesignFrameAppearanceMessage extends DesignFrameAppearance {\n type: typeof DESIGN_FRAME_APPEARANCE;\n}\n\n/** Frame -> host: the design has mounted and is listening for appearance. */\nexport interface DesignFrameReadyMessage {\n type: typeof DESIGN_FRAME_READY;\n}\n\n/** Frame -> host: the design failed. The host caps and treats `message` as text. */\nexport interface DesignFrameErrorMessage {\n type: typeof DESIGN_FRAME_ERROR;\n message: string;\n}\n\nexport type DesignFrameInboundMessage = DesignFrameReadyMessage | DesignFrameErrorMessage;\n\n/**\n * The four `<html data-*>` attributes that carry Appearance, by field. The names\n * and value domains are the library's own (`preference-values.ts`).\n */\nexport const APPEARANCE_ATTRIBUTES = {\n theme: \"theme\",\n contrast: \"contrast\",\n uiScale: \"ui-scale\",\n motion: \"motion\",\n} as const satisfies Record<keyof DesignFrameAppearance, string>;\n\nconst DOMAINS: Record<keyof DesignFrameAppearance, readonly string[]> = {\n theme: THEMES,\n contrast: CONTRAST,\n uiScale: SCALES,\n motion: MOTION,\n};\n\nconst FIELDS = Object.keys(APPEARANCE_ATTRIBUTES) as (keyof DesignFrameAppearance)[];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/** Reads the Appearance off an element's `data-*` attributes; an unset or foreign value is left out. */\nexport function readAppearance(root: Element): DesignFrameAppearance {\n const out: Record<string, string> = {};\n for (const field of FIELDS) {\n const value = root.getAttribute(`data-${APPEARANCE_ATTRIBUTES[field]}`);\n if (value !== null && DOMAINS[field].includes(value)) out[field] = value;\n }\n return out as DesignFrameAppearance;\n}\n\n/** Narrows an untrusted `event.data` to an appearance message; a value outside its domain is refused. */\nexport function parseAppearanceMessage(data: unknown): DesignFrameAppearanceMessage | null {\n if (!isRecord(data) || data.type !== DESIGN_FRAME_APPEARANCE) return null;\n const out: Record<string, unknown> = { type: DESIGN_FRAME_APPEARANCE };\n for (const field of FIELDS) {\n const value = data[field];\n if (value === undefined) continue;\n if (typeof value !== \"string\" || !DOMAINS[field].includes(value)) return null;\n out[field] = value;\n }\n return out as unknown as DesignFrameAppearanceMessage;\n}\n\n/** Narrows an untrusted `event.data` to a ready or error message. */\nexport function parseInboundMessage(data: unknown): DesignFrameInboundMessage | null {\n if (!isRecord(data)) return null;\n if (data.type === DESIGN_FRAME_READY) return { type: DESIGN_FRAME_READY };\n if (data.type === DESIGN_FRAME_ERROR && typeof data.message === \"string\") {\n return { type: DESIGN_FRAME_ERROR, message: data.message };\n }\n return null;\n}\n","import { applyPreference } from \"../preference-values\";\nimport {\n APPEARANCE_ATTRIBUTES,\n DESIGN_FRAME_ERROR,\n DESIGN_FRAME_READY,\n parseAppearanceMessage,\n type DesignFrameAppearance,\n type DesignFrameErrorMessage,\n type DesignFrameReadyMessage,\n} from \"./protocol\";\n\n/**\n * `designFrameClient` — the framed side of `SandboxedFrame` (DX-4075). A harness\n * of any framework imports it: no React is reachable from here.\n */\n\n/**\n * Applies each Appearance the host sends. `cb` is told what arrived, after it is\n * applied; the library's own `applyPreference` does the write, so a theme flip is\n * a cut rather than the dissolve of every transition in the document.\n *\n * Only a message from `window.parent` is believed, and only one whose values are\n * inside the library's own domains. Returns the unsubscribe.\n */\nfunction onAppearance(cb?: (appearance: DesignFrameAppearance) => void): () => void {\n const listener = (event: MessageEvent) => {\n if (event.source !== window.parent) return;\n const message = parseAppearanceMessage(event.data);\n if (!message) return;\n const appearance: DesignFrameAppearance = {};\n for (const [field, attribute] of Object.entries(APPEARANCE_ATTRIBUTES)) {\n const key = field as keyof DesignFrameAppearance;\n const value = message[key];\n if (value === undefined) continue;\n applyPreference(attribute, value);\n Object.assign(appearance, { [key]: value });\n }\n cb?.(appearance);\n };\n window.addEventListener(\"message\", listener);\n return () => window.removeEventListener(\"message\", listener);\n}\n\n/** Tells the host the design has mounted. Call it after `onAppearance`, so the reply is heard. */\nfunction ready(): void {\n const message: DesignFrameReadyMessage = { type: DESIGN_FRAME_READY };\n window.parent.postMessage(message, \"*\");\n}\n\n/** Reports a failure to the host, which caps it and shows it as text. */\nfunction reportError(message: string): void {\n const payload: DesignFrameErrorMessage = { type: DESIGN_FRAME_ERROR, message: String(message) };\n window.parent.postMessage(payload, \"*\");\n}\n\nexport const designFrameClient = { onAppearance, ready, reportError };\n"],"names":["formatBytes","bytes","units","value","unit","formatTokens","tokens","formatCompactNumber","sign","abs","TIERS","threshold","suffix","pad","n","width","AmbiguousTimeError","wall","zone","kind","isoDateTimeString","isoDateString","date","daysInMonth","year","month","d","parsePlainDate","iso","m","day","ABSENT","formatDate","US_FORMAT","ISO_FORMAT","parseDate","input","trimmed","us","HOUR_MS","formatters","formatterFor","f","instantToPlainDateTime","instant","parts","hour","minute","second","part","millisecond","wallAsUtcMs","offsetAt","sameWall","a","b","instantsFor","asUtc","offsetBefore","offsetAfter","found","offset","candidate","t","i","plainDateTimeToInstant","disambiguation","candidates","gap","earlier","later","localZone","toInstant","normalized","ms","formatDateTime","options","hour12","meridiem","TYPED_FORMAT","ISO_WITH_ZONE","parseDateTime","rawHour","USD","formatCurrency","parseCurrency","accounting","negative","stripped","magnitude","formatNumber","decimals","intlOptions","parseNumber","formatBoolean","trueLabel","falseLabel","unknownLabel","TRUE_WORDS","FALSE_WORDS","parseBoolean","confidenceLevel","rating","scale","step","CONFIDENCE_LEVEL_LABELS","CHART_SERIES_COUNT","CHART_SERIES_TOKENS","CHART_SEQUENTIAL_TOKEN","chartSeriesColor","index","token","CHART_SEQUENTIAL_COLOR","applyPreference","attribute","root","THEMES","SCALES","CONTRAST","MOTION","DESIGN_FRAME_APPEARANCE","DESIGN_FRAME_READY","DESIGN_FRAME_ERROR","DESIGN_FRAME_ERROR_MAX_LENGTH","APPEARANCE_ATTRIBUTES","DOMAINS","FIELDS","isRecord","parseAppearanceMessage","data","out","field","onAppearance","cb","listener","event","message","appearance","key","ready","reportError","payload","designFrameClient"],"mappings":"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"}
1
+ {"version":3,"file":"pure.js","sources":["../src/lib/intl-cache.ts","../src/lib/format.ts","../src/lib/pad.ts","../src/lib/time/types.ts","../src/lib/time/plain.ts","../src/lib/time/zone.ts","../src/lib/values/locale-date.ts","../src/lib/values/absent.ts","../src/lib/values/date.ts","../src/lib/values/datetime.ts","../src/lib/values/currency.ts","../src/lib/values/number.ts","../src/lib/values/boolean.ts","../src/lib/confidence.ts","../src/lib/chart-palette.ts","../src/lib/preference-values.ts","../src/lib/design-frame/protocol.ts","../src/lib/design-frame/client.ts"],"sourcesContent":["/* ----------------------------------------------------------------------------\n DX-4740 — ONE Intl FORMATTER PER (LOCALE, OPTIONS), AND THE LOCALE FACTS THE\n PARSERS NEED.\n\n Constructing an `Intl.*Format` costs tens of microseconds and a data grid\n formats thousands of cells, so formatters are cached. The cache key carries the\n locale: a formatter built for one language is never handed to another.\n\n NOTHING HERE MAY IMPORT REACT — `@danxbot/ui/pure` reaches this file.\n -------------------------------------------------------------------------- */\n\nconst numberFormats = new Map<string, Intl.NumberFormat>();\nconst dateFormats = new Map<string, Intl.DateTimeFormat>();\nconst listFormats = new Map<string, Intl.ListFormat>();\nconst collators = new Map<string, Intl.Collator>();\nconst pluralRulesCache = new Map<string, Intl.PluralRules>();\n\nexport function numberFormat(locale: string, options: Intl.NumberFormatOptions): Intl.NumberFormat {\n const key = `${locale}|${JSON.stringify(options)}`;\n let f = numberFormats.get(key);\n if (!f) {\n f = new Intl.NumberFormat(locale, options);\n numberFormats.set(key, f);\n }\n return f;\n}\n\nexport function dateFormat(locale: string, options: Intl.DateTimeFormatOptions): Intl.DateTimeFormat {\n const key = `${locale}|${JSON.stringify(options)}`;\n let f = dateFormats.get(key);\n if (!f) {\n f = new Intl.DateTimeFormat(locale, options);\n dateFormats.set(key, f);\n }\n return f;\n}\n\nexport function listFormat(locale: string, options: Intl.ListFormatOptions = {}): Intl.ListFormat {\n const key = `${locale}|${JSON.stringify(options)}`;\n let f = listFormats.get(key);\n if (!f) {\n f = new Intl.ListFormat(locale, options);\n listFormats.set(key, f);\n }\n return f;\n}\n\n/** A cached `Intl.Collator`: sort names the way `locale` orders them (DX-4740), never by code point. */\nexport function collator(locale: string, options: Intl.CollatorOptions = {}): Intl.Collator {\n const key = `${locale}|${JSON.stringify(options)}`;\n let c = collators.get(key);\n if (!c) {\n c = new Intl.Collator(locale, options);\n collators.set(key, c);\n }\n return c;\n}\n\n/** A cached `Intl.PluralRules` (DX-4740): which plural category (`one`, `other`, ...) a count takes in `locale`. */\nexport function pluralRules(locale: string, options: Intl.PluralRulesOptions = {}): Intl.PluralRules {\n const key = `${locale}|${JSON.stringify(options)}`;\n let r = pluralRulesCache.get(key);\n if (!r) {\n r = new Intl.PluralRules(locale, options);\n pluralRulesCache.set(key, r);\n }\n return r;\n}\n\n/** The locale's digit-group and decimal separators (\",\" and \".\" in en-US, \".\" and \",\" in es-AR). */\nexport function numberSeparators(locale: string): { group: string; decimal: string } {\n const parts = numberFormat(locale, { useGrouping: true, minimumFractionDigits: 1 }).formatToParts(-1234567.5);\n return {\n group: parts.find((p) => p.type === \"group\")!.value,\n decimal: parts.find((p) => p.type === \"decimal\")!.value,\n };\n}\n\n/**\n * Text a person typed in `locale`'s number convention → a finite `number`, or `null`.\n * `strip` removes locale-specific decoration first (a currency symbol). Group\n * separators are dropped and the decimal separator becomes \".\", then the result\n * must be a plain signed decimal — `\"12abc\"` is a typo, not twelve.\n */\nexport function parseLocalizedNumber(text: string, locale: string, strip: readonly string[] = []): number | null {\n const { group, decimal } = numberSeparators(locale);\n let body = text;\n for (const s of strip) body = body.split(s).join(\"\");\n /* A whitespace group (fr, ru: U+202F / U+00A0) is typed as a plain space just as often. */\n if (/^\\s$/.test(group)) body = body.replace(/\\s/g, \"\");\n else body = body.split(group).join(\"\");\n body = body.split(decimal).join(\".\");\n if (!/^-?\\d+(\\.\\d+)?$/.test(body)) return null;\n const value = Number(body);\n return Number.isFinite(value) ? value : null;\n}\n\n/**\n * Create a Date from year/month/day values, treating them as UTC (not local time).\n * Used by date formatting and parsing to work in UTC consistently.\n */\nexport function asUtcDate(year: number, month: number, day: number): Date {\n const d = new Date(0);\n d.setUTCFullYear(year, month - 1, day);\n return d;\n}\n\n/**\n * The locale's digit set as an array [0, 1, 2, ..., 9], for normalizing user input\n * or parsing output of locale-specific formatters.\n */\nexport function localeDigits(locale: string): string[] {\n const digits: string[] = [];\n for (let i = 0; i < 10; i++) {\n const s = numberFormat(locale, {}).format(i);\n digits.push(s);\n }\n return digits;\n}\n\n/** A percentage as the locale writes it. `percent` is on the 0-100 scale; it is rounded to `digits` decimals. */\nexport function formatPercent(percent: number, locale: string, digits = 0): string {\n return numberFormat(locale, { style: \"percent\", maximumFractionDigits: digits }).format(percent / 100);\n}\n","/* ----------------------------------------------------------------------------\n NUMBERS, TURNED INTO THE STRINGS PEOPLE READ.\n\n These lived inside the components that needed them first — `formatBytes` in\n `chat/Attachment.tsx`, `formatTokens` in `chat/ContextMeter.tsx` — which was\n fine right up until somebody outside a component wanted one. The package\n exports a single entry, so importing `formatBytes` into an application's own\n formatting layer dragged React and every component in this library into a\n layer that is deliberately framework-free.\n\n They are here because they are DECISIONS, not utilities: how much precision\n a reader can act on, and where a rounded figure stops telling the truth. Two\n places rendering the same quantity to different precision is the defect this\n file prevents — see each function for the call it makes.\n\n NOTHING IN THIS FILE MAY IMPORT REACT, directly or transitively. It is the\n whole content of the `@danxbot/ui/pure` entry; `src/pure.ts` states the\n contract and `pure-entry.test.ts` enforces it against both the source graph\n and the built artifact.\n -------------------------------------------------------------------------- */\n\nimport { numberFormat } from \"./intl-cache\";\n\n/** Binary units, because these are file sizes and every OS reports them this way. */\nexport function formatBytes(bytes: number, locale: string): string {\n if (!Number.isFinite(bytes) || bytes < 0) return \"\";\n if (bytes < 1024) return `${numberFormat(locale, {}).format(bytes)} B`;\n // I18N-ALLOW: international unit symbols (kilo-, mega-, giga-, tera-byte), shown as the standard writes them in every locale\n const units = [\"KB\", \"MB\", \"GB\", \"TB\"];\n let value = bytes / 1024;\n let unit = 0;\n while (value >= 1024 && unit < units.length - 1) {\n value /= 1024;\n unit += 1;\n }\n /* One decimal below 10 and none above: \"1.4 MB\" is useful precision,\n \"847.3 KB\" is noise on a number nobody acts on. */\n const digits = value < 10 ? 1 : 0;\n return `${numberFormat(locale, { minimumFractionDigits: digits, maximumFractionDigits: digits }).format(value)} ${units[unit]}`;\n}\n\n/**\n * 241_600 → \"241.6K\". A thin, token-domain-named wrapper — see\n * `formatCompactNumber` below for the shared algorithm and its own reasoning.\n * Kept as its own function (rather than every call site switching to the\n * generic one directly) because \"tokens\" is a real vocabulary word in this\n * package's chat components, and renaming every call site to the generic name\n * would cost real diff for zero behavioral gain.\n */\nexport function formatTokens(tokens: number, locale: string): string {\n return formatCompactNumber(tokens, locale);\n}\n\n/**\n * A GENERIC large count, abbreviated the way the locale writes it: in en-US\n * `850` → `\"850\"`, `1_100` → `\"1.1K\"`, `3_400_000` → `\"3.4M\"`,\n * `4_400_000_000` → `\"4.4B\"`. `null` / `undefined` / non-finite → `\"—\"`.\n *\n * DX-4740: THE SUFFIXES COME FROM `Intl` (`notation: \"compact\"`), NOT FROM A\n * TABLE HERE. A hand-written T/B/M/k ladder is English: \"B\" for 10^9 reads as\n * \"billón\" (10^12) to an es-AR reader, and every other language has its own\n * words for thousand and million. en-US now writes an uppercase \"K\".\n *\n * One decimal at most, trimmed of a trailing \".0\" (`1_000_000` → `\"1M\"`, not\n * `\"1.0M\"`), and exact below 1,000 — a count small enough to read digit-by-digit\n * gets no abbreviation at all, since \"850\" is not improved by becoming \"0.9K\".\n * `formatTokens` is the token-budget's name for the same thing; this one is for\n * any large count a screen needs to abbreviate — a field tally, a donut's\n * fraction, a record count.\n *\n * NEGATIVE VALUES keep their sign (`-1_100` → `\"-1.1K\"`) rather than being\n * treated as absent — a generic count utility cannot assume its caller's\n * domain never has one.\n */\nexport function formatCompactNumber(value: number | null | undefined, locale: string): string {\n if (value == null || !Number.isFinite(value)) return \"—\";\n /* Below 1,000 (after rounding) the number is written whole. 999.6 rounds UP to 1,000 and so belongs to the compact branch. */\n /* `Math.round(-0.4)` is -0, which prints \"-0\"; adding 0 turns it into 0. */\n if (Math.abs(value) < 999.5) return numberFormat(locale, { maximumFractionDigits: 0 }).format(Math.round(value) + 0);\n return numberFormat(locale, { notation: \"compact\", maximumFractionDigits: 1 }).format(value);\n}\n","/** Zero-pad a non-negative integer to `width` digits — `pad(9)` → `\"09\"`,\n * `pad(7, 3)` → `\"007\"`. Internal only: shared by the time and value\n * formatters that print fixed-width calendar/clock fields, not part of the\n * public package surface (DX-3254 — this one-liner used to be redefined\n * independently in four places). */\nexport function pad(n: number, width = 2): string {\n return String(n).padStart(width, \"0\");\n}\n","/* ============================================================================\n THE TIME LAYER'S VOCABULARY — deliberately `Temporal`'s, minus what we do\n not need.\n\n WHY WE HAVE OUR OWN AT ALL. `Temporal` is the right answer and it is not\n available: Chrome 144+, Firefox 139+, Node 26+, and **Safari has not shipped\n it in any stable version, desktop or iOS**. Every iOS browser is WebKit, so\n \"modern browsers only\" does not rescue us — global support is ~69% and the\n entire gap is Apple. The polyfill is 20.4 kB gzip and is a real option; it is\n priced on the tracker (CAL-TIME) so reversing this decision is one sentence.\n\n WHY THE SHAPES MIRROR `Temporal` ANYWAY. The day Safari ships it, the swap\n should be mechanical rather than a rewrite. So `PlainDate` has `year` /\n `month` / `day` with a ONE-BASED month, `PlainDateTime` extends it,\n `Instant` is a fixed point on the timeline, and the disambiguation modes are\n spelled exactly as `Temporal.ZonedDateTime.from` spells them. Anywhere a name\n here differs from `Temporal`'s, that is a bug in this file.\n\n WHAT IS DELIBERATELY ABSENT: calendars other than ISO 8601, nanosecond\n precision, `Temporal.Duration`'s full field set, and any notion of a\n `ZonedDateTime` object. A calendar needs an instant, a wall-clock reading, a\n calendar day, and a way to move between them; carrying more would be carrying\n a date library we did not choose to write.\n ========================================================================= */\n\nimport { pad } from \"../pad\";\n\n/**\n * A point on the timeline, as milliseconds since the epoch.\n *\n * NOT a `Date`. A `Date` is a mutable object that prints itself in the host's\n * local zone, and every calendar bug in the survey behind this component\n * involved someone reading a `Date`'s local fields when they meant a specific\n * zone's. A number cannot be misread that way — it has no fields to read.\n */\nexport type Instant = number;\n\n/** An IANA time zone identifier, e.g. `\"America/New_York\"`. */\nexport type TimeZone = string;\n\n/**\n * A calendar day. No time, no zone, no offset — just which square on the wall.\n *\n * `month` IS ONE-BASED, matching `Temporal` and matching how every human writes\n * a date. `Date`'s zero-based month is the single most common off-by-one in\n * date code and it is not reproduced here.\n */\nexport interface PlainDate {\n readonly year: number;\n /** 1–12. */\n readonly month: number;\n /** 1–31. */\n readonly day: number;\n}\n\n/**\n * A wall-clock reading. What a clock on the wall says, with no claim about\n * which wall.\n *\n * This is the type that makes the ambiguity explicit rather than accidental:\n * `2026-11-01T01:30` in New York happened TWICE, and `2026-03-08T02:30` never\n * happened at all. A `PlainDateTime` can represent both, which is precisely why\n * turning one into an `Instant` requires saying what to do about it.\n */\nexport interface PlainDateTime extends PlainDate {\n /** 0–23. */\n readonly hour: number;\n /** 0–59. */\n readonly minute: number;\n /** 0–59. */\n readonly second: number;\n /** 0–999. */\n readonly millisecond: number;\n}\n\n/**\n * How much to move, in CALENDAR units rather than elapsed time.\n *\n * THE DISTINCTION IS THE WHOLE POINT, and getting it wrong is the bug in every\n * library surveyed. \"One day later\" is not \"86,400,000 ms later\": across a\n * spring-forward boundary it is 23 hours, and across a fall-back it is 25. A\n * user dragging an event from Saturday to Sunday expects it to keep its\n * wall-clock time, so the move has to be expressed as `{ days: 1 }` and\n * resolved against the zone — never added as a number.\n *\n * `hours` and `minutes` ARE exact elapsed time, and that is correct: dragging\n * an event down by two hours means two hours of real duration.\n */\nexport interface CalendarDuration {\n readonly days?: number;\n readonly hours?: number;\n readonly minutes?: number;\n}\n\n/**\n * What to do when a wall-clock reading does not name exactly one instant.\n *\n * Spelled as `Temporal.ZonedDateTime.from`'s option of the same name, with the\n * same meanings, so this is one more thing that becomes a deletion later.\n *\n * - `compatible` — what every other date library does, and the default.\n * In a GAP (spring forward) move forward by the size of the gap; in an\n * OVERLAP (fall back) take the first of the two.\n * - `earlier` / `later` — take that side explicitly.\n * - `reject` — throw. For a parser or an import, where a time that never\n * existed is a fact about bad input rather than something to paper over.\n */\nexport type Disambiguation = \"compatible\" | \"earlier\" | \"later\" | \"reject\";\n\n/** Thrown by `reject` disambiguation, and never by anything else. */\nexport class AmbiguousTimeError extends Error {\n constructor(\n readonly wall: PlainDateTime,\n readonly zone: TimeZone,\n readonly kind: \"gap\" | \"overlap\",\n ) {\n super(\n kind === \"gap\"\n ? `${isoDateTimeString(wall)} does not exist in ${zone} — the clock skipped it.`\n : `${isoDateTimeString(wall)} happens twice in ${zone}.`,\n );\n this.name = \"AmbiguousTimeError\";\n }\n}\n\n/** `2026-03-08T02:30:00.000` — used in errors, and by the ISO helpers. */\nexport function isoDateTimeString(wall: PlainDateTime): string {\n return (\n `${pad(wall.year, 4)}-${pad(wall.month)}-${pad(wall.day)}` +\n `T${pad(wall.hour)}:${pad(wall.minute)}:${pad(wall.second)}.${pad(wall.millisecond, 3)}`\n );\n}\n\n/** `2026-03-08`. */\nexport function isoDateString(date: PlainDate): string {\n return `${pad(date.year, 4)}-${pad(date.month)}-${pad(date.day)}`;\n}\n","import { isoDateString, type PlainDate, type PlainDateTime } from \"./types\";\n\n/* ============================================================================\n CALENDAR ARITHMETIC — no zones, no instants, no offsets.\n\n A month grid is a question about the calendar, not about time: which squares\n does September have, which weekday does the 1st fall on, how many rows. None\n of that depends on a zone, and none of it should reach for one. Keeping this\n file zone-free is what stops a month grid quietly acquiring a dependency on\n the viewer's location.\n\n Every function here goes through UTC internally — NOT because these values\n are UTC (they are not; they have no zone at all) but because UTC is the only\n mode of `Date` with no transitions in it, so the arithmetic cannot be\n disturbed by one. A local-mode `Date` doing the same work would lose an hour\n twice a year and, at midnight-transition zones, land on the wrong DAY.\n ========================================================================= */\n\n/** Days in `month` of `year`, leap years included. */\nexport function daysInMonth(year: number, month: number): number {\n const d = new Date(0);\n d.setUTCFullYear(year, month, 0);\n return d.getUTCDate();\n}\n\nfunction fromUtc(d: Date): PlainDate {\n return { year: d.getUTCFullYear(), month: d.getUTCMonth() + 1, day: d.getUTCDate() };\n}\n\nfunction toUtc(date: PlainDate): Date {\n const d = new Date(0);\n d.setUTCFullYear(date.year, date.month - 1, date.day);\n return d;\n}\n\n/** `date` moved by `days`, which may be negative, rolling months and years. */\nexport function addDays(date: PlainDate, days: number): PlainDate {\n const d = toUtc(date);\n d.setUTCDate(d.getUTCDate() + days);\n return fromUtc(d);\n}\n\n/**\n * `date` moved by `months`, clamping the day to the target month's length.\n *\n * CLAMPING, NOT ROLLING: one month after the 31st of January is the 28th or\n * 29th of February, never the 3rd of March. Rolling is what `Date.setMonth`\n * does natively and it is why \"next month\" buttons skip February.\n */\nexport function addMonths(date: PlainDate, months: number): PlainDate {\n const total = date.year * 12 + (date.month - 1) + months;\n const year = Math.floor(total / 12);\n const month = (total % 12) + 1;\n return { year, month, day: Math.min(date.day, daysInMonth(year, month)) };\n}\n\n/** Whole days from `a` to `b`; negative when `b` is earlier. */\nexport function daysBetween(a: PlainDate, b: PlainDate): number {\n return Math.round((toUtc(b).getTime() - toUtc(a).getTime()) / 86_400_000);\n}\n\n/** Negative when `a` is earlier, 0 when equal, positive when later. */\nexport function comparePlainDate(a: PlainDate, b: PlainDate): number {\n if (a.year !== b.year) return a.year - b.year;\n if (a.month !== b.month) return a.month - b.month;\n return a.day - b.day;\n}\n\nexport function isSameDate(a: PlainDate, b: PlainDate): boolean {\n return comparePlainDate(a, b) === 0;\n}\n\n/** 0 = Sunday … 6 = Saturday, matching `Date.getUTCDay` and `Intl`'s weekday order. */\nexport function dayOfWeek(date: PlainDate): number {\n return toUtc(date).getUTCDay();\n}\n\n/**\n * The first day of the week `date` falls in.\n *\n * `weekStartsOn` IS A PARAMETER WITH NO DEFAULT ANYWHERE UP THE STACK, because\n * there is no correct global answer: Sunday in the US and Japan, Monday across\n * Europe and by ISO 8601, Saturday in much of the Middle East. A component that\n * picks one silently is wrong for most of the world, so the calendar surfaces\n * it and this function simply obeys.\n */\nexport function startOfWeek(date: PlainDate, weekStartsOn: number): PlainDate {\n const shift = (dayOfWeek(date) - weekStartsOn + 7) % 7;\n return addDays(date, -shift);\n}\n\n/** The 1st of `date`'s month. */\nexport function startOfMonth(date: PlainDate): PlainDate {\n return { year: date.year, month: date.month, day: 1 };\n}\n\n/** The last day of `date`'s month. */\nexport function endOfMonth(date: PlainDate): PlainDate {\n return { year: date.year, month: date.month, day: daysInMonth(date.year, date.month) };\n}\n\n/**\n * Every day a month grid shows, INCLUDING the leading and trailing days that\n * belong to the neighbouring months.\n *\n * Always whole weeks, so the result is a multiple of seven and the grid never\n * has a ragged edge. The count varies — a 28-day February beginning on the\n * week's first day needs four rows, and a 31-day month beginning on its last\n * day needs six. Callers that assume six rows draw an empty one eleven months\n * a year; callers that assume five are simply wrong twice a year.\n */\nexport function monthGridDays(date: PlainDate, weekStartsOn: number): PlainDate[] {\n const first = startOfWeek(startOfMonth(date), weekStartsOn);\n const lastOfMonth = endOfMonth(date);\n const last = addDays(startOfWeek(lastOfMonth, weekStartsOn), 6);\n const out: PlainDate[] = [];\n for (let d = first; comparePlainDate(d, last) <= 0; d = addDays(d, 1)) out.push(d);\n return out;\n}\n\n/** The seven days of the week `date` falls in. */\nexport function weekDays(date: PlainDate, weekStartsOn: number): PlainDate[] {\n const first = startOfWeek(date, weekStartsOn);\n return Array.from({ length: 7 }, (_, i) => addDays(first, i));\n}\n\n/**\n * Parse `YYYY-MM-DD`.\n *\n * FAILS LOUDLY on anything else, including the shapes `Date` accepts and\n * silently reinterprets. `new Date(\"2026-2-30\")` yields the 2nd of March, which\n * is a data error becoming a rendered event on the wrong day with nothing\n * anywhere reporting a problem.\n */\nexport function parsePlainDate(iso: string): PlainDate {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(iso);\n if (!m) throw new Error(`not an ISO date: ${JSON.stringify(iso)} — expected YYYY-MM-DD`);\n const year = Number(m[1]);\n const month = Number(m[2]);\n const day = Number(m[3]);\n if (month < 1 || month > 12) throw new Error(`month out of range in ${iso}`);\n if (day < 1 || day > daysInMonth(year, month)) {\n throw new Error(`${iso} is not a real date — ${year}-${month} has ${daysInMonth(year, month)} days`);\n }\n return { year, month, day };\n}\n\nexport { isoDateString };\n\n/** A `PlainDateTime` at midnight on `date` — the wall reading, not an instant. */\nexport function atMidnight(date: PlainDate): PlainDateTime {\n return { ...date, hour: 0, minute: 0, second: 0, millisecond: 0 };\n}\n\n/** A `PlainDateTime` at `hour`:`minute` on `date`. */\nexport function atTime(date: PlainDate, hour: number, minute = 0): PlainDateTime {\n return { ...date, hour, minute, second: 0, millisecond: 0 };\n}\n","import {\n AmbiguousTimeError,\n type Disambiguation,\n type Instant,\n type PlainDate,\n type PlainDateTime,\n type TimeZone,\n} from \"./types\";\nimport { addDays } from \"./plain\";\nimport { dateFormat } from \"@/lib/intl-cache\";\n\n/* ============================================================================\n MOVING BETWEEN A WALL CLOCK AND THE TIMELINE.\n\n Everything here rests on ONE primitive — `offsetAt(zone, instant)` — read out\n of `Intl.DateTimeFormat`, which is the only IANA database a browser exposes.\n Everything else is arithmetic on top of it, so there is exactly one place\n where the rules of a zone enter this package.\n\n THE RULE THIS FILE EXISTS TO ENFORCE: a day is not 24 hours. It is 23 in\n New York on 2026-03-08, 25 on 2026-11-01, 23.5 in Lord Howe on 2026-10-04\n (its shift is THIRTY MINUTES, so any branch on \"is the delta 3600000\" is\n already wrong), and in Santiago on 2026-09-06 it is 23 hours that begin at\n 01:00 — `startOfDay` is NOT always midnight. Every one of those was measured\n before this file was written, and all five are in the test corpus.\n\n Kendo's scheduler crashed because two of its own methods disagreed about how\n long a day was; Mozilla's calendar drew an event an hour out plus a phantom\n row; react-big-calendar still shifts an entire week's columns because it\n derives them by adding `n * 86400000`. The shape of all three is the same:\n somewhere, a day was assumed.\n ========================================================================= */\n\nconst HOUR_MS = 3_600_000;\nconst MINUTE_MS = 60_000;\n\nfunction formatterFor(zone: TimeZone): Intl.DateTimeFormat {\n /* I18N-ALLOW: this formatter is zone ARITHMETIC, never display — the parts are read back as Latin digits, so the tag must not vary with the viewer's locale. */\n return dateFormat(\"en-US\", {\n timeZone: zone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n /* `h23` EXPLICITLY, not `hour12: false`. Several ICU versions render\n midnight as hour \"24\" under `hour12: false`, which parses as a valid\n number and silently lands the reading on the wrong day. */\n hourCycle: \"h23\",\n });\n}\n\n/** The wall-clock reading in `zone` at `instant`. */\nexport function instantToPlainDateTime(zone: TimeZone, instant: Instant): PlainDateTime {\n const parts = formatterFor(zone).formatToParts(new Date(instant));\n let year = 0;\n let month = 1;\n let day = 1;\n let hour = 0;\n let minute = 0;\n let second = 0;\n for (const part of parts) {\n switch (part.type) {\n case \"year\":\n year = Number(part.value);\n break;\n case \"month\":\n month = Number(part.value);\n break;\n case \"day\":\n day = Number(part.value);\n break;\n case \"hour\":\n hour = Number(part.value);\n break;\n case \"minute\":\n minute = Number(part.value);\n break;\n case \"second\":\n second = Number(part.value);\n break;\n }\n }\n /* Sub-second precision passes straight through: every offset in the IANA\n database since 1972 is a whole number of minutes, so a zone shift can never\n move the millisecond field. */\n const millisecond = ((instant % 1000) + 1000) % 1000;\n return { year, month, day, hour, minute, second, millisecond };\n}\n\n/**\n * The same wall-clock fields, but as though they were UTC.\n *\n * `Date.UTC` is NOT used directly anywhere in this file: it maps years 0–99\n * onto 1900–1999, so a year-40 date silently becomes 1940. `setUTCFullYear` has\n * no such rule. The calendar will never render year 40, but a helper that is\n * wrong for an input it never receives is a trap left for whoever reuses it.\n */\nexport function wallAsUtcMs(wall: PlainDateTime): number {\n const d = new Date(0);\n d.setUTCFullYear(wall.year, wall.month - 1, wall.day);\n d.setUTCHours(wall.hour, wall.minute, wall.second, wall.millisecond);\n return d.getTime();\n}\n\n/**\n * `zone`'s offset from UTC at `instant`, in milliseconds. East is positive.\n *\n * THE ONE PRIMITIVE. Read by asking the browser what the wall clock says at\n * that instant and subtracting: if it is 07:00 UTC and the clock in New York\n * reads 02:00, the offset is −5 hours. No table, no parsing of a zone name, no\n * assumption that an offset is a whole number of hours — `Pacific/Chatham` sits\n * at +12:45, and `Asia/Kathmandu` at +5:45.\n */\nexport function offsetAt(zone: TimeZone, instant: Instant): number {\n return wallAsUtcMs(instantToPlainDateTime(zone, instant)) - instant;\n}\n\nfunction sameWall(a: PlainDateTime, b: PlainDateTime): boolean {\n return (\n a.year === b.year &&\n a.month === b.month &&\n a.day === b.day &&\n a.hour === b.hour &&\n a.minute === b.minute &&\n a.second === b.second &&\n a.millisecond === b.millisecond\n );\n}\n\n/**\n * Every instant at which `zone`'s clock reads exactly `wall` — zero, one, or\n * two of them, in ascending order.\n *\n * TWO CANDIDATES, BOTH VERIFIED, WHICH IS THE PART THAT IS EASY TO GET WRONG.\n * The naive method — take the offset at a guessed instant and subtract it — is\n * correct in the ordinary case and wrong at exactly the boundaries this\n * component exists to handle. So both plausible offsets are tried, and each\n * candidate is accepted ONLY if reading the clock back at it reproduces the\n * requested wall time. That round-trip is what distinguishes a real reading\n * from one the clock skipped.\n *\n * two results → an OVERLAP; the clock struck this time twice (fall back)\n * one result → the ordinary case\n * none → a GAP; the clock never struck it at all (spring forward)\n */\nfunction instantsFor(zone: TimeZone, wall: PlainDateTime): Instant[] {\n const asUtc = wallAsUtcMs(wall);\n const offsetBefore = offsetAt(zone, asUtc - 24 * HOUR_MS);\n const offsetAfter = offsetAt(zone, asUtc + 24 * HOUR_MS);\n\n const found: Instant[] = [];\n for (const offset of offsetBefore === offsetAfter ? [offsetBefore] : [offsetBefore, offsetAfter]) {\n const candidate = asUtc - offset;\n if (sameWall(instantToPlainDateTime(zone, candidate), wall)) found.push(candidate);\n }\n found.sort((a, b) => a - b);\n /* A zone can in principle be sampled twice at the same offset either side of\n a transition that returns to where it started; de-duplicate rather than\n report one instant as two. */\n return found.filter((t, i) => i === 0 || t !== found[i - 1]);\n}\n\n/**\n * The instant at which `zone`'s clock reads `wall`.\n *\n * See `Disambiguation` for what happens when that is not exactly one instant.\n * The default is `compatible`, which is what `Temporal` and every other date\n * library do, so the surprising cases are the ones you have to ask for.\n */\nexport function plainDateTimeToInstant(\n zone: TimeZone,\n wall: PlainDateTime,\n disambiguation: Disambiguation = \"compatible\",\n): Instant {\n const candidates = instantsFor(zone, wall);\n\n if (candidates.length === 1) return candidates[0];\n\n if (candidates.length > 1) {\n /* OVERLAP — the hour ran twice. */\n if (disambiguation === \"reject\") throw new AmbiguousTimeError(wall, zone, \"overlap\");\n if (disambiguation === \"later\") return candidates[candidates.length - 1];\n /* `compatible` takes the earlier, matching Temporal. */\n return candidates[0];\n }\n\n /* GAP — the clock skipped this reading entirely. */\n if (disambiguation === \"reject\") throw new AmbiguousTimeError(wall, zone, \"gap\");\n\n const asUtc = wallAsUtcMs(wall);\n const offsetBefore = offsetAt(zone, asUtc - 24 * HOUR_MS);\n const offsetAfter = offsetAt(zone, asUtc + 24 * HOUR_MS);\n const gap = offsetAfter - offsetBefore;\n\n /* Both of these are real instants; they differ by the size of the gap. Using\n the PRE-transition offset lands before the jump, the post-transition offset\n lands after it. `compatible` shifts FORWARD by the gap — the same choice\n Temporal makes, and the one that matches what a person means by \"07:30\" on\n a morning when 07:30 did not happen. */\n const earlier = asUtc - offsetBefore;\n const later = asUtc - offsetAfter;\n if (disambiguation === \"earlier\") return Math.min(earlier, later);\n return gap > 0 ? Math.max(earlier, later) : Math.min(earlier, later);\n}\n\n/** The calendar day `instant` falls on, in `zone`. */\nexport function instantToPlainDate(zone: TimeZone, instant: Instant): PlainDate {\n const wall = instantToPlainDateTime(zone, instant);\n return { year: wall.year, month: wall.month, day: wall.day };\n}\n\n/**\n * The first instant of `date` in `zone`.\n *\n * NOT \"midnight\". In `America/Santiago` on 2026-09-06 the clock jumps from\n * 00:00 to 01:00, so the day's first instant reads 01:00 — and a grid that\n * assumes its own origin is midnight puts every event on that day an hour out.\n * `America/Havana` and `Asia/Beirut` do the same thing on their own dates.\n *\n * Implemented by asking for midnight and, when the clock skipped it, walking\n * forward a minute at a time until a reading exists. The walk is bounded: no\n * transition in the IANA database exceeds two hours, so 180 iterations cannot\n * be reached by real data, and the bound exists to fail loudly rather than spin\n * if that ever stops being true.\n */\nexport function startOfDay(zone: TimeZone, date: PlainDate): Instant {\n const midnight: PlainDateTime = { ...date, hour: 0, minute: 0, second: 0, millisecond: 0 };\n const exact = instantsFor(zone, midnight);\n /* An OVERLAP at midnight means the day started twice; the day begins at the\n first of them, not the second. */\n if (exact.length > 0) return exact[0];\n\n for (let minutes = 1; minutes <= 180; minutes++) {\n const candidate = instantsFor(zone, {\n ...date,\n hour: Math.floor(minutes / 60),\n minute: minutes % 60,\n second: 0,\n millisecond: 0,\n });\n if (candidate.length > 0) return candidate[0];\n }\n throw new Error(\n `startOfDay: no instant exists in the first three hours of ${date.year}-${date.month}-${date.day} in ${zone}`,\n );\n}\n\n/** The day after `date` on the calendar, ignoring zones entirely. */\nexport function nextDay(date: PlainDate): PlainDate {\n return addDays(date, 1);\n}\n\n/**\n * How long `date` actually lasts in `zone`, in milliseconds.\n *\n * Measured as the distance between one day's first instant and the next day's,\n * which is true by construction for every case — 23 hours, 25 hours, the 30\n * minutes of Lord Howe, and a day that starts at 01:00 — with no special case\n * for any of them. That is the entire reason positions in the grid are a RATIO\n * of this number rather than `hour × rowHeight`.\n */\nexport function dayLengthMs(zone: TimeZone, date: PlainDate): number {\n return startOfDay(zone, nextDay(date)) - startOfDay(zone, date);\n}\n\n/**\n * How many hours `date` lasts in `zone` — 23, 24, 25, or 23.5.\n *\n * Fractional by design. `Australia/Lord_Howe` shifts by thirty minutes, so a\n * signature returning an integer would be lying about a real zone, and any\n * caller branching on `=== 23` is already broken.\n */\nexport function hoursInDay(zone: TimeZone, date: PlainDate): number {\n return dayLengthMs(zone, date) / HOUR_MS;\n}\n\n/**\n * Where `instant` sits within `date`, as a fraction from 0 to 1.\n *\n * THE LOAD-BEARING FUNCTION. A 23-hour day compresses and a 25-hour day\n * stretches with no branch anywhere, because the divisor is the day's real\n * length. Values outside 0–1 mean the instant is not on this day and are\n * returned rather than clamped — the layout core needs to know by how much in\n * order to clip a multi-day span.\n */\nexport function dayFraction(zone: TimeZone, date: PlainDate, instant: Instant): number {\n const from = startOfDay(zone, date);\n return (instant - from) / dayLengthMs(zone, date);\n}\n\n/**\n * The exact inverse of `dayFraction`.\n *\n * IT MUST BE EXACT, and that is not a stylistic preference. Rendering maps an\n * instant to a position and hit-testing maps a position back to an instant; if\n * the two disagree by even a rounding step, an event lands one slot away from\n * where it was dropped. FullCalendar's Australia selection bug is exactly this\n * pair drifting apart. `dayFraction(z, d, instantAtFraction(z, d, f)) === f`\n * for every zone in the corpus, and there is a test that says so.\n */\nexport function instantAtFraction(zone: TimeZone, date: PlainDate, fraction: number): Instant {\n return startOfDay(zone, date) + fraction * dayLengthMs(zone, date);\n}\n\n/**\n * Move `instant` by a CALENDAR duration.\n *\n * Days are applied on the calendar and then re-anchored to the same wall-clock\n * time, so an event dragged from Saturday to Sunday across a transition keeps\n * reading 09:00 rather than becoming 08:00 or 10:00. Hours and minutes are\n * applied afterwards as exact elapsed time, because dragging an event down the\n * grid by two hours does mean two hours.\n *\n * ORDER MATTERS AND IS FIXED: days first, then time. The other order makes\n * \"one day and two hours later\" depend on which side of a transition the\n * intermediate value happened to land on.\n */\nexport function addDuration(\n zone: TimeZone,\n instant: Instant,\n duration: { days?: number; hours?: number; minutes?: number },\n disambiguation: Disambiguation = \"compatible\",\n): Instant {\n const { days = 0, hours = 0, minutes = 0 } = duration;\n\n let result = instant;\n if (days !== 0) {\n const wall = instantToPlainDateTime(zone, instant);\n const shifted = new Date(0);\n shifted.setUTCFullYear(wall.year, wall.month - 1, wall.day + days);\n result = plainDateTimeToInstant(\n zone,\n {\n year: shifted.getUTCFullYear(),\n month: shifted.getUTCMonth() + 1,\n day: shifted.getUTCDate(),\n hour: wall.hour,\n minute: wall.minute,\n second: wall.second,\n millisecond: wall.millisecond,\n },\n disambiguation,\n );\n }\n return result + hours * HOUR_MS + minutes * MINUTE_MS;\n}\n","/* ----------------------------------------------------------------------------\n DX-4740 — HOW A LOCALE WRITES A CALENDAR DATE AND A CLOCK TIME, LEARNED FROM\n `Intl` ONCE PER LOCALE SO THE FORMATTERS AND THE PARSERS CANNOT DISAGREE.\n\n `formatDate` / `formatDateTime` print through `Intl.DateTimeFormat(locale)`;\n `parseDate` / `parseDateTime` read back exactly that shape. Both derive the\n field order, the separator and the day-period words from the same probe here,\n so a value displayed in a locale round-trips through its own parser.\n -------------------------------------------------------------------------- */\n\nimport { dateFormat, localeDigits } from \"../intl-cache\";\nimport { wallAsUtcMs } from \"../time/zone\";\nimport type { PlainDateTime } from \"../time/types\";\n\ntype DateField = \"year\" | \"month\" | \"day\";\n\n/* `calendar: \"gregory\"`: stored dates are Gregorian, but some locales default to another calendar (fa-IR prints Jalali), which would show a different date than the one stored. */\n// I18N-ALLOW: Intl.DateTimeFormat option values\nexport const DATE_OPTIONS: Intl.DateTimeFormatOptions = { calendar: \"gregory\", timeZone: \"UTC\", year: \"numeric\", month: \"2-digit\", day: \"2-digit\" };\nexport const TIME_OPTIONS: Intl.DateTimeFormatOptions = { ...DATE_OPTIONS, hour: \"numeric\", minute: \"2-digit\" };\n\n/** A wall-clock reading as a `Date` whose UTC fields ARE the reading, so `timeZone: \"UTC\"` prints it untouched. */\nexport function wallDate(wall: PlainDateTime): Date {\n return new Date(wallAsUtcMs(wall));\n}\n\n/* ICU separates the time from a day period with U+202F in recent versions and a plain\n space in older ones; one space keeps the output identical across both. */\nexport function plainSpaces(text: string): string {\n return text.replace(/[\\u202f\\u00a0]/g, \" \");\n}\n\nexport interface DatePattern {\n order: DateField[];\n /** Regex source matching one numeric date in the locale's order and separator. */\n source: string;\n}\n\nconst patterns = new Map<string, DatePattern>();\n\nconst BIDI_MARKS = /[‎‏؜]/g;\nconst escapeRegex = (text: string) => text.replace(/[.*+?^${}()|[\\]\\\\/]/g, \"\\\\$&\");\n\n/**\n * The locale's own digits and bidi marks → plain Latin digits and nothing, so a number typed in\n * Eastern Arabic or Persian digits (ar-EG, fa-IR) reads the same as one typed in Latin.\n */\nexport function normalizeDigits(text: string, locale: string): string {\n const digits = localeDigits(locale);\n let out = text.replace(BIDI_MARKS, \"\");\n digits.forEach((d, i) => {\n if (d !== String(i)) out = out.split(d).join(String(i));\n });\n return out;\n}\n\nexport function datePattern(locale: string): DatePattern {\n let pattern = patterns.get(locale);\n if (!pattern) {\n const parts = dateFormat(locale, DATE_OPTIONS).formatToParts(new Date(0));\n const lastField = parts.reduce((last, p, i) => (p.type === \"year\" || p.type === \"month\" || p.type === \"day\" ? i : last), -1);\n const order: DateField[] = [];\n const source = parts\n .map((part, i) => {\n if (part.type === \"year\" || part.type === \"month\" || part.type === \"day\") {\n order.push(part.type);\n return part.type === \"year\" ? \"(\\\\d{4})\" : \"(\\\\d{1,2})\";\n }\n const literal = part.value.replace(BIDI_MARKS, \"\");\n /* Whitespace around a literal is optional, so \"2026.09.11\" reads as \"2026. 09. 11.\"; a literal\n AFTER the last field (\"11.09.2026 г.\", the final \".\" of ko-KR) may be left off. */\n const text = escapeRegex(literal.trim());\n const inner = `\\\\s*${text}\\\\s*`;\n return i > lastField ? `(?:${inner})?` : inner;\n })\n .join(\"\");\n pattern = { order, source };\n patterns.set(locale, pattern);\n }\n return pattern;\n}\n\n/** The three captured numbers of a `datePattern` match, by field. */\nexport function dateFields(order: DateField[], match: RegExpExecArray, offset = 1): { year: number; month: number; day: number } {\n const out = { year: 0, month: 0, day: 0 };\n order.forEach((field, i) => {\n out[field] = Number(match[offset + i]);\n });\n return out;\n}\n\nexport interface ClockPattern {\n /** True when the locale shows 1-12 with a day period (\"AM\"/\"PM\"), false for a 0-23 clock. */\n twelveHour: boolean;\n /** Lower-case, space-free forms of the locale's morning and afternoon words (12-hour clocks only). */\n am: string;\n pm: string;\n}\n\nconst clocks = new Map<string, ClockPattern>();\n\nexport function normalizePeriod(text: string): string {\n return text.toLowerCase().replace(/[\\s.\\u202f\\u00a0]/g, \"\");\n}\n\nexport function clockPattern(locale: string): ClockPattern {\n let clock = clocks.get(locale);\n if (!clock) {\n const probe = (hour: number) =>\n dateFormat(locale, { timeZone: \"UTC\", hour: \"numeric\" }).formatToParts(new Date(Date.UTC(2000, 0, 1, hour))).find((p) => p.type === \"dayPeriod\");\n const am = probe(0);\n clock = { twelveHour: am !== undefined, am: am ? normalizePeriod(am.value) : \"\", pm: am ? normalizePeriod(probe(12)!.value) : \"\" };\n clocks.set(locale, clock);\n }\n return clock;\n}\n","/** The shared \"no value\" reading for a blank field — a date, a number, a\n * phone number, a currency amount: nothing recorded. Shared by every\n * formatter that reads absence this way, so the glyph itself never drifts\n * between them (DX-3254). `formatBoolean` does NOT use this — see its own\n * module doc for why a tri-state boolean reads absence as \"Unknown\"\n * instead. */\nexport const ABSENT = \"—\";\n","import { daysInMonth, isoDateString, parsePlainDate } from \"../time/plain\";\nimport type { PlainDate } from \"../time/types\";\nimport { asUtcDate, dateFormat } from \"../intl-cache\";\nimport { DATE_OPTIONS, datePattern, dateFields, normalizeDigits } from \"./locale-date\";\nimport { ABSENT } from \"./absent\";\n\n/* ----------------------------------------------------------------------------\n A CALENDAR DATE — no time, no zone, just which square on the wall.\n\n Storage form is `YYYY-MM-DD`, the same ISO shape `lib/time` already parses\n and prints (`parsePlainDate` / `isoDateString`). That is not a coincidence —\n an accident date, a policy effective date, a date of birth are all exactly\n the `PlainDate` the calendar layer already models, and reusing its\n leap-year-correct arithmetic here is the whole point of `lib/time` existing\n as a shared layer instead of a calendar-component implementation detail.\n\n THE BUG THIS FILE CANNOT HAVE, BY CONSTRUCTION. The classic defect —\n `new Date(\"2026-09-11\")` parsed as UTC midnight, then printed in a reader\n west of Greenwich, silently showing \"09/10/2026\" — requires a `Date` object\n and an implicit zone somewhere in the path from string to string. Here the\n only `Date` is built in UTC from the parsed fields and printed in UTC, and\n `parseDate` reads typed fields straight into a `PlainDate`. There is no\n step at which the reader's zone could enter.\n\n DISPLAY FORMAT: the locale's all-numeric date — `MM/DD/YYYY` in `en-US`,\n `DD.MM.YYYY` in `de-DE`. Not `Intl`'s long `\"Sep 11, 2026\"`: a 4-digit year\n with no ambiguity about which field is the day matters more than prose, and\n an all-numeric form is what a claim file, a police report and an EOB expect.\n The fields are laid out by `Intl` in the locale's own order and separator,\n fed a UTC `Date` built from the parsed fields (never a zone-shifted one),\n so the bug above still cannot happen. It is also the shape `parseDate`\n accepts back, so a value a person types after seeing this library's own\n display round-trips without translation.\n -------------------------------------------------------------------------- */\n\n\n/**\n * `\"2026-09-11\"` → `\"09/11/2026\"` in `en-US`, `\"11.09.2026\"` in `de-DE`.\n *\n * `null` / `undefined` / `\"\"` render as the em dash — no date entered, the\n * convention the rest of this package's formatters share (`formatTokens`,\n * `formatBoolean`'s \"Unknown\" is the one deliberate exception; see its own\n * docblock for why absence needs a different word there).\n *\n * A non-empty value that is NOT a valid `YYYY-MM-DD` date is returned\n * UNCHANGED rather than folded into the same em dash. Absence and corruption\n * are different facts — silently rendering a malformed stored value as \"no\n * date\" hides a data problem behind a blank field; showing the raw string\n * puts it in front of whoever is looking at the row.\n */\nexport function formatDate(value: string | null | undefined, locale: string): string {\n if (value == null || value === \"\") return ABSENT;\n let date: PlainDate;\n try {\n date = parsePlainDate(value);\n } catch {\n return value;\n }\n return dateFormat(locale, DATE_OPTIONS).format(asUtcDate(date.year, date.month, date.day));\n}\n\nconst ISO_FORMAT = /^(\\d{4})-(\\d{1,2})-(\\d{1,2})$/;\n\n/**\n * A human-typed date → the stored `YYYY-MM-DD`, or `null` if it is not one.\n *\n * Accepts the numeric date in `locale`'s own order (what `formatDate` prints, and what a person types\n * into a text field) and bare ISO `YYYY-MM-DD` (what a paste from another\n * system, or this library's own storage form, looks like) — nothing looser.\n * `Date`-style leniency (`new Date(\"2026-2-30\")` silently becoming the 2nd of\n * March) is exactly the failure mode `parsePlainDate`'s calendar-length check\n * exists to refuse, so this function refuses it too: `daysInMonth` bounds the\n * day before anything is accepted.\n *\n * Returns `null`, never throws — a form validating a field checks the return\n * value; it should not have to wrap every keystroke in `try`/`catch`.\n */\nexport function parseDate(input: string, locale: string): string | null {\n const trimmed = normalizeDigits(input, locale).trim();\n if (trimmed === \"\") return null;\n\n let year: number;\n let month: number;\n let day: number;\n\n const iso = ISO_FORMAT.exec(trimmed);\n if (iso) {\n year = Number(iso[1]);\n month = Number(iso[2]);\n day = Number(iso[3]);\n } else {\n const { order, source } = datePattern(locale);\n const typed = new RegExp(`^${source}$`).exec(trimmed);\n if (!typed) return null;\n ({ year, month, day } = dateFields(order, typed));\n }\n\n if (month < 1 || month > 12) return null;\n if (day < 1 || day > daysInMonth(year, month)) return null;\n return isoDateString({ year, month, day });\n}\n","import { instantToPlainDateTime, plainDateTimeToInstant } from \"../time/zone\";\nimport { daysInMonth } from \"../time/plain\";\nimport type { PlainDateTime } from \"../time/types\";\nimport { dateFormat } from \"../intl-cache\";\nimport { TIME_OPTIONS, clockPattern, dateFields, datePattern, normalizeDigits, normalizePeriod, plainSpaces, wallDate } from \"./locale-date\";\nimport { ABSENT } from \"./absent\";\n\n/* ----------------------------------------------------------------------------\n AN INSTANT — a fixed point on the timeline, rendered through a zone.\n\n THE DISTINCTION FROM `date.ts`, AND WHY IT IS A SEPARATE FILE RATHER THAN AN\n OPTION ON THE SAME ONE. An accident date is a calendar square: everyone who\n reads a claim file agrees it happened on the 11th, regardless of where they\n are sitting. \"When this record was created\" is not — it is one instant, and\n a reader in Los Angeles and a reader in New York see it at different clock\n times on (near a midnight boundary) potentially different DAYS. Collapsing\n the two into one formatter with a \"is this a date-time\" flag is how a\n calendar date ends up accidentally zone-shifted, which is the exact defect\n `date.ts`'s docblock explains `formatDate` cannot have. Keeping them as two\n files with two contracts is what keeps that true.\n\n STORAGE FORM is an ISO instant string (`\"2026-09-11T19:18:43.000Z\"`,\n `\"...43.000000Z\"` — microsecond precision, as a Laravel/Carbon backend\n emits, is accepted and truncated to milliseconds, see `toInstant` below) or\n a raw epoch-millisecond number — `lib/time`'s own `Instant`. Either is\n accepted so a consumer does not have to convert before handing a value in.\n\n DISPLAY FORMAT is the locale's all-numeric date and clock — `MM/DD/YYYY,\n h:mm AM/PM` in `en-US`, `DD.MM.YYYY, HH:mm` in `de-DE` — the SAME numeric\n date `date.ts` uses, not `Intl`'s long `\"Sep 11, 2026, 1:18 PM\"`. Two\n reasons, not one: it keeps a date and a date-time reading consistently\n across a table that shows both, and it means `parseDateTime` can invert\n exactly what `formatDateTime` prints without parsing a localised month name\n back out of a string — both read the field order and day-period words from\n the same `Intl` probe (`locale-date.ts`), so the two functions agree with\n each other in the locales `date-parse-locales.test.ts` pins (en-US, de-DE,\n es-AR, hu-HU, bg-BG); `parseDate` is pinned for those plus ko-KR, ar-EG and\n fa-IR. A locale whose clock puts the day period before the time (ko-KR) is\n not claimed.\n\n THE ZONE CONVERSION ITSELF IS NOT REIMPLEMENTED HERE. `instantToPlainDateTime`\n and `plainDateTimeToInstant` are `lib/time/zone.ts`'s own primitives, proven\n against a five-zone DST corpus (`time-dst.test.ts`) that includes half-hour\n offsets, midnight-skipping transitions and hours that happen twice. Writing\n a second, ad-hoc offset calculation here — the way a naive datetime\n formatter usually does — is exactly how the bugs that corpus documents get\n reintroduced a second time in the same codebase.\n -------------------------------------------------------------------------- */\n\n/** The reader's own zone, per the host `Intl` implementation — the correct\n * default for an INSTANT, which (unlike a `PlainDate`) has no zone of its\n * own until someone reads it. */\nfunction localZone(): string {\n return Intl.DateTimeFormat().resolvedOptions().timeZone;\n}\n\n/**\n * A stored instant (ISO string or epoch ms) → epoch milliseconds, or `null`.\n *\n * Microsecond-precision ISO strings (`.043210Z`, six fractional digits — what\n * Carbon's default JSON cast emits) are truncated to the three digits\n * `Date.parse` is spec-guaranteed to accept; V8 happens to be lenient with\n * more, but nothing requires every engine this bundles into to be.\n */\nfunction toInstant(value: string | number): number | null {\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n const normalized = value.replace(/(\\.\\d{3})\\d+/, \"$1\");\n const ms = Date.parse(normalized);\n return Number.isNaN(ms) ? null : ms;\n}\n\nexport interface FormatDateTimeOptions {\n /**\n * IANA zone to render in, e.g. `\"America/New_York\"`. Omit to use the\n * reader's own zone — the right default for an instant, and the reason\n * this is a parameter at all rather than always-local: a report emailed to\n * someone in a different zone, or a server rendering a PDF, needs to say\n * WHOSE clock the printed time is on.\n */\n zone?: string;\n}\n\n/**\n * A stored instant → `\"09/11/2026, 1:18 PM\"` (`en-US`) in `options.zone` (default: the\n * reader's own).\n *\n * `null` / `undefined` / `\"\"` render as the em dash. A non-empty value that\n * does not parse as an instant is returned UNCHANGED — see `date.ts`'s\n * docblock for why absence and corruption get different treatment.\n */\nexport function formatDateTime(\n value: string | number | null | undefined,\n locale: string,\n options: FormatDateTimeOptions = {},\n): string {\n if (value == null || value === \"\") return ABSENT;\n const instant = toInstant(value);\n if (instant === null) return String(value);\n\n const zone = options.zone ?? localZone();\n const wall = instantToPlainDateTime(zone, instant);\n return plainSpaces(dateFormat(locale, TIME_OPTIONS).format(wallDate(wall)));\n}\n\n/* The time after a typed date: `h:mm[:ss]`, then the locale's day period when its clock has one —\n what `formatDateTime` prints, plus optional seconds for a value pasted from somewhere more precise. */\n// I18N-ALLOW: regular expression source for a typed time\nconst TYPED_TIME = String.raw`(?:,?\\s+(\\d{1,2}):(\\d{2})(?::(\\d{2}))?(?:\\s*([^\\d\\s].*?))?)?`;\n\n/* An ISO instant carrying its own offset or `Z` — unambiguous on its own, so\n it never needs a `zone` argument to resolve. */\nconst ISO_WITH_ZONE = /^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}(:\\d{2}(\\.\\d+)?)?\\s*(Z|[+-]\\d{2}:?\\d{2})$/;\n\nexport interface ParseDateTimeOptions {\n /**\n * The zone a bare (no AM/PM-less midnight, no offset) typed value's\n * wall-clock reading is IN. Omit to use the reader's own zone — matching\n * `formatDateTime`'s default, so parsing a value typed against that display\n * uses the same zone it was rendered in.\n */\n zone?: string;\n}\n\n/**\n * A human-typed date-time → the stored ISO instant, or `null`.\n *\n * TWO PATHS, DELIBERATELY NOT ONE. An ISO string carrying its own offset or\n * `Z` is unambiguous — `Date.parse` resolves it correctly regardless of what\n * zone this function is running in, and is used directly. Everything else is\n * a WALL-CLOCK reading with no zone of its own (`\"09/11/2026, 1:18 PM\"`), and\n * `Date.parse`'s native handling of that shape resolves it against the\n * HOST's zone — which is wrong the instant `options.zone` names a different\n * one, e.g. a server in UTC parsing a value entered by a reader in\n * `America/New_York`. So the wall-clock path is hand-parsed into fields and\n * handed to `plainDateTimeToInstant`, which resolves it against the\n * REQUESTED zone rather than whatever zone the code happens to be running in.\n *\n * A wall-clock reading that lands in a DST gap or overlap does not throw —\n * `plainDateTimeToInstant`'s default (`\"compatible\"`) disambiguation applies,\n * matching every other date library's default and `Temporal`'s own. See\n * `time-dst.test.ts` for what that resolves to at a transition.\n */\nexport function parseDateTime(input: string, locale: string, options: ParseDateTimeOptions = {}): string | null {\n const trimmed = normalizeDigits(input, locale).trim();\n if (trimmed === \"\") return null;\n\n if (ISO_WITH_ZONE.test(trimmed)) {\n const ms = toInstant(trimmed);\n return ms === null ? null : new Date(ms).toISOString();\n }\n\n const { order, source } = datePattern(locale);\n const m = new RegExp(`^${source}${TYPED_TIME}$`).exec(trimmed);\n if (!m) return null;\n\n const { year, month, day } = dateFields(order, m);\n if (month < 1 || month > 12) return null;\n if (day < 1 || day > daysInMonth(year, month)) return null;\n\n let hour = 0;\n let minute = 0;\n let second = 0;\n if (m[4] !== undefined) {\n const rawHour = Number(m[4]);\n minute = Number(m[5]);\n second = m[6] !== undefined ? Number(m[6]) : 0;\n if (minute > 59 || second > 59) return null;\n const clock = clockPattern(locale);\n if (clock.twelveHour) {\n const period = m[7] === undefined ? undefined : normalizePeriod(m[7]);\n if (rawHour < 1 || rawHour > 12 || (period !== clock.am && period !== clock.pm)) return null;\n hour = period === clock.am ? (rawHour === 12 ? 0 : rawHour) : rawHour === 12 ? 12 : rawHour + 12;\n } else {\n if (rawHour > 23 || m[7] !== undefined) return null;\n hour = rawHour;\n }\n }\n\n const wall: PlainDateTime = { year, month, day, hour, minute, second, millisecond: 0 };\n const zone = options.zone ?? localZone();\n return new Date(plainDateTimeToInstant(zone, wall)).toISOString();\n}\n","/* ----------------------------------------------------------------------------\n USD MONEY.\n\n ALWAYS TWO DECIMAL PLACES, regardless of magnitude — the opposite choice\n from `formatBytes`/`formatTokens`, and deliberately so. Those two round MORE\n aggressively as a number grows because the tenth of a KB or the exact token\n count stop being digits anyone acts on at scale. A dollar figure never\n reaches that point: the gap between $1,234,567.89 and $1,234,568.00 is\n eighty-nine real cents on an invoice or a settlement figure, not noise, no\n matter how many digits come before the decimal point. So precision here is\n fixed, not magnitude-scaled.\n\n ZERO IS NOT ABSENCE. `formatCurrency(0)` is `\"$0.00\"` — a real, recorded\n amount, e.g. a fully-waived fee or a $0 deductible. `null`/`undefined` is\n \"no figure was entered\", rendered as the em dash this package's other\n formatters use for the same fact. Collapsing the two (rendering 0 as \"—\",\n or treating \"—\" as 0 in a sum) is the classic spreadsheet bug this\n distinction exists to prevent.\n -------------------------------------------------------------------------- */\n\nimport { ABSENT } from \"./absent\";\nimport { numberFormat, parseLocalizedNumber } from \"../intl-cache\";\n\nconst USD: Intl.NumberFormatOptions = {\n style: \"currency\",\n // I18N-ALLOW: ISO 4217 currency code option\n currency: \"USD\",\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n};\n\n/**\n * `1234.5` → `\"$1,234.50\"` in `en-US`, `\"1.234,50 $\"` in `de-DE`. `null` / `undefined` → `\"—\"`. `0` → `\"$0.00\"`.\n *\n * `NaN` / `Infinity` are not real amounts either and render as the em dash —\n * grouped with absence rather than with a malformed string, because unlike\n * `formatDate`'s malformed-input case, there is no ORIGINAL\n * user-facing string to show back: the caller already has a `number`, and a\n * non-finite one is a computation defect upstream, not a typo to surface.\n */\nexport function formatCurrency(value: number | null | undefined, locale: string): string {\n if (value == null || !Number.isFinite(value)) return ABSENT;\n return numberFormat(locale, USD).format(value);\n}\n\n/**\n * A human-typed amount → a `number` in dollars, or `null`.\n *\n * Tolerates the punctuation someone actually types or pastes: the dollar sign\n * (and `locale`'s own symbol for it), `locale`'s thousands separator, surrounding whitespace, and ACCOUNTING-style negatives —\n * `\"($1,234.56)\"` for `-1234.56`, the convention every US invoice and EOB\n * already uses for a credit or a refund. A leading `-` is accepted too.\n * Returns `null` — never `NaN`, never a silent `0` — for anything that is not\n * recognizably a number once that punctuation is stripped, so a caller can\n * reject a bad edit instead of storing garbage.\n */\nexport function parseCurrency(input: string, locale: string): number | null {\n const trimmed = input.trim();\n if (trimmed === \"\") return null;\n\n const accounting = /^\\((.*)\\)$/.exec(trimmed);\n const negative = accounting !== null;\n const body = accounting ? accounting[1] : trimmed;\n\n const symbol = numberFormat(locale, USD).formatToParts(0).find((p) => p.type === \"currency\")!.value;\n const magnitude = parseLocalizedNumber(body.replace(/\\s/g, \"\"), locale, [symbol, \"$\"]);\n if (magnitude === null) return null;\n return negative ? -Math.abs(magnitude) : magnitude;\n}\n","/* ----------------------------------------------------------------------------\n A GENERIC NUMBER — a float or an integer, with thousands separators.\n\n NO FIXED PRECISION BY DEFAULT, unlike `formatCurrency`'s always-two-decimals\n rule. Money has a currency-defined minor unit, so \"how many decimals\" is\n never a real question; a generic number's field might be a whole count\n of pages, a percentage to one decimal, or a computed rate to four — there is\n no single right answer, so the caller states it (`decimals`) and the\n default preserves whatever precision the value actually carries (up to 6\n fraction digits, trimmed of trailing zeros) rather than inventing digits\n that were not there or discarding real ones.\n -------------------------------------------------------------------------- */\n\nimport { ABSENT } from \"./absent\";\nimport { numberFormat, parseLocalizedNumber } from \"../intl-cache\";\n\nexport interface FormatNumberOptions {\n /**\n * Fixed fraction digits, e.g. `1` for a percentage rendered as `\"42.0\"`.\n * Omit to show the value's own precision, up to 6 fraction digits, with no\n * trailing zeros invented.\n */\n decimals?: number;\n}\n\n/**\n * `1234.5` → `\"1,234.5\"` in `en-US`, `\"1.234,5\"` in `de-DE`. `null` / `undefined` → `\"—\"`.\n *\n * `NaN` / `Infinity` render as the em dash too, for the same reason\n * `formatCurrency` groups them with absence rather than showing them back:\n * the caller holds a `number`, not a string a person typed, so there is no\n * original text to preserve.\n */\nexport function formatNumber(value: number | null | undefined, locale: string, options: FormatNumberOptions = {}): string {\n if (value == null || !Number.isFinite(value)) return ABSENT;\n const { decimals } = options;\n const intlOptions: Intl.NumberFormatOptions =\n decimals === undefined\n ? { maximumFractionDigits: 6 }\n : { minimumFractionDigits: decimals, maximumFractionDigits: decimals };\n return numberFormat(locale, intlOptions).format(value);\n}\n\n/**\n * A human-typed number → a `number`, or `null`.\n *\n * Reads `locale`'s own convention — `\"1,234.5\"` in `en-US` and `\"1.234,5\"` in\n * `de-DE` are both `1234.5` — and accepts a leading `-` for a negative value. Rejects, rather than\n * silently truncating, anything `parseFloat` would otherwise accept a PREFIX\n * of — `\"12abc\"` is not twelve, it is a typo, and `parseFloat` alone would\n * return `12` and hide that.\n */\nexport function parseNumber(input: string, locale: string): number | null {\n const trimmed = input.trim();\n if (trimmed === \"\") return null;\n return parseLocalizedNumber(trimmed, locale);\n}\n","/* ----------------------------------------------------------------------------\n A TRI-STATE BOOLEAN — `true`, `false`, or nothing recorded.\n\n `\"Unknown\"`, NOT THE EM DASH, is the default for `null`/`undefined` here —\n the one deliberate departure from the convention every other formatter in\n this file follows. The em dash reads correctly as \"no value\" for a date, a\n number, a phone number: a blank field on a form. A boolean field usually\n means something was actually ASKED — \"was the patient transported by\n ambulance,\" \"is there a police report\" — and `null` there means the\n question has not been answered yet, which is a materially different fact\n from \"answered no.\" An em dash sits visually closer to \"false-ish\" than\n \"unanswered\" does; \"Unknown\" does not. `unknownLabel` exists to opt back\n into the em dash (or anything else) for a dense table where every column\n already uses it and a differently-worded cell would stand out for the\n wrong reason — the default is chosen for a single tri-state field read on\n its own, not for a grid.\n -------------------------------------------------------------------------- */\n\nimport type { BooleanStrings } from \"../i18n/areas/formats\";\n\nexport interface FormatBooleanOptions {\n /** Word for `true`. Default `strings.yes`. */\n trueLabel?: string;\n /** Word for `false`. Default `strings.no`. */\n falseLabel?: string;\n /** Word for `null` / `undefined`. Default `strings.unknown` — see the module doc\n * for why this is not the em dash the rest of this package uses for\n * absence. */\n unknownLabel?: string;\n}\n\n/**\n * `true` → `strings.yes`, `false` → `strings.no`, `null` / `undefined` →\n * `strings.unknown` (all three overridable via `options`).\n */\nexport function formatBoolean(\n value: boolean | null | undefined,\n strings: BooleanStrings,\n options: FormatBooleanOptions = {},\n): string {\n const { trueLabel = strings.yes, falseLabel = strings.no, unknownLabel = strings.unknown } = options;\n if (value === true) return trueLabel;\n if (value === false) return falseLabel;\n return unknownLabel;\n}\n\n/**\n * A human-typed word → `true`, `false`, or `null` for anything else\n * (including an empty string — a caller cannot distinguish \"typed nothing\"\n * from \"typed a real value\" if both return `false`).\n *\n * Matches case-insensitively against the spellings in `strings.trueWords` /\n * `strings.falseWords` (English: `yes`/`y`/`true`/`t`/`1` and `no`/`n`/`false`/\n * `f`/`0`). Deliberately does NOT guess at anything outside that list — a\n * typo like `\"yse\"` returning `null` for a caller to reject is correct;\n * returning `false` for it (or any other unrecognised text) would silently\n * record the opposite of an unreadable answer.\n */\nexport function parseBoolean(input: string, strings: BooleanStrings): boolean | null {\n const normalized = input.trim().toLowerCase();\n if (strings.trueWords.includes(normalized)) return true;\n if (strings.falseWords.includes(normalized)) return false;\n return null;\n}\n","/* ----------------------------------------------------------------------------\n THE FIVE-STEP CONFIDENCE SCALE, AS DATA — how a 1-5 rating READS.\n\n React-free on purpose (it is exported from `@danxbot/ui/pure`), because not\n every surface that draws a rating is a React consumer of this library: the\n demand letter's verification overlay is a Vue package, and it bundles these\n from here rather than keeping its own copy of the rounding rule.\n Its colours come the same way, from `dist/confidence.css`. One scale, one\n source — a second copy is how a letter and the screen beside it came to\n disagree about what a 4 is (SG-843 / SG-901).\n -------------------------------------------------------------------------- */\n\n/** One of the five steps of the confidence scale — see `ConfidenceBar` for how each is drawn. */\nexport type ConfidenceLevel = 1 | 2 | 3 | 4 | 5;\n\n/**\n * A RATING to one of the five confidence steps, or null when there is no\n * rating on a real scale to draw.\n *\n * A five-point rating maps one-to-one. Any other scale is mapped by its\n * fraction, so a colour and a length keep one meaning whatever scale a consumer\n * rates on. A rating below one step is \"not rated\", never level 1: a zero is\n * the absence of a rating, and drawing it as the lowest earned level would\n * claim a judgement nobody made.\n *\n * The fraction feeds a COLOUR and a LENGTH, never a printed figure — a step is\n * a judgement five values wide and survives the trip; a percentage is a figure\n * and does not.\n */\nexport function confidenceLevel(rating: number, scale: number): ConfidenceLevel | null {\n if (!Number.isFinite(rating) || !Number.isFinite(scale) || scale <= 0 || rating <= 0) return null;\n const step = Math.round((Math.min(rating, scale) / scale) * 5);\n return Math.max(1, Math.min(5, step)) as ConfidenceLevel;\n}\n\n/**\n * Each level's own word, the operator's scale (SG-843): 4 is \"Confident\" and 5\n * is \"Highly confident\", so the two readings of \"fine\" stay distinguishable in\n * words exactly as they are in colour and length.\n *\n * These are the ENGLISH words for a non-React consumer that cannot read the\n * provider (the demand letter's Vue overlay bundles them, SG-901). Components\n * read `confidenceBar.levels` from the provider, whose English set is this very\n * object, so there is one place the English lives.\n */\nexport const CONFIDENCE_LEVEL_LABELS: Readonly<Record<ConfidenceLevel, string>> = {\n // I18N-ALLOW: English reference word for consumers outside React; components render the provider's `confidenceBar.levels`\n 1: \"Just a guess\",\n // I18N-ALLOW: English reference word for consumers outside React; components render the provider's `confidenceBar.levels`\n 2: \"Somewhat unsure\",\n // I18N-ALLOW: English reference word for consumers outside React; components render the provider's `confidenceBar.levels`\n 3: \"Fairly confident\",\n // I18N-ALLOW: English reference word for consumers outside React; components render the provider's `confidenceBar.levels`\n 4: \"Confident\",\n // I18N-ALLOW: English reference word for consumers outside React; components render the provider's `confidenceBar.levels`\n 5: \"Highly confident\",\n};\n","/* ----------------------------------------------------------------------------\n THE CHART PALETTE, AS DATA (DX-3963) — the token names any chart reads.\n\n The values live in `styles/chart-palette.css`, one set per theme and per\n contrast level; this module is only the NAMES, so a chart component, a\n consumer's own SVG and a test all say `chartSeriesColor(2)` instead of each\n spelling `var(--chart-3)` by hand. React-free on purpose (exported from\n `@danxbot/ui/pure`): a server-rendered report or a non-React surface reads\n the same names.\n -------------------------------------------------------------------------- */\n\n/** How many categorical hues exist. A 9th series is never a 9th hue: it folds into \"Other\". */\nexport const CHART_SERIES_COUNT = 8;\n\n/** The eight categorical tokens, in the fixed order series are assigned. */\nexport const CHART_SERIES_TOKENS = [\n \"--chart-1\",\n \"--chart-2\",\n \"--chart-3\",\n \"--chart-4\",\n \"--chart-5\",\n \"--chart-6\",\n \"--chart-7\",\n \"--chart-8\",\n] as const;\n\n/** The one sequential hue: a single series of magnitudes (a ranked bar list). */\nexport const CHART_SEQUENTIAL_TOKEN = \"--chart-seq\" as const;\n\n/**\n * The CSS colour for the series at zero-based `index` — `var(--chart-1)` for\n * index 0. Throws past the eighth: a ninth series is folded into \"Other\" by\n * the caller, never given a generated hue.\n */\nexport function chartSeriesColor(index: number): string {\n const token = CHART_SERIES_TOKENS[index];\n if (!Number.isInteger(index) || token === undefined) {\n throw new Error(\n `chartSeriesColor: there are ${CHART_SERIES_COUNT} categorical hues (index 0-${CHART_SERIES_COUNT - 1}); got ${index}. Fold the rest into \"Other\".`,\n );\n }\n return `var(${token})`;\n}\n\n/** The CSS colour of the sequential hue. */\nexport const CHART_SEQUENTIAL_COLOR = `var(${CHART_SEQUENTIAL_TOKEN})`;\n","/**\n * The vocabulary of the document-level display preferences, and the one way a\n * value is written onto `<html>`.\n *\n * DX-4075: REACT-FREE ON PURPOSE. `preferences.ts` holds the hooks, so it imports\n * React; `@danxbot/ui/pure` may not reach React (`pure-entry.test.ts`). The framed\n * side of `SandboxedFrame` (`designFrameClient`) has to apply the host's\n * appearance with the very same write the host used and accept exactly the same\n * values, so the unions, the value lists and `applyPreference` live here, imported\n * by both. Two copies would be two chances for the host and the frame to drift.\n */\n\n/**\n * Writes one preference onto `<html>` with document-wide motion suspended for\n * the duration of the change.\n *\n * WHY THIS IS NOT JUST `setAttribute`. Colour transitions are a foundation in\n * this system: every element has one. That is right for an interaction — a\n * control easing as you hover it — and wrong for a preference, because\n * switching theme changes the computed colour of EVERY element at once.\n * Measured on this demo: 2,360 transitions started simultaneously and the page\n * took **957ms** to settle, 1,560ms on the chat route, for a token whose\n * duration is 110ms. A theme switch should be a cut, not a dissolve; what it\n * actually looked like was the whole document struggling.\n *\n * `--motion-scale: 0` is how it is suspended, rather than a blanket\n * `transition: none !important`. Every duration in the system is already\n * pre-multiplied by that scalar, so zeroing it turns them all off through the\n * layer that exists for the purpose, overrides nothing a component declared,\n * and cannot be forgotten by a component author.\n *\n * THE FORCED READ IS LOAD-BEARING. Style is normally recalculated once, after\n * this function has returned — by which time the attribute is gone and the\n * durations are back. Reading a computed value flushes the recalculation\n * synchronously, so the new colours are committed WHILE durations are still\n * zero, and no transition is ever started. Delete that line and the suspension\n * silently does nothing at all, which is why `audit:motion` measures the settle\n * time rather than the presence of the attribute.\n */\nexport function applyPreference(attribute: string, value: string) {\n const root = document.documentElement;\n root.setAttribute(\"data-motion-suspend\", \"\");\n root.setAttribute(`data-${attribute}`, value);\n void getComputedStyle(root).backgroundColor;\n root.removeAttribute(\"data-motion-suspend\");\n}\n\nexport type Theme = \"light\" | \"dark\";\nexport const THEMES = [\"light\", \"dark\"] as const;\n\nexport type UiScale = \"sm\" | \"md\" | \"lg\";\nexport const SCALES = [\"sm\", \"md\", \"lg\"] as const;\n\n/**\n * `design` is the palette as drawn; `aa` raises every token to the 4.5:1 floor.\n *\n * Opt-IN, not enforced, because enforcing the floor everywhere flattens the\n * hierarchy for everyone: de-emphasised text stops being de-emphasised, and in\n * this palette light-theme tertiary collapsed onto the same ramp step as\n * secondary. A floor is a floor — reachable in one click by anyone who needs\n * it, not a target that drags the whole design up to meet it.\n */\nexport type ContrastPreference = \"design\" | \"aa\";\nexport const CONTRAST = [\"design\", \"aa\"] as const;\n\n/**\n * `system` is a real third state, not a synonym for \"full\": it means defer to\n * the OS, which is what most people want and what the OS-level preference\n * exists to express. Collapsing it into a boolean would make an app-level\n * control that says \"full\" override a user who set reduced motion system-wide.\n */\nexport type MotionPreference = \"system\" | \"full\" | \"reduced\";\nexport const MOTION = [\"system\", \"full\", \"reduced\"] as const;\n","import {\n CONTRAST,\n MOTION,\n SCALES,\n THEMES,\n type ContrastPreference,\n type MotionPreference,\n type Theme,\n type UiScale,\n} from \"../preference-values\";\n\n/**\n * The messages a `SandboxedFrame` host and the document inside it exchange\n * (DX-4075). One definition, imported by both sides, so they cannot drift.\n *\n * REACT-FREE: this module is reachable from `@danxbot/ui/pure` (the framed side\n * is a harness of any framework), so nothing here may import React.\n *\n * NO SECRET TRAVELS THIS CHANNEL. The host posts the four appearance values and\n * nothing else, which is why it can post to targetOrigin \"*\": the frame's origin\n * is the string \"null\" and cannot be named anyway.\n */\n\nexport const DESIGN_FRAME_APPEARANCE = \"danx-design:appearance\";\nexport const DESIGN_FRAME_READY = \"danx-design:ready\";\nexport const DESIGN_FRAME_ERROR = \"danx-design:error\";\n\n/** The most characters of a frame's error message the host will accept. */\nexport const DESIGN_FRAME_ERROR_MAX_LENGTH = 500;\n\n/**\n * The host's Appearance. A field is absent when the host document has not set\n * that attribute (a host that never mounted the preference hooks); the frame then\n * leaves its own default alone rather than being told a guess.\n */\nexport interface DesignFrameAppearance {\n theme?: Theme;\n contrast?: ContrastPreference;\n uiScale?: UiScale;\n motion?: MotionPreference;\n}\n\n/** Host -> frame, on every change of the host's Appearance. */\nexport interface DesignFrameAppearanceMessage extends DesignFrameAppearance {\n type: typeof DESIGN_FRAME_APPEARANCE;\n}\n\n/** Frame -> host: the design has mounted and is listening for appearance. */\nexport interface DesignFrameReadyMessage {\n type: typeof DESIGN_FRAME_READY;\n}\n\n/** Frame -> host: the design failed. The host caps and treats `message` as text. */\nexport interface DesignFrameErrorMessage {\n type: typeof DESIGN_FRAME_ERROR;\n message: string;\n}\n\nexport type DesignFrameInboundMessage = DesignFrameReadyMessage | DesignFrameErrorMessage;\n\n/**\n * The four `<html data-*>` attributes that carry Appearance, by field. The names\n * and value domains are the library's own (`preference-values.ts`).\n */\nexport const APPEARANCE_ATTRIBUTES = {\n theme: \"theme\",\n contrast: \"contrast\",\n uiScale: \"ui-scale\",\n motion: \"motion\",\n} as const satisfies Record<keyof DesignFrameAppearance, string>;\n\nconst DOMAINS: Record<keyof DesignFrameAppearance, readonly string[]> = {\n theme: THEMES,\n contrast: CONTRAST,\n uiScale: SCALES,\n motion: MOTION,\n};\n\nconst FIELDS = Object.keys(APPEARANCE_ATTRIBUTES) as (keyof DesignFrameAppearance)[];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\n/** Reads the Appearance off an element's `data-*` attributes; an unset or foreign value is left out. */\nexport function readAppearance(root: Element): DesignFrameAppearance {\n const out: Record<string, string> = {};\n for (const field of FIELDS) {\n const value = root.getAttribute(`data-${APPEARANCE_ATTRIBUTES[field]}`);\n if (value !== null && DOMAINS[field].includes(value)) out[field] = value;\n }\n return out as DesignFrameAppearance;\n}\n\n/** Narrows an untrusted `event.data` to an appearance message; a value outside its domain is refused. */\nexport function parseAppearanceMessage(data: unknown): DesignFrameAppearanceMessage | null {\n if (!isRecord(data) || data.type !== DESIGN_FRAME_APPEARANCE) return null;\n const out: Record<string, unknown> = { type: DESIGN_FRAME_APPEARANCE };\n for (const field of FIELDS) {\n const value = data[field];\n if (value === undefined) continue;\n if (typeof value !== \"string\" || !DOMAINS[field].includes(value)) return null;\n out[field] = value;\n }\n return out as unknown as DesignFrameAppearanceMessage;\n}\n\n/** Narrows an untrusted `event.data` to a ready or error message. */\nexport function parseInboundMessage(data: unknown): DesignFrameInboundMessage | null {\n if (!isRecord(data)) return null;\n if (data.type === DESIGN_FRAME_READY) return { type: DESIGN_FRAME_READY };\n if (data.type === DESIGN_FRAME_ERROR && typeof data.message === \"string\") {\n return { type: DESIGN_FRAME_ERROR, message: data.message };\n }\n return null;\n}\n","import { applyPreference } from \"../preference-values\";\nimport {\n APPEARANCE_ATTRIBUTES,\n DESIGN_FRAME_ERROR,\n DESIGN_FRAME_READY,\n parseAppearanceMessage,\n type DesignFrameAppearance,\n type DesignFrameErrorMessage,\n type DesignFrameReadyMessage,\n} from \"./protocol\";\n\n/**\n * `designFrameClient` — the framed side of `SandboxedFrame` (DX-4075). A harness\n * of any framework imports it: no React is reachable from here.\n */\n\n/**\n * Applies each Appearance the host sends. `cb` is told what arrived, after it is\n * applied; the library's own `applyPreference` does the write, so a theme flip is\n * a cut rather than the dissolve of every transition in the document.\n *\n * Only a message from `window.parent` is believed, and only one whose values are\n * inside the library's own domains. Returns the unsubscribe.\n */\nfunction onAppearance(cb?: (appearance: DesignFrameAppearance) => void): () => void {\n const listener = (event: MessageEvent) => {\n if (event.source !== window.parent) return;\n const message = parseAppearanceMessage(event.data);\n if (!message) return;\n const appearance: DesignFrameAppearance = {};\n for (const [field, attribute] of Object.entries(APPEARANCE_ATTRIBUTES)) {\n const key = field as keyof DesignFrameAppearance;\n const value = message[key];\n if (value === undefined) continue;\n applyPreference(attribute, value);\n Object.assign(appearance, { [key]: value });\n }\n cb?.(appearance);\n };\n window.addEventListener(\"message\", listener);\n return () => window.removeEventListener(\"message\", listener);\n}\n\n/** Tells the host the design has mounted. Call it after `onAppearance`, so the reply is heard. */\nfunction ready(): void {\n const message: DesignFrameReadyMessage = { type: DESIGN_FRAME_READY };\n window.parent.postMessage(message, \"*\");\n}\n\n/** Reports a failure to the host, which caps it and shows it as text. */\nfunction reportError(message: string): void {\n const payload: DesignFrameErrorMessage = { type: DESIGN_FRAME_ERROR, message: String(message) };\n window.parent.postMessage(payload, \"*\");\n}\n\nexport const designFrameClient = { onAppearance, ready, reportError };\n"],"names":["numberFormats","dateFormats","numberFormat","locale","options","key","f","dateFormat","numberSeparators","parts","p","parseLocalizedNumber","text","strip","group","decimal","body","s","value","asUtcDate","year","month","day","d","localeDigits","digits","i","formatBytes","bytes","units","unit","formatTokens","tokens","formatCompactNumber","pad","n","width","AmbiguousTimeError","wall","zone","kind","isoDateTimeString","isoDateString","date","daysInMonth","parsePlainDate","iso","m","HOUR_MS","formatterFor","instantToPlainDateTime","instant","hour","minute","second","part","millisecond","wallAsUtcMs","offsetAt","sameWall","a","b","instantsFor","asUtc","offsetBefore","offsetAfter","found","offset","candidate","t","plainDateTimeToInstant","disambiguation","candidates","gap","earlier","later","DATE_OPTIONS","TIME_OPTIONS","wallDate","plainSpaces","patterns","BIDI_MARKS","escapeRegex","normalizeDigits","out","datePattern","pattern","lastField","last","order","source","literal","inner","dateFields","match","field","clocks","normalizePeriod","clockPattern","clock","probe","am","ABSENT","formatDate","ISO_FORMAT","parseDate","input","trimmed","typed","localZone","toInstant","normalized","ms","formatDateTime","TYPED_TIME","ISO_WITH_ZONE","parseDateTime","rawHour","period","USD","formatCurrency","parseCurrency","accounting","negative","symbol","magnitude","formatNumber","decimals","parseNumber","formatBoolean","strings","trueLabel","falseLabel","unknownLabel","parseBoolean","confidenceLevel","rating","scale","step","CONFIDENCE_LEVEL_LABELS","CHART_SERIES_COUNT","CHART_SERIES_TOKENS","CHART_SEQUENTIAL_TOKEN","chartSeriesColor","index","token","CHART_SEQUENTIAL_COLOR","applyPreference","attribute","root","THEMES","SCALES","CONTRAST","MOTION","DESIGN_FRAME_APPEARANCE","DESIGN_FRAME_READY","DESIGN_FRAME_ERROR","DESIGN_FRAME_ERROR_MAX_LENGTH","APPEARANCE_ATTRIBUTES","DOMAINS","FIELDS","isRecord","parseAppearanceMessage","data","onAppearance","cb","listener","event","message","appearance","ready","reportError","payload","designFrameClient"],"mappings":"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"}
@@ -87,7 +87,7 @@ export interface AppShellProps {
87
87
  * The default names it rather than leaving it bare; give an EMBEDDED shell a
88
88
  * distinct one.
89
89
  *
90
- * @default "Main"
90
+ * @default the provider's `appShell.mainNavigation`
91
91
  */
92
92
  navLabel?: string;
93
93
  /**
@@ -1,13 +1,11 @@
1
1
  import { type ReactNode } from "react";
2
- import type { AutoScrollOptions, CrossDrop, DragStrings } from "../lib/dnd";
2
+ import type { AutoScrollOptions, CrossDrop } from "../lib/dnd";
3
3
  import type { BoardColumnModel, BoardItemId, BoardReorderLabels, UseBoardOptions, UseBoardResult } from "../lib/board";
4
4
  export interface BoardProps<T> extends UseBoardOptions<T> {
5
- /** Forwarded to `DragDrop` — announcement grammar, localisation. */
6
- strings?: Partial<DragStrings>;
7
5
  /**
8
6
  * DX-4575: the words for each card menu's two within-column items. Partial,
9
- * so a consumer renames one and keeps the other. Omitted, "Move to top" and
10
- * "Move to bottom".
7
+ * so a consumer renames one and keeps the other. Omitted, the provider's
8
+ * `board.moveToTop` and `board.moveToBottom`.
11
9
  */
12
10
  reorderLabels?: Partial<BoardReorderLabels>;
13
11
  /**
@@ -65,7 +63,7 @@ export interface BoardProps<T> extends UseBoardOptions<T> {
65
63
  * returns. */
66
64
  children: (board: UseBoardResult<T>) => ReactNode;
67
65
  }
68
- export declare function Board<T>({ items, columns, accessors, validateMove, onMove, selection, strings, reorderLabels, crossLaneDrop, autoScroll, className, columnsClassName, renderDragging, children, }: BoardProps<T>): import("react").JSX.Element;
66
+ export declare function Board<T>({ items, columns, accessors, validateMove, onMove, selection, reorderLabels, crossLaneDrop, autoScroll, className, columnsClassName, renderDragging, children, }: BoardProps<T>): import("react").JSX.Element;
69
67
  export interface BoardCardSurfaceProps {
70
68
  className?: string;
71
69
  children?: ReactNode;
@@ -16,6 +16,14 @@ declare const card: import("tailwind-variants").TVReturnType<{
16
16
  root: string;
17
17
  };
18
18
  };
19
+ radius: {
20
+ xl: {
21
+ root: string;
22
+ };
23
+ md: {
24
+ root: string;
25
+ };
26
+ };
19
27
  padding: {
20
28
  none: {
21
29
  root: string;
@@ -64,6 +72,14 @@ declare const card: import("tailwind-variants").TVReturnType<{
64
72
  root: string;
65
73
  };
66
74
  };
75
+ radius: {
76
+ xl: {
77
+ root: string;
78
+ };
79
+ md: {
80
+ root: string;
81
+ };
82
+ };
67
83
  padding: {
68
84
  none: {
69
85
  root: string;
@@ -112,6 +128,14 @@ declare const card: import("tailwind-variants").TVReturnType<{
112
128
  root: string;
113
129
  };
114
130
  };
131
+ radius: {
132
+ xl: {
133
+ root: string;
134
+ };
135
+ md: {
136
+ root: string;
137
+ };
138
+ };
115
139
  padding: {
116
140
  none: {
117
141
  root: string;
@@ -154,7 +178,7 @@ export interface CardProps extends VariantProps<typeof card> {
154
178
  ref?: Ref<HTMLElement>;
155
179
  onClick?: (event: React.MouseEvent) => void;
156
180
  }
157
- export declare function Card({ variant, padding, interactive, selected, render, className, children, ref, ...props }: CardProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
181
+ export declare function Card({ variant, radius, padding, interactive, selected, render, className, children, ref, ...props }: CardProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
158
182
  export interface CardHeaderProps {
159
183
  title?: ReactNode;
160
184
  description?: ReactNode;
@@ -1,8 +1,9 @@
1
1
  import { type ReactNode, type Ref } from "react";
2
2
  import { Combobox as BaseCombobox } from "@base-ui/react/combobox";
3
3
  export interface ComboboxProps<Value> extends Omit<BaseCombobox.Root.Props<Value, boolean>, "children"> {
4
+ /** Omitted, the provider's `combobox.placeholder`. */
4
5
  placeholder?: string;
5
- /** Shown when the filter matches nothing. */
6
+ /** Shown when the filter matches nothing. Omitted, the provider's `combobox.noMatch`. */
6
7
  emptyMessage?: ReactNode;
7
8
  /** Accessible name, when there is no visible label. */
8
9
  "aria-label"?: string;
@@ -16,7 +16,9 @@ export interface CommandPaletteProps {
16
16
  items: CommandItem[];
17
17
  open: boolean;
18
18
  onOpenChange: (open: boolean) => void;
19
+ /** Omitted, the provider's `commandPalette.placeholder`. */
19
20
  placeholder?: string;
21
+ /** Omitted, the provider's `commandPalette.noMatch`. */
20
22
  emptyMessage?: ReactNode;
21
23
  }
22
24
  export declare function CommandPalette({ items, open, onOpenChange, placeholder, emptyMessage, }: CommandPaletteProps): import("react").JSX.Element;
@@ -7,10 +7,10 @@ export interface DateFieldProps {
7
7
  onEditingChange?: (editing: boolean) => void;
8
8
  /** The accessible name. Required: a date box with no name is a box nobody can find. */
9
9
  "aria-label": string;
10
- /** @default "MM/DD/YYYY" */
10
+ /** Omit for the built-in placeholder. A custom one is not translated for you: it must describe the locale's date shape (the order and separator `formatDate` prints for the provider locale). */
11
11
  placeholder?: string;
12
12
  disabled?: boolean;
13
- /** The sentence shown for typed text that is not a date. */
13
+ /** The sentence shown for typed text that is not a date. Omit for the built-in one. */
14
14
  invalidMessage?: string;
15
15
  className?: string;
16
16
  }
@@ -1,8 +1,6 @@
1
1
  import { type ReactNode } from "react";
2
- import { type AutoScrollOptions, type ContainerId, type CrossDrop, type DragAxis, type DragId, type DragSessionCallbacks, type DragStrings } from "../lib/dnd";
2
+ import { type AutoScrollOptions, type ContainerId, type CrossDrop, type DragAxis, type DragId, type DragSessionCallbacks } from "../lib/dnd";
3
3
  export interface DragDropProps extends DragSessionCallbacks {
4
- /** Override any subset of the announcement grammar — localisation, or a domain noun. */
5
- strings?: Partial<DragStrings>;
6
4
  /** Tune the edge auto-scroll. It cannot be turned off; see the banner. */
7
5
  autoScroll?: AutoScrollOptions;
8
6
  /**
@@ -19,7 +17,7 @@ export interface DragDropProps extends DragSessionCallbacks {
19
17
  className?: string;
20
18
  children?: ReactNode;
21
19
  }
22
- export declare function DragDrop({ strings, autoScroll, renderCarried, className, children, ...callbacks }: DragDropProps): import("react").JSX.Element;
20
+ export declare function DragDrop({ autoScroll, renderCarried, className, children, ...callbacks }: DragDropProps): import("react").JSX.Element;
23
21
  export interface DragDropContainerProps {
24
22
  id: ContainerId;
25
23
  /**