Files
warmbly/internal/api
Matthew Meszaros 5845fc3069 feat: inbox tag+multi-account filter + org gate + invite/join flow
Inbox filter:
- Backend: MailSearchParams gained EmailAccountIDs []uuid.UUID; the
  search SQL filters with `email_id = ANY($)`. /unibox handler now
  accepts both `email_id=<uuid>` (legacy) and `email_ids=<csv>`.
- Frontend: UniboxSearchParams gained accountIds[] and a UI-only
  tagId. searchIncoming sends email_ids=csv. UniboxFilterSheet:
  Accounts section is now (a) a row of tag chips backed by user.tags
  with per-tag account counts and (b) a multi-select list of every
  connected mailbox with an inline checkbox + avatar; accounts that
  belong to the active tag get a "via tag" affordance. Picking a tag
  resolves to the underlying account IDs at Apply time. "Select all"
  / "Clear" inline in the SectionBar header.

Org gate + onboarding:
- New /select-org page. Three sections: pending invitations (one-
  click Join), existing memberships (pick one to enter), and a
  Create New Workspace form (slate-900 primary). Routed at
  /select-org.
- OrgGate hook lives inside RealtimeManager. On load, if the user
  has zero orgs and no current org, it navigates to /select-org
  replace. Renders null so it doesn't displace AppLayout.

Invite + join:
- Team page rebuilt with real data: useMembers + usePendingInvitations,
  plus InviteDialog (email + role popover, slate-900 send button).
  Inline remove on member rows (skip "owner"), inline cancel on
  pending invitations.
- Pending invitations show up on /select-org too — a freshly
  invited user can accept without ever entering the dashboard first.

Response unwrapping:
- Org/member/invitation list clients now tolerate the backend's
  {data: T[] | null} envelope (it's the consistent shape across the
  Go handlers). Map nested membership rows into the flat
  Organization shape the rest of the app expects.

Seeder: re-run verified — dev@warmbly.com still gets "Dev's
Organization" so they don't bounce through /select-org.
2026-05-23 10:19:56 +00:00
..