Repository navigation
Commit f9b6dfe
fix(plugin-calendar): a month-grid move keeps the wall-clock time across a DST change (objectui#11005) (#11036)
Fixes #11005
Clause-②: no
Ruling `5881826705` (letter B), PM claim `5882077455`, branch
`claude/issue-11005-month-grid-calendar-days`, dispatched from session
`https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec`. The month
grid now moves a value by calendar days and keeps its wall-clock time.
This holds for every value, for a move and for a drag of the span's end.
## What changes
1. **`CalendarView`, `MonthView.handleDrop`.** The move used to count
`deltaMs`, the milliseconds between the grab cell's local midnight and
the drop cell's, and add it to the start and the end. It now counts the
calendar days between the two cells from their local year, month and day
(`calendarDaysBetween`). The difference is taken on the UTC calendar, so
it is a whole number with no rounding. It then moves a local copy of the
start and of the end with `setDate` (`moveByCalendarDays`), leaving
hours, minutes, seconds and milliseconds untouched. The drag of a span's
end (`resize-end`) moves the end the same way, and its guards still
compare day cells.
2. **`ObjectCalendar`: the objectui#10866 slice-4 day repair is
removed** (`toMovedDateValue`, `isStoredDay`, `DAY_MS`). It was measured
redundant (see below). The default drop handler writes each field
through `toStoredDateValue` again, as it did before that slice: a `date`
field as the local day of the value the grid hands back, and a
`datetime` field as its instant.
3. **Pins.** A new zone pin, `CalendarView.monthMoveCalendarDays-11005`,
sits at the grid. It covers Los Angeles and Shanghai, plus two
suite-zone cases, and is registered in the driver's `PINS`
(`scripts/__tests__/date-only-zone-pins-10183.test.ts`). In
`ObjectCalendar.dstDayMove-10866`:
- The four `datetime` controls are renamed from "keeps the grid's
instants" to "keeps its wall-clock time".
- The first Los Angeles control's end moves from
`2026-11-02T17:00:00.000Z` (09:00 PST) to `2026-11-02T18:00:00.000Z`
(10:00 PST). This is the ruled instant change.
- Its header prose says what changed. Its ten `date` day moves are
untouched.
4. **Docs.** The plugin README's month-view drag table and the two
month-view bullets in `content/docs/plugins/plugin-calendar.mdx` now say
that a move and an end drag keep the time of day (AGENTS.md commandment
#2).
5. **Changesets.** `.changeset/11005-month-grid-calendar-days.md`
declares `@object-ui/plugin-calendar: patch`. The pending slice-4
changeset is reconciled in the dated-note form (see mechanism assumption
4).
## The ruled zone pins
These run under `TZ=America/Los_Angeles`, where the clocks fall back on
November 1st and spring forward on March 8th, 2026, and under
`TZ=Asia/Shanghai`. Base is `a33cf7e`.
| Ruled pin | Where | On base | On head |
|:--|:--|:--|:--|
| A datetime moved across a DST change keeps its wall-clock time | New
pin, four Los Angeles moves | A 10:00 event on Nov 1 moved two days
wrote 11:00. One moved back onto Nov 1 wrote 09:00. A 10:00 event on Mar
8 moved two days wrote 09:00. A span whose end crosses Nov 1 wrote its
end at 09:00. | 10:00 each |
| A local-midnight instant lands on the dropped day | New pin, two Los
Angeles spans | The card's case: a start of `2026-11-01T07:00:00.000Z`,
grabbed on Nov 2 and dropped on Nov 3, wrote `2026-11-02T07:00:00.000Z`
(Nov 1, 23:00). A span ending at midnight on Mar 9, moved back one day,
started at `2026-03-06T09:00:00.000Z` (01:00). | Midnight of the dropped
day |
| A date-only value is unchanged | Slice 4's ten day moves through
`ObjectCalendar`, Los Angeles and Shanghai | Written by the repair | The
same ten days, with the repair removed |
| A zone without DST behaves as before | New pin, four Shanghai drags |
Exactly N times 24 hours | The same instants |
| `resize-end` follows the same rule | New pin, two Los Angeles end
drags | Hours, minutes and seconds already kept; milliseconds dropped
(`19:30:15.000Z` for `19:30:15.250Z`) | The whole time of day |
## Mechanism assumptions (Zone 2), measured
1. **Partly held.** The move derived one `deltaMs` from local-midnight
cells. `resize-end` did not: it set the drop day's clock from the old
end with `setHours(h, m, s, 0)`, so it already kept hours, minutes and
seconds across a DST change. On base, the March 8th end drag passed and
the November 2nd one differed only in milliseconds. The resize guards
compare day cells, as assumed.
2. **Held: the week and day views do not ride.** The time grid places a
moved event at its snapped minutes on the drop day's own clock
(`setHours(0, snapped)` on the drop column's day). A throwaway probe in
a forked child under Los Angeles (not committed) moved a 10:00 event off
November 1st and onto it in the week view. It wrote 10:00 both ways:
`2026-11-03T18:00:00.000Z` and `2026-11-01T18:00:00.000Z`.
3. **Held: the repair is redundant, and B is what makes it so.** With B
in the grid and the repair removed, slice 4's ten day moves pass in both
zones. Ablation: put the base grid back under the committed head (repair
still removed), and four of those ten go red in Los Angeles. They are
exactly the four that slice 4 named (see Evidence).
4. **The named file does not describe the repair.**
`.changeset/10866-calendar-gantt-date-only-days.md` (slice 1) does not
describe the repair. Its calendar sentence "A `datetime` field keeps its
instant" is about the stored type, so it stays true under the ruling's
own reading, and the file is untouched. The repair is described in slice
4's `.changeset/10866-calendar-dst-day-moves.md`. Two phrases there go
false at this change: "The month grid moves each date…" and "…the grid's
own arithmetic does not change". Both are now scoped "at this change",
followed by a dated note naming this card. The front matter is
byte-identical (the md5 of the first six lines matches the base). Its
gantt and core bullets are untouched. `check-changeset-overwrite`
reports the modification (report-only, exit 0), and that is expected.
## Evidence
All counts are read from vitest's own summary lines.
- **New pin, red on base.** Before the fix, in a forked child with
`OBJECTUI_DATE_ZONE_CHILD=1`: `Tests 8 failed | 9 passed (17)`. The
eight red cases:
- the six Los Angeles moves;
- the Los Angeles November end drag (milliseconds only);
- the suite-zone end drag (milliseconds only).
The Los Angeles March end drag, the rigs, the fixture case and every
Shanghai case passed on base.
- **Head, all calendar zone pins driven.**
`CalendarView.monthMoveCalendarDays-11005`,
`ObjectCalendar.dstDayMove-10866`,
`ObjectCalendar.endDateNoSchemaZone-10866`,
`ObjectCalendar.dateOnlyZone-10866` and
`calendarViewRenderer.dateOnlyZone-10866`, forked with the flag: `Test
Files 5 passed (5)`, `Tests 104 passed (104)`.
- **Ablation.** Run on committed head `55d7add`. The base blob of
`CalendarView.tsx` was checked out on its own, with `ObjectCalendar.tsx`
staying at head (repair removed).
- Landing proof: after the mutation the file hashed to the base blob
`0fbcb72b`, and the `deltaMs` count went from 0 to 4 while the
`moveByCalendarDays` count went from 5 to 0.
- Result, forked, driven: `Tests 13 failed | 32 passed (45)`. The red
cases are the eight new-pin cases, slice 4's four Los Angeles day moves
(Oct 29 to 31 moved two days, Nov 1 to 3 grabbed on the 2nd, Oct 31 to
Nov 2 moved back one, Mar 5 to 9 moved back one), and the first Los
Angeles `datetime` control.
- Restore, from a `trap` on EXIT/INT/TERM with absolute paths: `git
checkout HEAD --` brought the file back to the HEAD blob `29701d6f`, and
`git diff HEAD` came back empty.
- **Package suite at `600e84c`.** `pnpm exec vitest run
packages/plugin-calendar/`: `Test Files 55 passed (55)`, `Tests 365
passed | 85 skipped (450)`. The skipped cases are zone pins, which run
in the driver's child.
- **Zone-pin driver at `600e84c`.**
`scripts/__tests__/date-only-zone-pins-10183.test.ts`: `Tests 24 passed
(24)`, which is every registered pin plus the child-exit case.
- **Tests outside the package that name an edited file**, derived with
`git grep` over test files for the edited paths and symbols. The zone
pins are excluded because the driver ran them. 14 files: `Test Files 14
passed (14)`, `Tests 1067 passed (1067)`. The 16 test files that read
`.changeset/`: `Test Files 16 passed (16)`, `Tests 618 passed (618)`.
- **Type-check.** The dependency closure was built with `turbo run build
--filter='@object-ui/plugin-calendar^...' --concurrency=2`: 13 tasks,
all restored from turbo's shared worktree cache, none of them in this
diff. Then `pnpm --filter @object-ui/plugin-calendar type-check`, which
echoed `tsc --noEmit && tsc -p tsconfig.test.json`, exited 0. `tsc -p
tsconfig.test.json --listFiles` lists both edited pins.
- **Gates, all exit 0:**
- `check-changeset-presence`: "4 source file(s) of 1 released package(s)
changed, and this change declares 1 changeset(s)".
- `check-changeset-no-major`, `-fixed`, `-overwrite` (report-only, see
above) and `-claims` (report-only). For `-claims`, five pending
changesets name `ObjectCalendar.tsx`, the README or the plugin page.
Each was re-read: they concern the record source, `filter`, `sort` and
the colour and all-day fields, and none is falsified.
- `check-pending-changeset-literals`, `check-control-bytes` and
`check-new-cross-file-line-citations` ("0 new citation(s)").
- `check-test-path-roots`, the three `check-vi-mock-*` gates and
`check-unreferenced-sources`.
- `check-doc-fence-languages`, `check-doc-component-types` and
`check-doc-example-ids`.
- `check-governed-queue-guard --test` over the nine paths: NOT GOVERNED.
- **NOT MEASURED: `check-doc-snippet-types` (exit 2),
`check-doc-example-types` (exit 2) and `check-readme-exports` (exit
1).** All three stopped on their prerequisite: packages outside this
closure are unbuilt, reported as `[unbuilt-package]` and "not on disk —
run the build first". That means the runs never reached a verdict, not
that the gates failed. The README and doc edits here change table rows
and bullets only, with no fenced block. These gates belong to CI.
- **Lint, narrowed and declared as a narrowing.**
- Population: the root config lints `**/*.{ts,tsx}`, and this diff has
five such files.
- Count: `eslint --no-inline-config --format json` returned 5 results
with no "ignored" message: 0 errors in total. Warnings equal each
modified file's base blob (`CalendarView.tsx` 24, `ObjectCalendar.tsx`
31), and the three test files have 0.
- Invariance: `eslint.config.js` sets no `parserOptions`, `project` or
`projectService`, so linting is not type-aware and this diff cannot move
the verdict for any untouched file.
Repo-wide lint is CI's.
## Acceptance notes
The dev records these here and files nothing; the seat decides.
- **A week or day view move of an overnight timed event shortens it**
(class (a): reproduced, pre-existing, unrelated to DST).
- The time grid clips a timed event into one piece per day it touches. A
move takes the grabbed piece's duration (`durationMin = entry.endMin -
entry.startMin`), not the event's.
- Measured with a throwaway jsdom probe (not committed): a 4-hour event
from January 12th 22:00 to January 13th 02:00, dragged by its first-day
piece to January 14th at 10:00, reached `onEventDrop` as 10:00 to 12:00,
which is 120 minutes. `ObjectCalendar`'s default drop handler writes the
`newEnd` it is handed (read, not driven through an app).
- Reach: the calendar's week and day view drag.
- Dedupe words: `week view drag overnight event duration`, `TimeGridView
move durationMin clipped piece`, `calendar time grid move shortens
event`.
- **objectui#10866 slice 5, which is serial behind this card.** Its
`ObjectCalendar` mixed-event half (a `date` start with a `datetime` end)
was not measured here. With the grid moving by calendar days, a `date`
start now comes back at local midnight of the dropped day. Re-price that
half against this head.
- **Observation, not measured, and out of this card's scope.** When a
gantt summary bar is dragged, `GanttView` shifts it and its descendants
by one `deltaMs` (the group move and its live preview). Its axis is
continuous time, so it is not known to be the same defect. It is not
touched here. Carrier: none.
Commits carry only the model-free trailer pair.
---
_Generated by [Claude
Code](https://claude.ai/code/session_011p7ikEivgXefNDaE5S5Uec)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 3ad6100 commit f9b6dfe
9 files changed
Lines changed: 411 additions & 95 deletions
File tree
- .changeset
- content/docs/plugins
- packages/plugin-calendar
- src
- __tests__
- scripts/__tests__
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
10 | | - | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
11 | 13 | | |
12 | 14 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
32 | | - | |
| 31 | + | |
| 32 | + | |
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
28 | 28 | | |
29 | 29 | | |
30 | 30 | | |
31 | | - | |
32 | | - | |
| 31 | + | |
| 32 | + | |
33 | 33 | | |
34 | 34 | | |
35 | 35 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
502 | 502 | | |
503 | 503 | | |
504 | 504 | | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
505 | 532 | | |
506 | 533 | | |
507 | 534 | | |
| |||
567 | 594 | | |
568 | 595 | | |
569 | 596 | | |
570 | | - | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
571 | 600 | | |
572 | 601 | | |
573 | 602 | | |
| |||
640 | 669 | | |
641 | 670 | | |
642 | 671 | | |
643 | | - | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
644 | 676 | | |
645 | | - | |
646 | | - | |
647 | | - | |
| 677 | + | |
| 678 | + | |
648 | 679 | | |
649 | 680 | | |
650 | 681 | | |
651 | 682 | | |
652 | | - | |
653 | | - | |
654 | | - | |
655 | | - | |
| 683 | + | |
656 | 684 | | |
657 | 685 | | |
658 | 686 | | |
659 | | - | |
| 687 | + | |
| 688 | + | |
660 | 689 | | |
661 | | - | |
662 | | - | |
663 | | - | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
664 | 699 | | |
665 | 700 | | |
666 | 701 | | |
667 | 702 | | |
668 | 703 | | |
669 | 704 | | |
670 | | - | |
| 705 | + | |
671 | 706 | | |
672 | | - | |
673 | | - | |
674 | | - | |
675 | | - | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
676 | 710 | | |
677 | 711 | | |
678 | | - | |
| 712 | + | |
679 | 713 | | |
680 | 714 | | |
681 | 715 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
346 | 346 | | |
347 | 347 | | |
348 | 348 | | |
349 | | - | |
350 | | - | |
351 | | - | |
352 | | - | |
353 | | - | |
354 | | - | |
355 | | - | |
356 | | - | |
357 | | - | |
358 | | - | |
359 | | - | |
360 | | - | |
361 | | - | |
362 | | - | |
363 | | - | |
364 | | - | |
365 | | - | |
366 | | - | |
367 | | - | |
368 | | - | |
369 | | - | |
370 | | - | |
371 | | - | |
372 | | - | |
373 | | - | |
374 | | - | |
375 | | - | |
376 | | - | |
377 | | - | |
378 | | - | |
379 | | - | |
380 | | - | |
381 | | - | |
382 | | - | |
383 | | - | |
384 | | - | |
385 | | - | |
386 | | - | |
387 | | - | |
388 | | - | |
389 | | - | |
390 | 349 | | |
391 | 350 | | |
392 | 351 | | |
| |||
1104 | 1063 | | |
1105 | 1064 | | |
1106 | 1065 | | |
1107 | | - | |
1108 | | - | |
1109 | | - | |
| 1066 | + | |
| 1067 | + | |
| 1068 | + | |
| 1069 | + | |
| 1070 | + | |
1110 | 1071 | | |
1111 | | - | |
1112 | | - | |
1113 | | - | |
1114 | 1072 | | |
1115 | | - | |
| 1073 | + | |
1116 | 1074 | | |
1117 | 1075 | | |
1118 | | - | |
| 1076 | + | |
1119 | 1077 | | |
1120 | 1078 | | |
1121 | 1079 | | |
| |||
0 commit comments