diff --git a/apps/pwa/src/components/CalendarShell.test.tsx b/apps/pwa/src/components/CalendarShell.test.tsx
index adcd83b..6d10b02 100644
--- a/apps/pwa/src/components/CalendarShell.test.tsx
+++ b/apps/pwa/src/components/CalendarShell.test.tsx
@@ -56,41 +56,6 @@ vi.mock('@schedule-x/event-modal', () => ({
})),
}))
-// Mock @schedule-x/calendar-controls so CalendarShell can create the plugin
-vi.mock('@schedule-x/calendar-controls', () => ({
- createCalendarControlsPlugin: vi.fn(() => ({
- name: 'calendarControls',
- beforeRender: vi.fn(),
- onRender: vi.fn(),
- setDate: vi.fn(),
- setView: vi.fn(),
- getDate: vi.fn(() => Temporal.Now.plainDateISO()),
- getView: vi.fn(() => 'month-grid'),
- setFirstDayOfWeek: vi.fn(),
- setLocale: vi.fn(),
- setViews: vi.fn(),
- setDayBoundaries: vi.fn(),
- setWeekOptions: vi.fn(),
- setCalendars: vi.fn(),
- setMinDate: vi.fn(),
- setMaxDate: vi.fn(),
- setMonthGridOptions: vi.fn(),
- setTimezone: vi.fn(),
- setResources: vi.fn(),
- getFirstDayOfWeek: vi.fn(),
- getLocale: vi.fn(),
- getViews: vi.fn(() => []),
- getDayBoundaries: vi.fn(),
- getWeekOptions: vi.fn(),
- getCalendars: vi.fn(() => ({})),
- getMinDate: vi.fn(),
- getMaxDate: vi.fn(),
- getMonthGridOptions: vi.fn(),
- getResources: vi.fn(() => []),
- getRange: vi.fn(() => null),
- })),
-}))
-
// Mock the API client
vi.mock('../api/client.js', () => ({
fetchMe: vi.fn(),
diff --git a/apps/pwa/src/components/CalendarShell.tsx b/apps/pwa/src/components/CalendarShell.tsx
index 9be6484..ac45271 100644
--- a/apps/pwa/src/components/CalendarShell.tsx
+++ b/apps/pwa/src/components/CalendarShell.tsx
@@ -14,10 +14,12 @@
* - Event popover: driven exclusively by Zustand openEventId via onEventClick → standalone
* EventDetailPopover; createEventModalPlugin and customComponents.eventModal are NOT used
* - Threat T-02d-01: all event fields are plain-text JSX children — no raw HTML injection
+ * - Navigation: Schedule-X's built-in header is the sole navigation bar (Today/‹›/view switcher
+ * + weekday-name row). The custom ViewToolbar and calendar-controls plugin have been removed.
*
* Layout:
- * - Phone (≤767px): AppNav top bar → ViewToolbar → calendar grid (primary focal point)
- * - Tablet/Desktop (≥768px): AppNav left sidebar (240px) + main area (ViewToolbar + grid)
+ * - Phone (≤767px): AppNav top bar → Schedule-X (header + grid)
+ * - Tablet/Desktop (≥768px): AppNav left sidebar (240px) + main area (Schedule-X header + grid)
*
* State branches:
* isLoading (initial) → SkeletonCalendar
@@ -37,7 +39,6 @@ import {
type CalendarType,
} from '@schedule-x/calendar'
import { createEventsServicePlugin } from '@schedule-x/events-service'
-import { createCalendarControlsPlugin } from '@schedule-x/calendar-controls'
import { fetchMe, fetchEvents } from '../api/client.js'
import { hydrateEvents } from '../lib/hydrateEvents.js'
@@ -45,7 +46,6 @@ import { buildCalendarConfig, SX_FIRST_DAY_OF_WEEK } from '../lib/calendarConfig
import { useCalendarStore } from '../store/calendarStore.js'
import { EventDetailPopover } from './EventDetailPopover.js'
import { AppNav } from './AppNav.js'
-import { ViewToolbar } from './ViewToolbar.js'
import { ColorLegend } from './ColorLegend.js'
import { SkeletonCalendar } from './SkeletonCalendar.js'
import { EmptyState } from './EmptyState.js'
@@ -96,7 +96,6 @@ export function CalendarShell() {
// Create plugins once (stable across renders)
const eventsService = useState(() => createEventsServicePlugin())[0]
- const calendarControls = useState(() => createCalendarControlsPlugin())[0]
// Build members list from /api/me for AppNav + ColorLegend
const members = useMemo(() => {
@@ -148,7 +147,7 @@ export function CalendarShell() {
},
},
},
- [eventsService, calendarControls],
+ [eventsService],
)
// Sync TanStack Query result into Schedule-X eventsService (Pitfall 4 guard)
@@ -205,9 +204,6 @@ export function CalendarShell() {
overflow: 'hidden',
}}
>
- {/* ViewToolbar */}
-
-
{/* Main calendar area */}
{isInitialLoading ? (
diff --git a/apps/pwa/src/components/ViewToolbar.tsx b/apps/pwa/src/components/ViewToolbar.tsx
deleted file mode 100644
index b15bbf5..0000000
--- a/apps/pwa/src/components/ViewToolbar.tsx
+++ /dev/null
@@ -1,192 +0,0 @@
-/**
- * ViewToolbar — calendar navigation and view switcher.
- *
- * UI-SPEC §ViewToolbar:
- * - Buttons: Today | < | > | [Day] [Week] [Month] [Agenda]
- * - Font: 13px label weight
- * - Active view: subtle surface tint (NOT accent) — --color-member-0 at 12% opacity
- * - Touch targets: 44px minimum height
- * - role="button", keyboard-activatable with Enter/Space
- *
- * Reviewer note: ViewToolbar is secondary chrome — accent colors NOT on chrome.
- * Active state uses a subtle surface tint, not the accent color directly.
- *
- * Schedule-X integration: navigation and view-switching via the official
- * @schedule-x/calendar-controls plugin (createCalendarControlsPlugin).
- * The plugin is created once in CalendarShell and passed down as `controls`.
- *
- * Zustand subscription: uses per-field selectors so ViewToolbar only re-renders
- * when selectedView or setSelectedView change — NOT when openEventId changes.
- */
-
-import { useCalendarStore } from '../store/calendarStore.js'
-import type { createCalendarControlsPlugin } from '@schedule-x/calendar-controls'
-
-type ViewId = 'day' | 'week' | 'month-grid' | 'month-agenda'
-
-interface ViewConfig {
- id: ViewId
- label: string
-}
-
-const VIEWS: ViewConfig[] = [
- { id: 'day', label: 'Day' },
- { id: 'week', label: 'Week' },
- { id: 'month-grid', label: 'Month' },
- { id: 'month-agenda', label: 'Agenda' },
-]
-
-type CalendarControlsPlugin = ReturnType
-
-interface ViewToolbarProps {
- controls: CalendarControlsPlugin | null
-}
-
-export function ViewToolbar({ controls }: ViewToolbarProps) {
- // Per-field selectors: do NOT subscribe to openEventId.
- // If the whole store is destructured unselected, every popover open/close
- // causes a re-render here, which flashes the toolbar (Bug B).
- const selectedView = useCalendarStore((s) => s.selectedView)
- const setSelectedView = useCalendarStore((s) => s.setSelectedView)
-
- /**
- * Navigate using the calendar-controls plugin API.
- * - today: setDate(Temporal.Now.plainDateISO())
- * - prev/next: read current date, step by the active view's unit, call setDate()
- *
- * Step mapping (matches Schedule-X built-in backward/forward behaviour):
- * day → ±1 day
- * week → ±1 week
- * month-grid → ±1 month
- * month-agenda → ±1 month
- */
- const navigate = (direction: 'prev' | 'next' | 'today') => {
- if (!controls) return
-
- if (direction === 'today') {
- controls.setDate(Temporal.Now.plainDateISO())
- return
- }
-
- // Get the current date from the controls plugin (returns Temporal.PlainDate)
- const current = controls.getDate()
-
- let step: Temporal.DurationLike
- if (selectedView === 'day') {
- step = { days: 1 }
- } else if (selectedView === 'week') {
- step = { weeks: 1 }
- } else {
- // month-grid or month-agenda
- step = { months: 1 }
- }
-
- const newDate = direction === 'prev' ? current.subtract(step) : current.add(step)
- controls.setDate(newDate)
- }
-
- const switchView = (viewId: ViewId) => {
- if (!controls) return
- controls.setView(viewId)
- setSelectedView(viewId)
- }
-
- const buttonBase: React.CSSProperties = {
- background: 'none',
- border: '1px solid var(--color-border)',
- cursor: 'pointer',
- minHeight: '44px',
- padding: '0 var(--space-3)',
- fontSize: 'var(--text-label-size)',
- fontWeight: 'var(--text-label-weight)',
- lineHeight: 'var(--text-label-line-height)',
- color: 'var(--color-text-primary)',
- borderRadius: 'var(--space-1)',
- fontFamily: 'var(--font-family-base)',
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- transition: 'background 0.1s',
- }
-
- const activeButtonStyle: React.CSSProperties = {
- ...buttonBase,
- // Subtle surface tint — NOT accent color (UI-SPEC 60/30/10 rule)
- // Active state: --color-member-0 at 12% opacity over white
- background: 'rgba(74, 144, 217, 0.12)',
- fontWeight: 600,
- border: '1px solid rgba(74, 144, 217, 0.3)',
- }
-
- return (
-
- {/* Today button */}
-
-
- {/* Prev / Next */}
-
-
-
- {/* Spacer */}
-
-
- {/* View switcher */}
-
- {VIEWS.map((view) => (
-
- ))}
-
-
- )
-}