404 Commits

Author SHA1 Message Date
0e35d473eb Refine calendar events: dot-only month timed, title-first week, no left border
- Month timed events: dot + title only, hover reveals translucent card
- Month all-day events: keep translucent fill
- Time right-aligned in month view (ml-auto)
- Week/day view: title on top, time underneath for better scanning
- Remove 2px left accent border from all events
- Set color:'transparent' on FC event data to prevent inline style conflicts
- Recurring repeat icon preserved in all views

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 02:24:20 +08:00
dec2c5d526 Fix event colors: remove inline backgroundColor/borderColor from event data
The previous commit failed to remove inline color props due to CRLF line
endings. FullCalendar was still setting inline styles that override CSS.
calendarColor is now correctly in extendedProps for the eventDidMount callback.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 02:09:14 +08:00
c473e7e235 Calendar visual overhaul: translucent events, AU locale, typography hierarchy
- Import en-AU locale (object, not string) for proper date format (day/month)
- Add 12-hour time format (9:00 AM), side-by-side overlap columns
- Replace flat opaque event rectangles with translucent color-mix fills (12% opacity)
- Add 2px left accent border per calendar color via CSS custom property
- Implement eventContent render hook with typography hierarchy (time secondary, title primary)
- Add recurring event indicator (Repeat icon) next to recurring event titles
- Add now-indicator 6px pulse dot with prefers-reduced-motion respect
- Add subtle weekend column tint (hsl 0 0% 6%)
- Mobile: hide time spans in month view custom eventContent
- Update +more popover to inherit translucent event styling
- Document calendar event patterns in stylesheet.md

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 02:02:21 +08:00
652be41da4 Merge fix/category-filter-position into main
Fix category chips rendering in wrong position (between search and add button).
Chips now appear inline after the Categories toggle pill.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 01:27:20 +08:00
85a9882d26 Fix category chips appearing in wrong position
Category chips were rendering as a separate flex row that got pushed to the
far right (between search and add button). Flatten the layout so chips appear
inline immediately after the Categories toggle pill, separated by a divider.

Remove redundant wrapper divs from TodosPage, PeoplePage, LocationsPage —
CategoryFilterBar now owns its own flex-1 sizing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 01:22:28 +08:00
bb5cbfa4b3 Merge optimize/docker-and-performance into main
Docker: .dockerignore, entrypoint.sh (PID 1), pinned images, network
segmentation, resource limits, npm ci, frontend healthcheck, DRY proxy config.

Backend: single JOIN auth, async Argon2id, settings cache, batch reorder,
bulk ntfy dedup, permission JOIN, cascade batch, collapsed admin COUNTs,
connection pool tuning, composite indexes (calendar_members, ntfy_sent).

Frontend: lazy-loaded routes, vendor chunk splitting, date-scoped calendar
events (87% payload reduction), 30s poll interval, clock isolation,
conditional shared-calendar polling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 01:15:40 +08:00
e270a2f63d Fix team review findings: reactive shared-calendar gate + ReorderItem hardening
- Convert hasSharingRef from useRef to useState in useCalendars so
  refetchInterval reacts immediately when sharing is detected (P-01)
- Add extra="forbid" to ReorderItem schema to prevent mass-assignment (S-03)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:50:02 +08:00
a94485b138 Address code review findings across all phases
Phase 1 fixes:
- W-01: Add start_period: 30s to backend healthcheck for migration window
- W-03: Narrow .dockerignore *.md to specific files (preserve alembic/README)

Phase 2 fixes:
- C-01: Wrap Argon2id calls in totp.py (disable, regenerate, backup verify,
  backup store) — missed in initial AC-2 pass
- S-01: Extract async wrappers (ahash_password, averify_password,
  averify_password_with_upgrade) into services/auth.py, refactor all
  callers to use them instead of manual run_in_executor boilerplate
- W-01: Fix ntfy dedup regression — commit per category instead of per-user
  to preserve dedup records if a later category fails

Phase 4 fixes:
- C-01: Fix optimistic drag-and-drop cache key to include date range
- C-02: Replace toISOString() with format() to avoid UTC date shift in
  visible range calculation
- W-02: Initialize visibleRange from current month to eliminate unscoped
  first fetch + immediate refetch

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:19:33 +08:00
2ab7121e42 Phase 4: Frontend performance optimizations
- AW-2: Scope calendar events fetch to visible date range via start/end
  query params, leveraging existing backend support
- AW-3: Reduce calendar events poll from 5s to 30s (personal organiser
  doesn't need 12 API calls/min)
- AS-4: Gate shared-calendar polling on hasSharedCalendars — saves 12
  wasted API calls/min for personal-only users
- AS-2: Lazy-load all route components with React.lazy() — only
  AdminPortal was previously lazy, now all 10 routes are code-split
- AS-1: Add Vite manualChunks to split FullCalendar (~400KB), React,
  TanStack Query, and UI libs into separate cacheable chunks
- AS-3: Extract clockNow into isolated ClockDisplay memo component —
  prevents all 8 dashboard widgets from re-rendering every minute

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:12:33 +08:00
846019d5c1 Phase 3: Backend queries and indexes optimization
- AW-1: Add composite index on calendar_members(user_id, status) for the
  hot shared-calendar polling query
- AS-6: Add composite index on ntfy_sent(user_id, sent_at) for dedup lookups
- AW-5: Combine get_user_permission into single LEFT JOIN query instead of
  2 sequential queries (called twice per event edit)
- AC-5: Batch cascade_on_disconnect — single GROUP BY + bulk UPDATE instead
  of N per-calendar checks when a connection is severed
- AW-6: Collapse admin dashboard 5 COUNT queries into single conditional
  aggregation using COUNT().filter()
- AC-3: Cache get_current_settings in request.state to avoid redundant
  queries when multiple dependencies need settings in the same request

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:08:45 +08:00
1f2083ee61 Phase 2: Backend critical path optimizations
- AC-1: Merge get_current_user into single JOIN query (session + user in
  one round-trip instead of two sequential queries per request)
- AC-2: Wrap all Argon2id hash/verify calls in run_in_executor to avoid
  blocking the async event loop (~150ms per operation)
- AW-7: Add connection pool config (pool_size=10, pool_pre_ping=True,
  pool_recycle=1800) to prevent connection exhaustion under load
- AC-4: Batch-fetch tasks in reorder_tasks with IN clause instead of
  N sequential queries during Kanban drag operations
- AW-4: Bulk NtfySent inserts with single commit per user instead of
  per-notification commits in the dispatch job

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:05:54 +08:00
dbad9c69b3 Phase 1: Docker infrastructure optimization
- Add .dockerignore for backend and frontend (DC-1: eliminates node_modules/
  and .env from build context)
- Delete start.sh with --reload flag (DC-2: superseded by Dockerfile CMD)
- Create entrypoint.sh with exec uvicorn (DW-5: proper PID 1 signal handling)
- Pin base images to patch-level tags (DW-1: reproducible builds)
- Reorder Dockerfile: create appuser before COPY, use --chown (DW-2)
- Switch to npm ci for lockfile-enforced installs (DW-3)
- Add network segmentation: backend_net + frontend_net (DW-4: db unreachable
  from frontend container)
- Add deploy.resources limits to all services (DW-6: OOM protection)
- Refactor proxy-params.conf to include security headers, deduplicate from
  nginx.conf location blocks (DW-7)
- Add image/svg+xml to gzip_types (DS-1)
- Add wget healthcheck for frontend service (DS-2)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 00:03:46 +08:00
a73bd17f47 Merge fix/lock-bypass-and-ui-polish into main
Server-persisted lock state, accent color persistence, data prefetching.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 23:14:25 +08:00
aa47ba4136 Address QA findings: prefetch reset on re-lock, settings gate, HSL validation
W-02: Reset hasPrefetched ref when app re-locks so subsequent unlocks
refresh stale cache data.
S-01: Validate localStorage HSL values with regex to prevent CSS injection.
S-05: Defer prefetch until settings are loaded for accurate upcoming_days.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 22:40:46 +08:00
379cc74387 Add data prefetching to eliminate skeleton flash on tab switch
Prefetches all main page queries (dashboard, upcoming, todos, reminders,
projects, people, locations) in parallel when the app unlocks, so the
TanStack Query cache is warm before the user navigates to each tab.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 22:19:08 +08:00
18a2c1314a Remove @layer base cyan defaults to eliminate refresh flash
Browser paints cached Vite CSS (@layer base cyan defaults) before the
inline script populates the static style tag. Remove the competing
cyan defaults — accent vars now come exclusively from the static
<style id="umbra-accent"> tag in index.html, which the inline script
always populates with the correct color before first paint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 21:15:47 +08:00
4e1b59c0f9 Use static style tag in HTML source for accent color persistence
Vite's initialization strips dynamically created elements from <head>.
Place <style id="umbra-accent"> directly in the HTML source instead of
creating it with createElement. Source-authored elements survive Vite's
head cleanup. The inline script populates it via textContent (XSS-safe).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:47:02 +08:00
f9359bd78a Recreate accent style tag if removed during page init
The <style id="umbra-accent"> tag injected by index.html gets removed
during page initialization. useTheme now defensively recreates the tag
if it's missing, ensuring color changes from the settings page work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:39:37 +08:00
b202ee1a84 Use style tag with !important for accent color persistence
Inline style attribute on <html> gets stripped during page load.
Switch to injecting a <style id="umbra-accent"> tag with !important
CSS custom properties which persists in the DOM and beats @layer base
defaults. useTheme updates the same style tag when settings load.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:29:53 +08:00
fce7405b14 Use !important inline styles for accent color to beat all CSS cascade
Both the index.html inline script and useTheme now use setProperty
with 'important' priority flag. This is the highest CSS cascade
priority and cannot be overridden by Vite's stylesheet injection,
@layer rules, or source order. Removes the <style> tag injection
approach which was being overridden.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:17:04 +08:00
e7be762198 Fix accent color loss on refresh by using injected style tag
The inline script's style.setProperty values on <html> were being
stripped during Vite's CSS injection. Switch to injecting a <style>
tag with :root vars which persists in the DOM. Restore CSS defaults
as safety fallback. Update useTheme to sync both the style tag and
inline styles when settings load.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:10:51 +08:00
988dc37b64 Fix accent color flash on refresh by eliminating CSS/JS race
Guard useTheme effect to skip when settings are undefined, preventing
it from overwriting the inline script's cached color with cyan defaults.
Move CSS accent var defaults from index.css :root into the index.html
inline script so they are always set synchronously before paint.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 20:02:13 +08:00
3d7166740e Fix lock screen flash, theme flicker, and lock state gating
Gate dashboard rendering on isLockResolved to prevent content flash
before lock state is known. Remove animate-fade-in from LockOverlay
so it renders instantly. Always write accent color to localStorage
(even default cyan) to prevent theme flash on reload. Resolve lock
state on auth query error to avoid permanent blank screen. Lift
mobileOpen state above lock gate to survive lock/unlock cycles.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 19:56:05 +08:00
89519a6dd3 Fix lock screen bypass, theme flicker, skeleton flash, and sidebar click target
Critical: Lock state was purely React useState — refreshing the page reset it.
Now persisted server-side via is_locked/locked_at columns on user_sessions.
POST /auth/lock sets the flag, /auth/verify-password clears it, and
GET /auth/status returns is_locked so the frontend initializes correctly.

UI: Cache accent color in localStorage and apply via inline script in
index.html before React hydrates to eliminate the cyan flash on load.

UI: Increase TanStack Query gcTime from 5min to 30min so page data
survives component unmount/remount across tab switches without skeleton.

UI: Move Projects nav onClick from the icon element to the full-width
container div so the entire row is clickable when the sidebar is collapsed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 19:00:55 +08:00
3dee52b6ad Merge feature/ambient-dashboard-background into main
Global ambient background with drifting gradient orbs, glassmorphism
cards, lightened color palette, live dashboard clock, calendar UI fixes,
and Upcoming widget polish. QA reviewed — 0 critical, all suggestions
addressed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:36:19 +08:00
2770a9e88e Address remaining QA suggestions S-02 through S-06
S-02: Confirmed drift-3 is used by auth/AmbientBackground — not dead code.
S-03: Extracted noise SVG data URI to module-level NOISE_SVG constant.
S-04: Added will-change: transform to drift orbs for GPU layer promotion.
S-05: Documented the 9 AM snooze default in getMinutesUntilTomorrowMorning.
S-06: Made calendar toolbar bg-card/95 with backdrop-blur-md for better
      readability over the transparent FullCalendar grid.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:35:44 +08:00
6e0a848c45 Fix QA findings: rename ambient component, add clock tab-resume sync
W-02: Renamed layout/AmbientBackground → AppAmbientBackground to avoid
naming collision with auth/AmbientBackground (IDE auto-import confusion).

S-01: Added visibilitychange listener to re-sync clock after tab
sleep/resume. Previously the interval would drift after laptop sleep
or long tab backgrounding.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:28:14 +08:00
b663455c26 Sync clock to minute boundary and stabilize "Updated" text
Clock: Instead of starting a 60s interval from mount time (which drifts
from the system clock), calculate ms until the next :00 second mark,
setTimeout to that point, then setInterval every 60s from there.

Updated text: Replaced formatDistanceToNow (which flickered between
"less than a minute ago" / "a minute ago" / "2 minutes ago" on each
render) with a stable minute-based calculation derived from clockNow:
"just now" / "1 min ago" / "N min ago".

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:18:00 +08:00
3afa894e1b Add live clock to dashboard header in 12hr format
Displays current time before the date separated by a vertical bar:
"6:30 PM | Thursday, March 12, 2026". Updates every 60 seconds.
Uses tabular-nums for stable digit widths.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:08:05 +08:00
246b54d10c Increase day header separator line visibility
Border was at 30% opacity — nearly invisible against the glassmorphic
card background. Restored to full border-border opacity for clear
section separation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 18:02:03 +08:00
b2e68d3100 Refine Upcoming day headers: thinner, subtler, aligned
- Removed bg-card from sticky headers (was creating opaque bars against
  glassmorphic card background)
- Reduced padding from pb-1.5 to py-0.5 for slimmer profile
- Added leading-none for proper vertical centering of chevron + text
- Softened border opacity to 30%, text to 70%, chevron to 60%
- Shrunk text from text-xs to text-[10px], chevron from h-3 to h-2.5

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 17:56:23 +08:00
39a42d08ec Fix phantom dropdown arrow next to Today button on desktop
The mobile view Select had md:hidden on the <select> element, but the
Select component wraps it in a <div> with an absolute ChevronDown icon
that remained visible. Moved md:hidden to a wrapper div so the entire
Select (including the chevron) is hidden on desktop.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 15:31:44 +08:00
91f929c39b Fix outline button background for glassmorphism consistency
The outline variant used bg-background (opaque near-black) which created
a visible dark rectangle against semi-transparent card toolbars. Changed
to bg-transparent so outline buttons blend with their parent container.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 15:27:00 +08:00
8d854b703e Fix calendar view visual inconsistencies with glassmorphism
FullCalendar backgrounds were opaque while the toolbar used semi-transparent
glassmorphism cards, creating a patchy look. Now all FC elements match:
- Page background: transparent (ambient shows through grid)
- Column headers: semi-transparent (0.65 opacity)
- Neutral background: semi-transparent (0.65 opacity)
- More-popover: semi-transparent with backdrop blur

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 13:59:15 +08:00
01c276fc8d Make ambient background global and lighten card colors
- Moved ambient from DashboardPage to AppLayout so all pages get the
  drifting gradient effect, not just the dashboard
- Lightened card colors: --card 5% → 8%, --card-elevated 7% → 11%,
  popover and FullCalendar backgrounds updated to match
- Renamed DashboardAmbient → AmbientBackground in layout/
- Glassmorphism class renamed dashboard-glass → ambient-glass,
  applied at AppLayout content wrapper level

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 12:59:12 +08:00
62949c997f Fix ambient edge clipping: extend orb layers with -100px inset
Drift animations translate orbs up to 80px, causing hard cutoff at
container edges. Giving orb layers inset: -100px provides enough
bleed room so the gradient edges are always beyond the overflow-hidden
boundary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 11:18:46 +08:00
34ea31421f Boost ambient visibility: stronger orbs, reduced vignette, transparent cards
- Orbs repositioned centrally with larger ellipses (90%/80%) and higher
  opacity (0.45/0.35) so glow is visible through glassmorphism cards
- Vignette reduced from 0.45 to 0.30, transparent zone expanded to 50%
- Card opacity reduced from 0.80 to 0.65 to let more ambient bleed through
- Added overflow-hidden on ambient container to prevent black bar artifacts
  during drift animations

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 09:55:39 +08:00
a4b3a8f7fe Switch ambient background to radial gradients + glassmorphism cards
Blurred circle approach was invisible on near-black backgrounds.
Use radial-gradient orbs at 25%/15% opacity instead, with semi-transparent
cards (backdrop-filter: blur) so the ambient effect shows through.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 09:44:38 +08:00
11fe3df513 Fix ambient background: use positive z-index layering instead of negative
Negative z-index (-z-10) placed orbs behind the body's opaque background,
making them invisible. Moved all ambient layers (orbs, noise texture,
vignette) into the DashboardAmbient component as absolute-positioned
children at z-0, with content at z-10. Boosted orb opacities to 12%/7%
for perceptible effect. Removed CSS pseudo-element approach in favor of
inline React elements for better stacking control.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 09:26:02 +08:00
6b02cfa1f8 Add ambient dashboard background: drifting orbs, noise texture, vignette, card breathe
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>
2026-03-12 00:54:00 +08:00
c21d7592ae Merge feature/upcoming-widget-redesign into main
Upcoming Widget redesign + Dashboard Polish (Batch 1+2):
- Upcoming feed: day-grouped, collapsible, focus mode, hover actions,
  optimistic todo completion, staggered row entrance
- Dashboard: plus rotation, card hover glow, DayBriefing container,
  WeekTimeline hover+pulse+tooltips, countdown urgency, CalendarWidget
  progress bars + current highlight + empty state, TodoWidget inline
  complete + empty state, auto-refresh, keyboard quick-add, progress
  rings, content crossfade, prefers-reduced-motion, ARIA compliance
2026-03-12 00:16:20 +08:00
ac3f746ba3 Fix QA findings: combine todo queries, remove dead prop, add aria-labels
- Merge total_todos and total_incomplete_todos into single DB query (W-04)
- Remove unused `days` prop from UpcomingWidget interface (W-03)
- Add aria-label to focus/show-past toggle buttons (S-08)
- Add zero-duration event guard in CalendarWidget progress calc (S-07)
- Combine duplicate date-utils imports (S-01)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 00:16:00 +08:00
b41b0b6635 Add dashboard polish: micro-animations, visual upgrades, and interactivity
Batch 1+2 implementation (17 items): plus button rotation, card hover
glow consistency, DayBriefing container with Sparkles icon, WeekTimeline
hover scale + pulsing today dot + dot tooltips, countdown urgency scaling,
CalendarWidget time progress bar + current event highlight + empty state,
TodoWidget inline complete + empty state, dashboard auto-refresh (2min),
optimistic todo completion, "Updated Xm ago" with refresh button, keyboard
quick-add (Ctrl+N → e/t/r), progress rings on stat cards, staggered row
entrance in Upcoming, content crossfade, prefers-reduced-motion support,
ARIA attributes on dropdown menu, and hover:bg-card-elevated consistency.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 00:02:04 +08:00
8b6530c901 Fix hover jitter by overlaying actions instead of swapping content
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>
2026-03-11 23:29:37 +08:00
66e230f740 Make right column cards fill height to align with Upcoming card
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>
2026-03-11 23:21:04 +08:00
b8bc097f6f Fix Upcoming card to match grid row height with internal scroll
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>
2026-03-11 22:47:00 +08:00
847372643b Fix QA findings: bound queries, error handlers, snooze clamp
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>
2026-03-11 22:21:10 +08:00
99161f1b47 Fix Upcoming card height constraint with flex column + maxHeight
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>
2026-03-11 22:15:53 +08:00
27a5002c74 Fix Upcoming card height — use natural height with scroll cap
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>
2026-03-11 22:07:59 +08:00
076b2fc3c9 Add scroll cap and fix all-day event time display
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>
2026-03-11 21:37:04 +08:00