Add read and replace endpoints for conversation labels, wire service methods, and route sender/category filtering through the thread-collapsed search path.
Move audit logs into org-scoped Postgres storage, wire audit events across backend handlers, and surface actor details in the dashboard activity log.
Add realtime audit invalidation and retention pruning so the trail stays current and bounded.
Add a thread_id filter for scheduled Unibox sends and expose a per-thread hook for the dashboard.
Render queued sends inline in ThreadView with cancellation, refreshing the thread, scheduled list, and overview caches after cancel.
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.