42 Commits

Author SHA1 Message Date
3dcf9d1671 Fix isSharedEvent excluding calendar owners — lock banner never appeared for owners
The selectedEventIsShared check used `permissionMap.get(...) !== 'owner'` which
excluded calendar owners from all shared-event behavior (lock polling, lock
acquisition, lock banner display). Replaced with a sharedCalendarIds set that
includes both owned shared calendars (via cal.is_shared) and memberships.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:41:47 +08:00
e62503424c Fix event lock system: banner persistence, stale isEditing guard, and Edit button gating
- Remove isEditing guard from viewLockQuery effect so lock banner shows for user B
  even after user A transitions into edit mode (fixes banner disappearing)
- Disable Edit button proactively when lockInfo.locked is already known from polling,
  preventing the user from even attempting acquireLock when a lock is active
- Fix acquire callback dep array in useEventLock (missing acquireMutation)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:26:22 +08:00
c55af91c60 Fix two shared calendar bugs: lock banner missing and calendar not found on save
Bug 1 (lock banner): Owners bypassed lock acquisition entirely, so no DB lock
was created when an owner edited a shared event. Members polling GET
/shared-calendars/events/{id}/lock correctly saw `locked: false`. Fix: remove
the `myPermission !== 'owner'` guard in handleEditStart so owners also acquire
a temporary 5-min edit lock when editing shared events, making the banner
visible to all other members.

Bug 2 (calendar not found on save): PUT /events/{id} called
_verify_calendar_ownership whenever calendar_id appeared in the payload, even
when it was unchanged. For shared-calendar members this always 404'd because
they don't own the calendar. Fix: add `update_data["calendar_id"] !=
event.calendar_id` to the guard — ownership is only verified when the calendar
is actually being changed (existing M-01 guard handles the move-off-shared case).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:16:35 +08:00
38334b77a3 Shrink color picker swatches (h-8 → h-6)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 17:02:29 +08:00
a2f4d297a3 Single-line member rows + purple umbral name
- Flatten member row to strict single line: avatar | name | umbral name (violet) | pending badge | permission toggle | controls
- Umbral name shown in text-violet-400 for visual differentiation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:58:51 +08:00
1b36e6b6a7 Widen shared calendar dialogs + single-line member rows
- CalendarForm: max-w-3xl when sharing (was sm:max-w-2xl, overridden by base max-w-xl)
- SharedCalendarSettings: max-w-2xl (was sm:max-w-lg)
- CalendarMemberRow: back to single-line with PermissionToggle inline (less cramped)
- Use unprefixed max-w classes so twMerge properly overrides DialogContent base

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:54:25 +08:00
b401fd9392 Phase 6: Real-time sync, drag-drop guards, security fix, invite bug fix, UI polish
- Event polling (5s refetchInterval) so collaborators see changes without refresh
- Lock status polling in EventDetailPanel view mode — proactive lock banner
- Per-event editable flag blocks drag on read-only shared events
- Read-only permission guard in handleEventDrop/handleEventResize
- M-01 security fix: block non-owners from moving events off shared calendars (403)
- Fix invite response type (backend returns list, not wrapper object)
- Remove is_shared from CalendarCreate/CalendarUpdate input schemas
- New PermissionToggle segmented control (Eye/Pencil/Shield icons)
- CalendarMemberRow restructured into spacious two-line card layout
- CalendarForm dialog widened (sm:max-w-2xl), polished invite card with accent border
- SharedCalendarSettings dialog widened (sm:max-w-lg)
- CalendarMemberList max-height increased (max-h-48 → max-h-72)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 16:46:15 +08:00
14fc085009 Phase 5: Shared calendar polish — scoped polling, admin stats, dual panel fix, edge case handling
- Scope shared calendar polling to CalendarPage only (other consumers no longer poll)
- Add admin sharing stats card (owned/member/invites sent/received) in UserDetailSection
- Fix dual EventDetailPanel mount via JS media query breakpoint (replaces CSS hidden)
- Auto-close panel + toast when shared calendar is removed while viewing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 15:38:52 +08:00
f45b7a2115 Fix 4 reported bugs from Phase 4 testing
1. Invite auto-sends at read_only: now stages connection with permission
   selector (Read Only / Create Modify / Full Access) before sending
2. Shared calendars missing from event create dropdown: members with
   create_modify+ permission now see shared calendars in calendar picker
3. Shared calendar category not showing for owner: owner's shared calendars
   now appear under SHARED CALENDARS section with "Owner" badge
4. Event creation not updating calendar: handlePanelClose now invalidates
   calendar-events query to ensure FullCalendar refreshes

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 06:23:45 +08:00
e5690625eb Fix member removal bug + QA fixes + shared calendar sidebar styling
Bug fix:
- CalendarMemberRow: add type="button" to remove button (was submitting parent form)

QA fixes:
- EventDetailPanel: use axios.isAxiosError() instead of duck-typing for lock errors
- EventDetailPanel: only call onSaved on create (edits return to view mode, not close)
- CalendarForm: remove 4 redundant membersQuery.refetch() calls (mutations already invalidate)
- useEventLock: remove unused lockHeld ref from return, fix stale eventId in onSuccess
- EventLockBanner: guard against invalid date parse

UI:
- SharedCalendarSection: add purple Ghost icon next to "SHARED CALENDARS" header

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 06:07:55 +08:00
eedfaaf859 Phase 4: Event locking + permission gating for shared calendars
- useEventLock hook with auto-release on unmount/event change
- EventLockBanner component for locked event display
- EventDetailPanel: lock acquire on edit, release on save/cancel, permission-gated edit/delete buttons
- CalendarPage: permission map from owned+shared calendars, per-event editable gating

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 05:24:43 +08:00
4e3fd35040 Phase 3: Frontend core for shared calendars
Types: CalendarPermission, SharedCalendarMembership, CalendarMemberInfo,
CalendarInvite, EventLockInfo. Calendar type extended with is_shared.

Hooks: useCalendars extended with shared calendar polling (5s).
useSharedCalendars for member CRUD, invite responses, color updates.
useConnections cascade invalidation on disconnect.

New components: PermissionBadge, CalendarMemberSearch,
CalendarMemberRow, CalendarMemberList, SharedCalendarSection,
SharedCalendarSettings (non-owner dialog with color, members, leave).

Modified: CalendarForm (sharing toggle, member management for owners),
CalendarSidebar (shared calendars section with localStorage visibility),
CalendarPage (shared calendar ID integration in event filtering),
NotificationToaster (calendar_invite toast with accept/decline),
NotificationsPage (calendar_invite inline actions + type icons).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 04:59:13 +08:00
6cd648f3a8 Replace native date/time inputs with DatePicker across calendar and todo forms
- EventForm + EventDetailPanel: native <Input type=date|datetime-local> → DatePicker with dynamic mode via all_day toggle
- TodoForm + TodoDetailPanel: merge date + time into single datetime DatePicker, remove separate time input, move recurrence select into 2-col grid beside date picker

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 16:43:14 +08:00
a128005ae5 Fix create-mode crash in detail panels (null entity access)
The desktop detail panels are pre-mounted (always in DOM, hidden with w-0).
useState(isCreating) only captures the initial value on mount (false), so
when isCreating later becomes true via props, isEditing stays false. The
view-mode branch then runs with a null entity, crashing on property access.

Fix: use (isEditing || isCreating) for all conditionals that gate between
edit/create form and view mode, ensuring the form always renders when
isCreating is true regardless of isEditing state.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 09:57:19 +08:00
f07ce02576 Fix crash when creating new todo/reminder/event (null.priority)
All three DetailPanel components initialized isEditing=false even
when isCreating=true. The useEffect that flips it to true runs AFTER
the first render, so the view-mode branch executes with todo=null,
crashing on null.priority. Initialize isEditing from isCreating.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-27 08:35:31 +08:00
87a7a4ae32 Reformat detail panel view modes to 2-column grid layout
Match TaskDetailPanel gold standard: short fields use grid grid-cols-2
with icon+label headers, full-width fields (description) below the grid.
All grid slots render with "—" fallback to keep alignment consistent.

- Todo: Priority, Category, Due Date, Recurrence in grid
- Reminder: Status, Recurrence, Remind At, Snoozed Until in grid
- Calendar: Calendar, Starred, Start, End, Location, Recurrence in grid
- Task: Add "Updated X ago" footer (was missing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:52:15 +08:00
2e2466bfa6 Fix People search: alignment, focus ring, and name matching
- Wrap CategoryFilterBar in flex-1 min-w-0 so search aligns right
- Add first_name, last_name, nickname to People search filter
- Add ring-inset to all header search inputs (People, Todos,
  Locations, Reminders, Calendar) to prevent focus ring clipping

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:35:40 +08:00
057b43627b Smooth calendar resize during panel transitions
Replace single delayed updateSize() with requestAnimationFrame loop
that continuously resizes FullCalendar throughout the 300ms CSS
transition, eliminating the visual desync between panel and calendar.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 23:13:40 +08:00
8945295e2a Replace Sheet overlays with inline detail panels across all pages
- Calendar: move view selector left, inline EventDetailPanel with view/edit/create
  modes, fix resize on panel close, remove all Sheet/Dialog usage
- Todos: add TodoDetailPanel with inline view/edit/create, replace CategoryFilterBar
  with shared component (drag-and-drop categories), 55/45 split layout
- Reminders: add ReminderDetailPanel with inline view/edit/create, 55/45 split layout
- Dashboard: all widget items now deep-link to destination page AND open the
  relevant item's detail panel (events, todos, reminders)
- Fix TS errors: unused imports, undefined→null coalescing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:43:06 +08:00
898ecc407a Stage 7: final polish — transitions, navigation, calendar panel
- Add animate-fade-in page transitions to all pages
- Persist sidebar collapsed state in localStorage
- Add two-click logout confirmation using useConfirmAction
- Restructure Todos header: replace <select> with pill filters, move search right
- Move Reminders search right-aligned with spacer
- Add event search dropdown + Create Event button to Calendar toolbar
- Add search input to Projects header with name/description filtering
- Fix CategoryFilterBar search focus ring clipping with ring-inset
- Create EventDetailPanel: read-only event view with copyable fields,
  recurrence display, edit/delete actions, location name resolution
- Refactor CalendarPage to 55/45 split-panel layout matching People/Locations
- Add mobile overlay panel for calendar event details
- Add navigation state handler for CalendarPage (date/view from dashboard)
- Add navigation state handler for ProjectsPage (status filter from dashboard)
- Make all dashboard widgets navigable: stat cards → pages, week timeline
  days → calendar day view, upcoming items → source pages, countdown items
  → calendar, today's events/todos/reminders → respective pages

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 22:08:08 +08:00
f5265a589e Fix form validation: red outline only on submit, add required asterisks
- Remove instant invalid:ring/border from Input component (was showing
  red outline on empty required fields before any interaction)
- Add CSS rule: form[data-submitted] input:invalid shows red border
- Add global submit listener in main.tsx that sets data-submitted on forms
- Add required prop to Labels missing asterisks: PersonForm (First Name),
  LocationForm (Location Name), CalendarForm (Name), LockScreen
  (Username, Password, Confirm Password)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 17:53:15 +08:00
27003374e3 Fix QA review warnings: model server_default, calendar_id coercion
- EventTemplate model: add server_default=func.now() to created_at
  to match migration 011 and prevent autogenerate drift
- CalendarPage: use nullish coalescing for template calendar_id
  instead of || 0 which produced an invalid falsy ID

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 13:50:31 +08:00
f64e181fbe Fix template form: location picker auto-open and event title wording
- LocationPicker: skip initial mount effect so dropdown doesn't auto-open
  when form loads with an existing location value
- EventForm: separate templateData/templateName props from event prop so
  template-based creation shows "Create Event from X Template" title
  instead of "Edit Event", and correctly uses Create button + no Delete
- CalendarPage: pass templateName through to EventForm

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 13:10:31 +08:00
b21343601b Convert TemplateForm from Dialog to Sheet side panel
Matches the EventForm UI pattern for consistency — same slide-in panel,
same layout structure with scrollable content area and pinned footer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:58:55 +08:00
4a8c44ab80 Widen template form dialog from max-w-xl to max-w-2xl
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 11:51:59 +08:00
b1075d6ad4 Remove duration_minutes from event templates, auto-prefill event times
- Drop duration_minutes column from event_templates (model, schema, migration)
- Remove duration field from TemplateForm UI and TypeScript types
- EventForm now defaults start to current date/time and end to +1 hour
  when no initial values are provided (new events and template-based events)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-23 10:55:27 +08:00
5c8b3f895d Fix template event creation, add location to templates, fix timestamps
- EventForm: check event?.id instead of event to decide PUT vs POST,
  fixes "unable to parse string as integer" when creating from template
- TemplateForm: add LocationPicker for setting location on templates
- docker-compose: set TZ=Australia/Perth on backend and db containers
  so datetime.now() and PostgreSQL NOW() return local time

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 19:11:48 +08:00
80f3f3ed10 Calendar enhancements: scroll navigation, birthday color editing, event templates
- Add wheel scroll navigation in month view (debounced, prevents rapid scrolling)
- Allow editing color on system calendars (Birthdays) - name field disabled
- Event templates: full CRUD backend (model, schema, router, migration 011)
- Event templates: sidebar section with create/edit/delete, click to pre-fill EventForm
- Register event_templates router at /api/event-templates

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 17:34:16 +08:00
4169c245c2 Global enhancements: none priority, optional remind_at, required labels, textarea flex, remove color picker
- Add "none" priority (grey) to task/todo schemas, types, and all priority color maps
- Make remind_at optional on reminders (schema, model, migration 010)
- Add required prop to Label component with red asterisk indicator
- Add invalid:ring-red-500 to Input, Select, Textarea base classes
- Mark mandatory fields with required labels across all forms
- Replace fixed textarea rows with min-h + flex-1 for auto-expand
- Remove color picker from ProjectForm
- Align TaskRow metadata into fixed-width columns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 11:58:19 +08:00
56876841c7 Fix scope dialog button alignment and night briefing showing wrong day
- Replace DialogFooter with plain div for vertical button layout in scope dialog
- Add today's remaining items to night briefing (before 5 AM) before tomorrow preview

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 02:13:16 +08:00
e22cad1d86 Fix QA findings: firstDay reactivity, state revert, helper extraction
- W1: Add key prop to FullCalendar so firstDay change triggers remount
- W2: Revert firstDayOfWeek toggle state on API failure
- S1: Extract _rule_int helper in recurrence service to reduce duplication

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 02:02:15 +08:00
f66ffba0ef Fix calendar not resizing when sidebar collapses
Use ResizeObserver on the calendar container to call
FullCalendar.updateSize() when the sidebar transition
changes the available width.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 01:39:23 +08:00
c62f8bc2a2 Add first day of week setting and fix calendar header alignment
- Add first_day_of_week column to settings (0=Sunday, 1=Monday)
- Add Calendar section in Settings with toggle button
- Pass firstDay to FullCalendar from settings
- Align calendar toolbar and sidebar header to h-16 (matches UMBRA header)
- Remove border/padding wrapper from calendar grid for full-width layout

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 01:33:45 +08:00
5701e067dd Fix issues from QA review: critical bugs, warnings, and accessibility
- C1: Nominatim search already uses run_in_executor (non-blocking)
- C2: Ensure target event is deleted in "this_and_future" scope
- W3: Add Field constraints (ge/le) on RecurrenceRule fields
- W4: Add safety cleanup for body overflow on Sheet unmount
- W5: Block drag-drop/resize on recurring events (must use scope dialog)
- W6: Discard stale LocationPicker responses via request ID
- S8: Add role="dialog" and aria-modal to Sheet component

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 01:22:57 +08:00
f826d05c60 Fix recurrence edit/delete scope and simplify weekly UI
- Weekly recurrence no longer requires manual weekday selection;
  auto-derives from event start date
- EventForm now receives and forwards editScope prop to API
  (edit_scope in PUT body, scope query param in DELETE)
- CalendarPage passes scope through proper prop instead of _editScope hack
- Backend this_and_future: inherits parent's recurrence_rule when child
  has none, properly regenerates children after edit
- Backend: parent-level edits now delete+regenerate all children

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 01:08:00 +08:00
d811890509 Add Sheet forms, recurrence UI, all-day fix, LocationPicker
- Sheet component: slide-in panel replacing Dialog for all forms
- EventForm: structured recurrence picker, all-day end-date offset fix,
  LocationPicker with OSM search integration
- CalendarPage: scope dialog for editing/deleting recurring events
- TodoForm/ReminderForm/LocationForm: migrated to Sheet with 2-col layouts
- LocationPicker: debounced search combining local DB + Nominatim results
- Backend: /locations/search endpoint with OSM proxy
- CSS: slimmer all-day event bars in calendar grid
- Types: RecurrenceRule interface, extended CalendarEvent fields

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-22 00:42:12 +08:00
5b056cf674 Add calendar redesign frontend with multi-calendar UI
- Custom toolbar replacing FullCalendar defaults (nav, today, view switcher)
- Calendar sidebar with visibility toggles, color dots, add/edit support
- CalendarForm dialog for creating/editing calendars with color swatches
- EventForm updated to use calendar dropdown instead of color picker
- CSS overrides: accent-tinted today highlight, now indicator, rounded event pills
- Types updated for Calendar interface and mixed id types

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-21 19:14:06 +08:00
ca8b654471 Dashboard Phase 2: weather widget, starred events, quick add, thinner events
- Add weather router with OpenWeatherMap integration and 1-hour cache
- Add is_starred column to calendar events with countdown widget
- Add weather_city setting with Settings page input
- Replace people/locations stats with open todos count + weather card
- Add quick-add dropdown (event/todo/reminder) to dashboard header
- Make CalendarWidget events single-line thin rows
- Add rain warnings to smart briefing when chance > 40%
- Invalidate dashboard/upcoming queries on form mutations
- Migration 004: is_starred + weather_city columns

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-20 13:15:43 +08:00
c5adc316ef resolved event create bug 2026-02-19 20:44:14 +08:00
d5080f59af fixed day view visual bug 2026-02-19 20:38:11 +08:00
a352a50b63 resolved click & drag bug 2026-02-19 20:24:41 +08:00
1f6519635f Initial commit 2026-02-15 16:13:41 +08:00