# Calendar UX review · 12 September 2026

## CalendarWorkspace.swift

1. **High: keep the agenda reachable at large text sizes.** The month occupied
   a fixed sibling above the scrolling agenda. It now shares one scrolling List
   with the agenda. Native phone navigation uses an inline title to give the
   calendar more room. The selected calendar name appears in the title.

   Before: `VStack { monthGrid; List { agenda } }`
   After: `List { Section { monthHeader; monthGrid }; agenda }`

   An intermediate LazyVGrid-in-List version crashed in the iOS collection-view
   layout pass. The final month uses an eager Grid with six GridRows; the
   subsequent create/inspect/edit XCTest passed. The 42 date identities persist.

2. **Medium: distinguish today without relying on colour.** Today's date is
   bold and outlined; VoiceOver reports Today and event count. Selection retains
   the selected trait and uses the Calendar rose, fixing a mismatched blue.
   Event strips use their calendar's hue and retain the calendar's written name.

   Before: `.background(selected ? Color.accentColor : .clear)`
   After: rose selection plus today outline/bold and accessible event counts.

3. **Medium: let month controls adapt.** ViewThatFits moves controls below the
   month title when horizontal space is insufficient; buttons retain 44-point
   minimum targets. At accessibility sizes weekday initials preserve grid space,
   while every date's accessible label contains the complete day/date.

4. **Medium: disclose exclusive all-day ends.** Inspection previously labelled
   the stored exclusive date Ends, suggesting one extra day of duration. It now
   says Ends before and explains the boundary, matching the editor. All-day
   inspection omits the irrelevant time-zone row. Stored dates are unchanged.

## EventEditor.swift

**Medium: label the editable notes field for assistive technology.**

Before: `TextEditor(text: $event.notes)`
After: `TextEditor(text: $event.notes).accessibilityLabel("Notes")`

Priority addressed: agenda reachability and layout stability, then date meaning,
control adaptation and editor accessibility.

## Method and validation

Applied `swiftui-pro` design, accessibility and navigation references, plus
`swiftui-native-component-design` semantics/control/accessibility guidance.
Reviewed actual SwiftUI renders on Mac, iPhone and iPad; checked light mode and
accessibility-large Dynamic Type in dark mode on iPhone. Real forms/navigation
are exercised through the isolated authenticated Go fixture and XCTest.
No backend, auth, model retrieval or permission behavior changed.

Independent reviewer: `/root/canary_release_review`, dispatched as `gpt-6-astra`
with high reasoning; runtime identity not independently verified. Screenshots
and source corrections were reviewed independently. Full VoiceOver walkthrough,
localization and every accessibility size remain hands-on acceptance work.

## Menu-bar agenda refinement · 13 September

Pinned the compact month and selected-day heading above a dedicated scrolling
agenda. Day selection updates the existing observed model immediately and resets
the agenda scroll position. The header shows the selected day's event count
only after a successful load. Previously the month and events shared one scroll
region, hiding the agenda and making day selection appear ineffective.

Mac quick-add now uses a plain native multiline field inside a padded surface
with a thin inset focus outline, avoiding the rounded field's clipped outer
focus ring. Expanded quick-add remains scrollable; panel height is bounded by
the screen's available height while retaining space for the day agenda.

Validation: Mac build and signature verification; authenticated fixture renders
for a timed day, the following all-day event, and an empty day. The optional
NATIVE_REVIEW_DAY_SEQUENCE=1 review harness changes the same published selection
used by the month buttons, asserts each result and captures the live view without
reloading. This verifies reactive rendering, not physical mouse input.
Artifacts: /tmp/calendar-menu-dynamic.png, its .all-day.png and .empty.png suffixes.
