Add fullWidth field option to PanelField interface. Short fields
render in a grid grid-cols-2 layout; fullWidth fields (address, notes)
render below at full width. Add icons to People and Locations fields
for consistency with other detail panels.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- 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>
Remove the collapsible search behavior that hid the input behind an
icon when ≥4 categories existed. Search should always be visible and
match the standard header pattern (w-52 input with icon).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CategoryFilterBar's internal flex-1 spacer needs room to push search
right. Wrapping in flex-1 min-w-0 matches the Locations/People pattern.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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>
- 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>
Swap LockProvider to outer wrapper so AlertsProvider can read isLocked.
When locked, dismiss all visible reminder toasts and skip firing new ones.
Toasts re-fire normally on unlock via the firedRef.clear() reset.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- [C-1] Add rate limiting and account lockout to /verify-password endpoint
- [W-3] Add max length validator (128 chars) to VerifyPasswordRequest
- [W-1] Move activeMutations to ref in useLock to prevent timer thrashing
- [W-5] Add user_id field to frontend Settings interface
- [S-1] Export auth schemas from schemas registry
- [S-3] Add aria-label to LockOverlay password input
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 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>
- Integrations card now has inline icon+title (no description), future-proofed
for additional integrations as sub-sections
- ntfy is its own sub-section with Bell icon + "ntfy Push Notifications" header
- Connection settings (URL, topic, token) collapse into a "Configure ntfy
connection" disclosure after saving, keeping the UI tidy
- Notification types and test/save buttons remain visible when enabled
- First-time setup shows connection fields expanded; they collapse on save
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Left column: Profile, Appearance, Calendar, Dashboard, Weather (prefs & display)
Right column: Security, Authentication, Integrations (security & services)
Also inlines the "Lock after [input] minutes" onto a single row.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The onChange was using parseInt(...) || 5 which snapped empty input back
to 5 immediately. Now allows empty string as intermediate state; value
is clamped to 1-60 on blur/Enter.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The swatches were added to SettingsPage but useTheme only had 5 presets,
so selecting the new colors saved to DB but never applied CSS variables.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- W1: Add ntfy_has_token property to Settings model for safe from_attributes usage
- W2: Eager-load event location and pass location_name to ntfy template builder
- W3: Add missing accent color swatches (red, pink, yellow) to match backend Literal
- W7: Cap IP rate-limit dict at 10k entries with stale-entry purge to prevent OOM
- W9: Include user_id in SettingsResponse for multi-user readiness
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- C3: Register User, UserSession, NtfySent, TOTPUsage, BackupCode in models/__init__.py
- C4: Enforce settings.user_id NOT NULL after backfill in migration 023, update model
- W4: Rename misleading current_user → current_settings in dashboard.py
- W5: Match NtfySettingsSection initial state defaults to backend (true/1/2)
- W8: Clear lockout banner on username/password input change in LockScreen
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds Authentication card (password change + TOTP 5-state setup flow) and
Integrations card (ntfy master toggle, connection config, per-type toggles,
test button) to SettingsPage right column in correct order.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- EntityTable: replace flat rows with grouped sections (label + rows),
each group gets its own section header for visual clarity
- EntityDetailPanel: add favourite/frequent toggle star button in header,
add multiline flag for notes with whitespace-pre-wrap
- CopyableField: use inline-flex to keep copy icon close to text
- PeoplePage: compute initials from first_name/last_name (not nickname),
build row groups from active filters, add toggle favourite mutation
- LocationsPage: build row groups from active filters, add toggle
frequent mutation, pass favourite props to detail panel
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Bugs fixed:
- formatUpdatedAt treats naive UTC timestamps as UTC (append Z before parsing)
- PersonForm/LocationForm X button now inline with star toggle, matching panel style
- LocationForm contact placeholder changed from +44 to +61
QA suggestions actioned:
- CategoryAutocomplete: replace blur setTimeout with onPointerDown preventDefault
- CategoryFilterBar: replace hardcoded 600px maxWidth with 100vw
- Location "other" category shows dash instead of styled badge
- Delete dead legacy constants files (people/constants.ts, locations/constants.ts)
- EntityTable rows: add tabIndex, Enter/Space keyboard navigation, focus ring
- Replace Record<string, unknown> casts with typed keyof accessors
- Add email validation (field_validator) to Person and Location schemas
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove `name` from PersonUpdate schema (always computed, prevents bypass)
- Auto-split legacy `name` into first/last on create when only name provided
- Expand backend search to cover first_name, last_name, nickname, email, company
- Add server_default=text('false') to is_favourite and is_frequent model columns
- Add .catch() to clipboard API call in CopyableField
- Extract duplicate renderHeader into shared function in PeoplePage
- Add Escape key handler to close mobile detail panel overlays
- Extract calculate() out of useTableVisibility effects to single function
- Guard getInitials against empty string input
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Add badge color constants for relationships and location categories
- Compact h-16 headers with segmented filters replacing dropdowns
- Stat cards (Total, Upcoming Birthdays, With Contact Info / Categories)
- People search expanded to match name, email, and relationship
- Avatar initials with deterministic color hash on PersonCard
- Two-click delete via useConfirmAction on both entity cards
- Error toasts use getErrorMessage for meaningful messages
- Query invalidation includes dashboard and upcoming keys
- PersonForm migrated from Dialog to Sheet with Select dropdown
- LocationForm: import CATEGORIES from constants, invalidate dashboard/upcoming
- Normalize badge colors to text-*-400 pattern (was text-*-500)
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- W3: Merge route-change and new-alert effects into single unified effect
- W6: Migration 018 extends due_lookup index with snoozed_until column
- S1: Extract useConfirmAction hook from TodoItem/ReminderItem
- S7: Update summary toast count on dismiss/snooze instead of dismissing
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- C1: Add onError handlers to dismiss/snooze mutations in useAlerts
- C2: Clear snoozed_until when dismissing via update endpoint
- W1: Handle future dates in getRelativeTime
- W2+S3: Add Escape key, aria-expanded, role=menu to SnoozeDropdown
- W4: Remove redundant field_validator (Literal suffices)
- W7: Validate recurrence_rule with Literal['daily','weekly','monthly']
- S2: Clean up delete confirmation setTimeout on unmount
- S6: Cap AlertBanner height with scroll for many alerts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- SnoozeDropdown: added direction prop (up/down), toasts use 'down'
so dropdown opens below the button instead of clipping off-screen
- AlertBanner dismiss button now shows X icon + 'Dismiss' text to
match toast style
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Toast dismiss button now shows X icon + 'Dismiss' text to match
the snooze button style
- Updating remind_at on a dismissed reminder clears is_dismissed
and snoozed_until, making the reminder active again
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Trash icon swaps to a red 'Sure?' label on first click, making the
two-click delete pattern discoverable. Applied to both TodoItem and
ReminderItem. Resets after 2 seconds if not confirmed.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Docker container datetime.now() returns UTC, but all stored datetimes
are naive local time from the browser. Both /due and /snooze now
accept client_now from the frontend, ensuring snooze computes from
the user's actual current time, not the container's clock.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Matches EventForm pattern used across UMBRA. Remind At and Recurrence
now sit side-by-side in a 2-column grid. Empty optional fields send
null instead of empty string.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Single clock icon opens a dropdown with 5/10/15 min options instead
of three inline buttons. Shared SnoozeDropdown component used in
both AlertBanner and toast notifications.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- S1: Add composite index (is_active, is_dismissed, remind_at) for
/due query performance with multi-user scaling
- W3: Snooze endpoint rejects dismissed/inactive reminders (409)
- W4: Custom field_validator on ReminderSnooze for clear error message
- S2: aria-label on all snooze/dismiss buttons in banner and toasts
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- C1: Replaced duplicate useAlerts() calls with AlertsProvider context
wrapping AppLayout — single hook instance, no double polling/toasts
- C2: Added null guard on remind_at in Active Reminders card format()
- W2: Clear snoozed_until when dismissing a reminder
- W5: Extracted getRelativeTime to shared lib/date-utils.ts
- S3: Replaced inline SVG with Lucide Bell component in toasts
- S4: Clear snoozed_until when remind_at is updated via PUT
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Backend: /due endpoint now matches both NULL and empty string for
recurrence_rule (form was sending '' not null, excluding all reminders)
- Form: sends null instead of '' for empty recurrence_rule
- ReminderForm: replaced datetime-local with date + hour/minute/AM-PM
selects for 12-hour time format
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Compact h-16 header with segmented filter, search input
- Stat cards (Active/Overdue/Dismissed) with semantic colors
- New ReminderItem component: single-line rows with grouped sections
- Optimistic delete, 2-second confirm pattern, dismiss action
- Mark Stage 4 Reminders as completed in ui_refresh.md
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- [W1] Add server_default=func.now() on created_at/updated_at
- [W2] Add index on reset_at column (migration 016)
- [W7] Document weekly reset edge case in code comment
Frontend:
- [W4] Extract shared isTodoOverdue() utility in lib/utils.ts,
used consistently across TodosPage, TodoItem, TodoList
- [W5] Delete requires double-click confirmation (button turns red
for 2s, second click confirms) with optimistic removal
- [W6] Stat cards now reflect filtered counts, not global
- [S3] Optimistic delete with rollback on error
- [S4] Add "None" to priority segmented filter
- [S7] Sort todos within groups by due date ascending
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Critical fixes:
- [C1] _reactivate_recurring_todos now uses flush + with_for_update
instead of mid-request commit; get_todos commits the full transaction
- [C2] recurrence_rule validated via Literal["daily","weekly","monthly"]
in Pydantic schemas (rejects invalid values with 422)
Warnings fixed:
- [W3] Clear due_time when due_date is set to null in update endpoint
Suggestions applied:
- [S2] Wrap filteredTodos in useMemo for consistent memoization
- [S6] Add aria-labels to edit/delete icon buttons
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- TodoItem: flatten to single-line row layout — checkbox, title,
pills, date/reset info, actions all inline. Use hover:bg-card-elevated
instead of bordered card with shadow.
- TodoList: tighten spacing from space-y-2 to space-y-0.5
- stylesheet.md: document list-view vs card-view convention —
list pages use single-line rows, grid pages use multi-line cards
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Backend:
- Add due_time (TIME, nullable) column to todos model + migration 015
- Add due_time to Create/Update/Response schemas
Frontend:
- Add due_time to Todo type
- TodoForm: add time input, convert empty strings to null before
sending (fixes date appearing required — Pydantic rejected '' as date)
- TodoItem: display clock icon + time when due_time is set
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>