Three layered effects to make the dashboard feel alive:
1. DashboardAmbient: two accent-colored drifting orbs at very low opacity
(0.04/0.025) with 120px blur — subtle depth and movement
2. Noise texture + radial vignette via CSS pseudo-elements — breaks the
flat digital surface and draws focus to center content
3. Card breathe animation: data-driven 4s pulsing glow on CalendarWidget
(when event in progress) and TodoWidget (when overdue todos exist)
All effects respect prefers-reduced-motion, use accent CSS vars (works
with any user-chosen accent color), and are GPU-composited (transform +
opacity only) for negligible performance cost.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The type pill, time label, and priority pill were being removed on
hover and replaced with action buttons, causing layout reflow and
visible jitter. Now the labels stay rendered (invisible when hovered
for todos/reminders) to hold their space, and action buttons are
absolutely positioned on top. Events show no actions so their labels
stay visible on hover. Zero layout shift.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Wrap TodoWidget in flex-1 container and add h-full to its Card so
the Upcoming Todos card stretches to fill remaining space in the
right column, keeping both columns visually aligned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace fixed maxHeight 520px with h-full + overflow-hidden so the
card stretches to match the right column height in the grid row.
The flex chain (Card flex-col → CardContent flex-1 min-h-0 →
ScrollArea flex-1 min-h-0) ensures content scrolls internally
within the row-determined height instead of capping independently.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
C-01: Add 30-day lower bound on overdue todo/reminder queries to
prevent fetching entire history.
C-02: Remove dead include_past query param — past-event filtering
is handled client-side.
W-01: Add onError toast handlers to all three inline mutations.
W-02: Snooze dropdown opens upward (bottom-full) to avoid clipping
inside the ScrollArea overflow container.
S-06: Clamp getMinutesUntilTomorrowMorning() to max 1440 to stay
within ReminderSnooze schema bounds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: h-full on Card inside a flex-col parent with no explicit
height meant nothing constrained the card — ScrollArea max-h never
triggered overflow. Fix: Card uses maxHeight 520px as the outer cap,
flex-col layout with shrink-0 header, and min-h-0 on CardContent +
ScrollArea so the flex chain allows content to shrink and scroll.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The flex-col h-full layout caused the card to stretch to match the
grid row, pushing content beyond the ScrollArea max-height. Switched
to natural card height with max-h-[400px] on ScrollArea so the card
stays compact and scrolls internally without mismatching the right
column cards.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restore max-h-[400px] on ScrollArea so the widget caps and scrolls
instead of growing unbounded and making cards uneven. All-day events
now show "All day" instead of the misleading "12:00 AM" time.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
/8 is not in Tailwind's default opacity scale so the classes were
purged. Use /[0.08] arbitrary value syntax instead.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Removes the always-visible 2px colored left border from each row.
On hover, the row background now glows with the type color at 8%
opacity (blue for todos, purple for events, orange for reminders).
Cleaner at rest, still provides type recognition on interaction.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend: Include overdue todos and snoozed reminders in /upcoming response,
add end_datetime/snoozed_until/is_overdue fields, widen snooze schema to
accept 1-1440 minutes for 1h/3h/tomorrow options.
Frontend: Full UpcomingWidget rewrite with sticky day separators (Today
highlighted in accent), collapsible groups, past-event toggle, focus mode
(Today + Tomorrow), color-coded left borders, compact type pills, relative
time for today's items, item count badge, and inline quick actions (complete
todo, snooze/dismiss reminder on hover). Card fills available height with
no dead space. DashboardPage always renders widget (no duplicate empty state).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace 895-line monolith with 5 focused tab components (Profile,
Appearance, Social, Security, Integrations) mirroring AdminPortal's
tab pattern. URL deep linking via ?tab= search param. Conditional
rendering prevents unmounted tabs from firing API calls.
Reviewed by senior-code-reviewer, senior-ui-designer, and
security-penetration-tester agents — all findings actioned.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Nav: justify-evenly on mobile, justify-start on desktop
- Title: "Admin Portal" on desktop, "Admin" on mobile
- Restore mr-6 spacing on title group for desktop
- Tab labels: icon-only on mobile, icon+label on sm+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- C-01: Simplify EntityTable sort dropdown to toggle-based (select
column, re-select to flip direction), add aria-label
- W-01: Convert CalendarPage mobile overlay to MobileDetailOverlay
- W-02: Use ref for onClose in MobileDetailOverlay to prevent
listener churn from inline arrow functions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- S-01/W-06/S-02/S-04: Extract MobileDetailOverlay shared component
with Escape key, body scroll lock, and ARIA dialog attributes.
Refactored Todos, Reminders, People, Locations, ProjectDetail.
- W-02: Add specificity contract comment to mobile-scale CSS
- W-03: Enforce 10px floor for text-[9px] on mobile
- W-05: Add sort dropdown to EntityTable mobile card view
- S-03: Export MOBILE/DESKTOP breakpoint constants from useMediaQuery,
updated all 8 consumer files to use constants
- S-06: Bump KanbanBoard TouchSensor tolerance from 5 to 8
- S-07: Hover state audit — no action needed, hoverOnlyWhenSupported
in Tailwind config already handles touch devices correctly
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Comprehensive mobile-responsive UI across all frontend pages:
- Global font scaling, responsive grids, progressive disclosure
- Mobile card views, touch-optimized inputs, bottom-sheet DatePicker
- Admin portal responsive tables, evenly spaced tab nav
- KanbanBoard touch drag-and-drop, FullCalendar mobile styling
- isDesktop media query guards for detail panels (no dual mount)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Replace CSS-only panel hiding with isDesktop media query guard
in Todos, Reminders, People, Locations, ProjectDetail (W-01)
- Add touch-action: manipulation for mobile interactive elements (W-04)
- Bump FullCalendar more-link from 0.55rem to 0.625rem (W-07)
- Add aria-label on admin portal tab NavLinks (S-05)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Tab nav: scroll isolation, icon-only on mobile, accessible titles
- IAM table: hide 6 columns on mobile, responsive padding
- User detail: responsive grid (1→2→3 cols), role select sizing
- Dashboard: responsive stats grid, hide Actor/Target cols on mobile
- Audit log: responsive column hiding and padding
- Actions menu: role submenu repositions below trigger on mobile
- Config: narrower filter select on mobile
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Reduce header gap to gap-2 on mobile, add min-w-0 so title can
shrink properly, hide status badge on small screens, and add
shrink-0 to action buttons to prevent them from compressing.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Hide verbose metadata columns (status badge, priority badge, date,
subtask count) on mobile and replace with compact priority dot +
overdue indicator. Reduce subtask indent and stack project summary
card vertically on small screens.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add pr-8 to mobile view Select to prevent text clipping under chevron.
Add min-w-0 flex-shrink to calendar title h2 to prevent nav arrow overlap.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add dark-themed FullCalendar "+more" popover with CSS X close button
(replaces broken font icon). Add pr-8 to all mobile Select dropdowns
to prevent text clipping under chevron. Normalize header gap to
gap-2 md:gap-4 across all page headers for tighter mobile layout.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Scale down all content text on mobile via .mobile-scale CSS class (excludes
navbar/UMBRA title). Hide calendar event times in month view (Google Calendar
style). Restructure CategoryFilterBar so categories display on a separate row
when toggled instead of being hidden behind the search bar. Reduce dashboard
widget density with hidden badges and tighter spacing on small screens.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Page titles: text-xl on mobile, text-2xl on desktop (7 pages)
- Stat cards: reduce padding/gap on mobile, hide icons below sm (3 pages)
- TodoItem: two-line layout on mobile (title row + metadata row)
- ReminderItem: same two-line treatment
- FullCalendar: smaller event font/padding on mobile via CSS media query
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The import was added but the sensors config replacement failed silently
due to line ending mismatch. TouchSensor now properly registered with
200ms delay / 5px tolerance alongside PointerSensor.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
3a. CalendarSidebar mobile collapse:
- Desktop sidebar + resize handle hidden below lg breakpoint
- Mobile Sheet overlay with PanelLeft toggle in toolbar
- Template selection closes mobile sidebar automatically
3b. KanbanBoard touch support:
- TouchSensor added alongside PointerSensor (200ms delay)
- Column min-width reduced on mobile (160px vs 200px)
- iOS smooth scroll enabled on horizontal container
3c. EntityTable mobile card view:
- mobileCardRender optional prop renders cards instead of table on mobile
- PeoplePage: card with name, category, email, phone
- LocationsPage: card with name, category, address
- TodosPage/RemindersPage use custom list components, not EntityTable
3d. DatePicker mobile bottom sheet:
- Renders as full-width bottom sheet on mobile (< 768px)
- Safe area inset padding for iOS home indicator
- Desktop positioned dropdown unchanged
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- EntityTable: add useMediaQuery hook, mobileCardRender prop, and mobile card path
that replaces the table on screens <768px when a renderer is provided
- PeoplePage: add mobileCardRender showing name, category, email, phone
- LocationsPage: add mobileCardRender showing name, category, address
Note: TodosPage and RemindersPage use custom list components (TodoList,
ReminderList), not EntityTable directly — no changes needed there.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- All page toolbars now flex-wrap on mobile with min-h instead of fixed h-16
- Segmented button filters (priority, status, view) hidden on mobile, replaced
with compact Select dropdowns
- Search inputs hidden on mobile where CategoryFilterBar already has search
- CategoryFilterBar wraps to full-width row on mobile (order-last)
- Action buttons show icon-only on mobile, full text on md+
- Calendar title hidden on xs screens for space
- Desktop layout completely unchanged (md:flex-nowrap restores original)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
C-01: sync_birthday_to_contacts now accepts (share_birthday, date_of_birth)
directly — no internal re-query, no stale-read risk with autoflush.
W-01: Eliminated redundant User/Settings SELECTs inside the service.
W-02: Removed scalar_one() on User query (no longer queries internally).
W-03: Settings router only syncs when share_birthday value actually changes.
S-02: Added logger.info with rowcount for observability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
When a user updates their date of birth or toggles share_birthday,
all linked Person records (where linked_user_id matches) are updated.
If share_birthday is off, the birthday is cleared on linked records.
Virtual birthday events auto-reflect the change on next calendar poll.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Replace per-mousemove setSidebarWidth() calls (triggering full React re-renders
including FullCalendar) with direct DOM style mutation during drag. React state
is committed only once on mouseup, eliminating all mid-drag re-renders and
localStorage writes that caused the lag.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sidebar width adjustable via click-and-drag (180–400px range, default 224px).
Width persists to localStorage across sessions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The _build_member_response helper tried to access member.user.preferred_name
but User model has no preferred_name field (it's on Settings). With lazy="raise"
this caused a 500 on GET /shared-calendars/{id}/members. Reverted to None —
the list_members endpoint already patches preferred_name from Settings.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
W-03: invite_member now verifies the target user has a reciprocal
UserConnection row before sending the invite.
W-04: CalendarMember relationships changed from lazy="selectin" to
lazy="raise". All queries that access .user, .calendar, or .inviter
already use explicit selectinload() — verified across all routers
and services.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical:
- C-01: Populate member_count in GET /calendars for shared calendars
- C-02: Differentiate 423 lock errors in drag-drop onError (show lock-specific toast)
- C-03: Add expired lock purge to APScheduler housekeeping job
Warnings:
- W-01: Replace setattr loop with explicit field assignment in update_member
- W-02: Cap sync `since` param to 7 days to prevent unbounded scans
- W-05: Remove cosmetic isShared toggle (is_shared is auto-managed by invite flow)
- W-06: Populate preferred_name in _build_member_response from user model
- W-07: Add releaseMutation to release callback dependency array
Suggestion:
- S-06: Remove unused ConvertToSharedRequest schema
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
SC-01: unlock_event now verifies caller has access to the calendar before
revealing lock state. Previously any authenticated user could probe event
existence via 404/204/403 response differences.
SC-02: acquire_lock no longer overwrites permanent locks. If the owner holds
a permanent lock and clicks Edit, the existing lock is returned as-is instead
of being downgraded to a 5-minute temporary lock.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Root cause: the previous approach synced poll data into lockInfo via a useEffect.
When the user selected an event with cached lock data, both the poll-data effect and
the event-change reset effect ran in the same render cycle. The event-change effect
ran second (effects are ordered by definition) and cleared lockInfo to null. On the
next render, viewLockQuery.data hadn't changed (TanStack Query structural sharing
returns same reference), so the poll-data effect never re-fired. Result: lockInfo
stayed null, banner stayed hidden until the next polling interval returned new data.
Fix: derive activeLockInfo directly from viewLockQuery.data (structural sharing
means it's always the latest authoritative value from TanStack Query) with lockInfo
as a fallback for the 423-error path only. Also add refetchIntervalInBackground:true
and refetchOnMount:'always' to ensure polling doesn't pause on tab switch and always
fires a fresh fetch when the component mounts.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>