Skip to content

decision(plugin-calendar): a record with no value in the declared date field is silently placed on today — and the isNaN guard beside it structurally cannot catch that #7071

Description

@os-sam

Filed by the domain:ui seat (session session_013hfmP9hoMd3dJwTh85J4yB) on behalf of PR #7062, whose body states this was "Filed separately". It was not — the implementing agent's dedupe channel was rate-limited, so it declined to file blind and handed the finding back. This is that filing, re-measured against origin/main at 592acafbe.

⚠️ This is layer 3 of the chain already described in #7029's body. It is filed as its own card, not as a duplicate, because #7029's ruled deliverable closes a different trigger — see "What #7062 leaves" below. If a reviewer judges it belongs inside #7029 after all, close this as a duplicate; the seat would rather over-file than leave a landed PR pointing at a card that does not exist.

Measured

packages/plugin-calendar/src/ObjectCalendar.tsx:445:

start: startDate ? new Date(startDate) : new Date(),

A record with no value at the configured startDateField is given the current moment as its event start, and then renders as an ordinary event indistinguishable from a real one.

⭐ The guard six lines below cannot catch it, by construction

:451:

}).filter(event => !isNaN(event.start.getTime())); // Filter out invalid dates

That filter exists to drop unusable dates. It cannot fire here: new Date() with no argument is always a valid Date, so the fallback at :445 produces exactly the one value the guard is guaranteed to pass. The absent-value case is converted into a well-formed lie before the check that would have caught it.

Note the asymmetry two lines away — end: endDate ? new Date(endDate) : undefined falls back to undefined, which is honest. Only start fabricates. (allDay: !endDate then infers all-day from the missing end; adjacent, and probably fine, but it means one absent field silently changes two rendered properties.)

What #7062 leaves

Before #7062, this fired for views that declared no calendar config at all — the invented 'due_date' binding meant every record missed its field, which is how nine hotcrm records piled onto today's cell.

After #7062, the fabricated bindings are gone and the refusal screen catches the unconfigured view. The hole that remains is narrower and genuinely different: a view that correctly declares startDateField, over records where some rows have no value in that field. Those rows silently collect on today, mixed in with correctly placed ones — arguably harder to notice than the original, because the screen is now mostly right.

Why this is needs-user-decision and not a fix card

The correct behaviour is undecided, and each option is a real product choice with a different failure mode:

  1. Drop the event — clean, but data disappears from a view with no indication it was ever there.
  2. Bucket as "unscheduled" — honest and discoverable, but the calendar surface has no such affordance today, so this is a feature, not a fix.
  3. Badge it in place — keeps it visible, but on a date that is still a fabrication.
  4. Refuse the whole view — consistent with ObjectView invents startDateField: 'due_date' / titleField: 'name' for views with no calendar: block — the guess short-circuits ObjectCalendar's own refusal screen and piles every record on today #7029's ruled loud-over-silent direction, but disproportionate: one bad row would blank a working calendar.

#7029's ruling ("no invented field names") arguably reaches option 1 or 2 by extension, but it was ruled about view configuration, not record data, and the seat will not stretch a ruling across that boundary on its own. ObjectCalendar.tsx:657's existing refusal screen answers the config question, not this one.

⛔ Whoever takes this should not pick an option without a ruling.

Refs: #7029 · PR #7062 · #7070 (the sibling finding from the same handback) · objectstack#13748.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

domain:uiobjectui ui stream: fix lands on the published library or apps — objectui execution seat

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions