26 Commits

Author SHA1 Message Date
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
a737f06e85 Action deferred QA items: shared overlay, sort, touch, a11y
- 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>
2026-03-11 03:43:25 +08:00
89f72895c1 Fix QA findings: dual panel mount, touch-action, font floor, a11y
- 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>
2026-03-11 03:14:38 +08:00
56175aaf86 Fix calendar popover, dropdown clipping, and header spacing across all tabs
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>
2026-03-11 02:13:41 +08:00
ec8f5a9b4e Fix mobile density issues from S24 Ultra testing
- 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>
2026-03-07 18:02:42 +08:00
4d5052d731 Action QA findings: fix all critical/warning/suggestion items
Critical fixes:
- C-01: DatePicker isMobile now actually used for bottom sheet positioning
- C-02: Calendar title always visible (text-sm on mobile, text-lg on sm+)
- C-03: Mobile card text-[10px] → text-xs (meets 12px minimum)

Warning fixes:
- W-01: useMediaQuery SSR-safe (typeof window guard)
- W-02: KanbanBoard TouchSensor added (was lost during branch ops)
- W-03: Removed duplicate isMobile query, derived from !isDesktop
- W-04: Search restored on mobile for Calendar/Reminders/Projects (w-32 sm:w-52)
- W-05: SheetClose added to CalendarSidebar mobile Sheet
- W-06: Button icon uses min-h/min-w for touch targets instead of h-11

Suggestion fixes:
- S-01: Removed deprecated WebkitOverflowScrolling from KanbanBoard
- S-02: Added role/tabIndex/onKeyDown to EntityTable mobile card wrappers
- S-03: Added overflow-y-auto to mobile event detail panel

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 17:16:47 +08:00
09c35752c6 Add mobile card view to EntityTable with renderers for People and Locations
- 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>
2026-03-07 16:59:58 +08:00
d0477b1c13 Phase 2: toolbar responsive patterns
- 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>
2026-03-07 16:56:08 +08:00
1c16df4db0 Phase 1: mobile responsive foundation
- useMediaQuery hook extracted from CalendarPage inline pattern
- h-screen → h-dvh for mobile address bar viewport fix
- px-6 → px-4 md:px-6 on all page containers/toolbars (14 files)
- Input/Select text-base on mobile to prevent iOS auto-zoom
- Sheet full-width on mobile, max-w-[540px] on sm+
- Button icon size touch-friendly (44px mobile, 40px desktop)
- Tailwind hoverOnlyWhenSupported: true (fixes 157 hover interactions)
- PWA meta tags (apple-mobile-web-app-capable, theme-color)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-07 16:51:53 +08:00
c8805ee4c4 Fix registration enumeration + contact detail panel name sync
M-01: Unify registration error messages to prevent username/email
enumeration — both now return the same generic message.

Bug fix: Contact detail panel header and initials now use shared_fields
for umbral contacts instead of stale Person record first_name/last_name.
The table list already did this via sf() helper; the detail panel header
and getPersonInitialsName were bypassing it.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-06 00:31:10 +08:00
4e2d48c50b Fix QA review findings: detach cleanup, sf() fallthrough, polling, commit guard
- W-01: Wrap accept flow db.commit() in IntegrityError handler (409)
- W-03: Remove refetchIntervalInBackground from unread count polling
- W-04: detach_umbral_contact now clears all shared fields on Person
- W-05: sf() callers no longer fall through via ?? to stale local data

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-05 19:12:31 +08:00
73cef1df55 Add umbral name header, preferred name field, and link button for contacts
- Inject umbral_name into shared_fields for umbral contacts (always visible)
- Show @umbralname subtitle in detail panel header
- Add preferred_name to panel fields with synced label for umbral contacts
- Add Link button on standard contacts to tie to umbral user via connection request
- Migration 046: person_id FK on connection_requests with index
- Validate person_id ownership on send, re-validate + convert on accept

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 08:37:01 +08:00
4513227338 Fix share name toggle revert and stale table data for umbral contacts
Bug 1: _to_settings_response() was missing share_first_name and
share_last_name — the response always returned false (Pydantic default),
causing the frontend to sync toggles back to off after save.

Bug 2: Table column renderers read from stale Person record fields.
Added sf() helper that overlays shared_fields for umbral contacts,
applied to name, phone, email, role, and birthday columns. The table
now shows live shared profile data matching the detail panel.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 08:07:45 +08:00
33aac72639 Add delete-with-sever and unlink actions for umbral contacts
Delete person now severs the bidirectional connection when the person
is an umbral contact — removes both UserConnection rows and detaches
the counterpart's Person record. Fixes "Already connected" error
when trying to reconnect after deleting an umbral contact.

New PUT /people/{id}/unlink endpoint converts an umbral contact to a
standard contact (detaches linked fields) while also severing the
bidirectional connection, keeping the Person in the contact list.

Frontend: EntityDetailPanel gains extraActions prop. PeoplePage renders
an "Unlink" button in the panel footer for umbral contacts. Delete
mutation now also invalidates connections query.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 07:50:31 +08:00
820ff46efa Fix QA W-01/W-05/W-06/W-08: cancel requests, detach umbral, notifications
W-08: Add CHECK constraint on notifications.type (migration 044) with
defensive pre-check and matching __table_args__ on model.

W-05: Auto-detach umbral contact before Person delete — nulls out
connection's person_id so the connection survives deletion.

W-01: Add PUT /requests/{id}/cancel endpoint with atomic UPDATE,
silent notification cleanup, and audit logging. Frontend: direction-aware
ConnectionRequestCard, cancel mutation, pending requests section on
PeoplePage with incoming/outgoing subsections.

W-06: Convert useNotifications to context provider pattern — single
subscription shared via NotificationProvider in AppLayout. Adds
refreshNotifications convenience function.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 07:17:31 +08:00
3d22568b9c Add user connections, notification centre, and people integration
Implements the full User Connections & Notification Centre feature:

Phase 1 - Database: migrations 039-043 adding umbral_name to users,
profile/social fields to settings, notifications table, connection
request/user_connection tables, and linked_user_id to people.

Phase 2 - Notifications: backend CRUD router + service + 90-day purge,
frontend NotificationsPage with All/Unread filter, bell icon in sidebar
with unread badge polling every 60s.

Phase 3 - Settings: profile fields (phone, mobile, address, company,
job_title), social card with accept_connections toggle and per-field
sharing defaults, umbral name display with CopyableField.

Phase 4 - Connections: timing-safe user search, send/accept/reject flow
with atomic status updates, bidirectional UserConnection + Person records,
in-app + ntfy notifications, per-receiver pending cap, nginx rate limiting.

Phase 5 - People integration: batch-loaded shared profiles (N+1 prevention),
Ghost icon for umbral contacts, Umbral filter pill, split Add Person button,
shared field indicators (synced labels + Lock icons), disabled form inputs
for synced fields on umbral contacts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 02:10:16 +08:00
6fc134d113 Update EntityDetailPanel to use 2-column grid layout
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>
2026-02-25 23:59:30 +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
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
1806e15487 Address all QA review warnings and suggestions for entity pages
Warnings fixed:
- 3.1: _compute_display_name stale-data bug on all-names-clear
- 3.3: Location getValue unsafe type cast replaced with typed helper
- 3.5: Explicit updated_at timestamp refresh in locations router
- 3.6: Drop deprecated relationship column (migration 021, model, schema, TS type)

Suggestions fixed:
- 4.1: CategoryAutocomplete keyboard navigation (ArrowUp/Down, Enter, Escape)
- 4.2: Mobile detail panel backdrop click-to-close on both pages
- 4.3: PersonCreate whitespace bypass in require_some_name validator
- 4.5/4.6: Extract SortIcon, DataRow, SectionHeader from EntityTable render body
- 4.8: PersonForm sends null instead of empty string for birthday
- 4.10: Remove unnecessary executeDelete wrapper in EntityDetailPanel

Also includes previously completed fixes from prior session:
- 2.1: Remove Z suffix from naive timestamp in formatUpdatedAt
- 3.2: Drag-then-click conflict prevention in SortableCategoryChip
- 3.4: localStorage JSON shape validation in useCategoryOrder
- 4.4: Category chip styling consistency (both pages use inline hsl styles)
- 4.9: restrictToHorizontalAxis modifier on CategoryFilterBar drag

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 01:04:20 +08:00
1231c4b36d Polish entity pages: groups, favourite toggle, copy icon, initials, notes
- 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>
2026-02-25 00:21:23 +08:00
1b78dadf75 Fix bugs and action remaining QA suggestions
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>
2026-02-24 21:46:38 +08:00
8cbc95939a Fix issues from QA review: schema safety, search scope, clipboard handling, UX polish
- 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>
2026-02-24 21:19:22 +08:00
f4b1239904 UI refresh Stage 5: rebuild People page with table/panel layout
- Rewrote PeoplePage.tsx: EntityTable + EntityDetailPanel + CategoryFilterBar
  with favourites pinned section, stat bar with birthday list, animated side panel
- Rewrote PersonForm.tsx: expanded fields (first/last/nickname, mobile, company,
  job_title, birthday+age, category autocomplete, LocationPicker, favourite star)
- Stripped constants.ts to legacy-only getRelationshipColor, removed RELATIONSHIPS
- Deleted PersonCard.tsx (replaced by table rows + detail panel)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-24 21:07:26 +08:00
765f692304 UI refresh Stage 5: redesign People & Locations pages
- 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>
2026-02-24 18:53:44 +08:00
1f6519635f Initial commit 2026-02-15 16:13:41 +08:00