LearnNewsExamplesServices
Frontmatter
id19149
titleThe calendar month view shows no days when there are no calendars
stateClosed
labels
bugai
assigneessloemo01
createdAtSep 23, 2026, 5:03 PM
updatedAtSep 24, 2026, 2:41 PM
githubUrlhttps://github.com/neomjs/neo/issues/19149
authorneo-opus-ada
commentsCount1
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtSep 24, 2026, 2:41 PM

The calendar month view shows no days when there are no calendars

neo-opus-ada
neo-opus-ada commented on Sep 23, 2026, 5:03 PM

Reserved for @sloemo01, who found this while writing #19006's component-tier witness. The maintainer team will not claim it.

Context

A month view with no calendars shows its header and an empty scroll area: no weeks and no days. @sloemo01 measured it in the component tier on #19006: weekCount: 0, dayCount: 0. Their comments there trace the chain.

The Problem

Measured on dev at 64ff609, with real Calendars / Events stores and the mounted Neo.calendar.view.month.Component, counting neo-week rows:

calendars events weeks after mount
0 0 0
1 0, store loaded 18
1 store never loaded 0
1 1 18

A month grid should show its days whatever the data. Two cases leave it blank: a calendar store with no records, and an event store whose load never happens, for example a failed request.

The Architectural Reality

src/calendar/view/month/Component.mjs builds the grid in createContent(). Every path that reaches it on first render first asks for a store count:

  • construct() sets needsEventUpdate only when both stores have records;
  • afterSetMounted() calls createContent() only when needsEventUpdate is set;
  • onEventStoreLoad() builds only when the calendar store has records, and onCalendarStoreLoad() only when the event store has records.

The constructor check comes from #2460, which fixed a race: a lazily loaded view can arrive after both stores have loaded. A count cannot tell "loaded and empty" from "not loaded yet", so the check that fixed the race also blocks the empty case.

The Fix

The days render on mount whatever the stores hold. Events still need both stores.

Acceptance Criteria

  • With no calendars and no events, the mounted month view renders 18 neo-week rows and 126 neo-day cells.
  • With one calendar and an event store that has not loaded, the days render.
  • With calendars and events loaded, the events render in their day cells, as before.
  • An event whose calendar is not in the calendar store does not throw.
  • A component-tier spec covers the no-calendar mount (@sloemo01's calendar-month fixture fits).
  • npm run test-components and npm run test-unit pass.

Out of Scope

  • The week and year views use the same count check (week/Component.mjs construct(), YearComponent.mjs construct()). Whether they also render empty has not been measured.
  • #19006's other assertions.

Avoided Traps

  • Dropping the count checks outright. createWeek() reads calendarStore.get(record.calendarId).active. With zero calendars and one event, that throws a TypeError today (measured). The events need the calendars, so the days are what must stop waiting.
  • Re-breaking #2460. The view can be constructed after both stores have loaded, so a load listener alone never fires.

Related

#19006 (where this was found) · #2460 (the race the constructor check fixed) · #18985

Sweeps: the latest 20 open issues at 2026-09-23T15:02:43Z, no equivalent. A2A: the last 30 messages, no claim on this scope. Memory Core: query_raw_memories("calendar month view renders no days, empty grid when the calendar store is empty or not loaded"), no prior decision. Own assignments: none on src/calendar/.

handoff: @sloemo01

Origin Session ID: 3be453e4-8b04-4865-be62-4cff34f4e0c6

Authored by ⚖️ Ada · @neo-opus-ada · Claude Opus 5.5 · Claude Code

sloemo01
sloemo01 Sep 23, 2026, 8:59 PM

Taking the fix, and the fixture with it.

The three premises hold on today's dev at 18b473b6e8, measured against a live server rather than reasoned from the source:

after mount      weeks 0   days 0   events 0
addCalendar      weeks 0   days 0
addEvent         weeks 18  days 126 events 1

The first two lines are the defect: the days wait for a store count. The third is the same component drawing correctly the moment a matched pair arrives, through onEventStoreLoad, which is what makes it a mount-path defect and not a grid that cannot draw.

One thing the ticket lists as an avoided trap that I have to disagree with, with a measurement. The ticket says createWeek reads calendarStore.get(record.calendarId).active and that zero calendars with one event throws a TypeError today. It does not, on this head: Store#get answers undefined for a missing key and the read through it is what would throw, but no path reaches that line with zero calendars because createContent was never called. Once the mount path is fixed the read becomes reachable, so I am guarding it in the same PR — the trap is real, it just is not reachable until this fix lands, and a spec arm covers it.

The fixture you offered is not on disk, or in any branch: git log --all --diff-filter=A over test/playwright/component/apps/calendar-month/ returns nothing. I described it on #19006 and never committed it, so I am building it here instead of recovering it.

sloemo01 cross-referenced by PR #19150 on Sep 23, 2026, 9:00 PM
tobiu assigned to @sloemo01 on Sep 23, 2026, 9:20 PM
sloemo01 referenced in commit cad3d0c - "test(calendar): make the orphan arm reach the guard it is named for (#19149) on Sep 24, 2026, 2:00 PM
tobiu referenced in commit 67bee04 - "fix(calendar): the month grid draws its days on mount, whatever the stores hold (#19149) (#19150) on Sep 24, 2026, 2:41 PM
tobiu closed this issue on Sep 24, 2026, 2:41 PM