feat(messaging-ui): Inbox domain — unified work items + mail (contract + UI + mock)

Second domain in the SDK, mirroring messaging's adapter/provider/hooks/components:
- InboxAdapter (listInbox unified feed + transition + mailHistory/mailReply +
  optional directory/composeInternal/composeExternal), InboxProvider/useInboxAdapter
- hooks: useInbox (filter + transition), useMailThread (history + reply), useCompose
- <Inbox> (filter tabs, unified list, detail pane), <MailReader> (HTML in a
  sandboxed iframe + reply), compose modal (in-app / email); themeable styles
- MockInboxAdapter (seeded mentions/needs-reply/alert + folded mail threads +
  directory) shipped from ./adapters/mock-inbox
- 6 inbox tests (mock unify/transition/reply + render open/reply/compose); 64 green

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-22 00:48:41 +05:30
parent d02cd152de
commit 2f24a95ef4
11 changed files with 958 additions and 2 deletions
@@ -0,0 +1,16 @@
import { createContext, useContext, type ReactNode } from 'react';
import type { InboxAdapter } from './adapter';
const InboxContext = createContext<InboxAdapter | null>(null);
export function InboxProvider({ adapter, children }: { adapter: InboxAdapter; children: ReactNode }) {
return <InboxContext.Provider value={adapter}>{children}</InboxContext.Provider>;
}
/** Access the host-injected inbox adapter. Throws outside a provider — a missing provider is a
* wiring bug, and failing loudly beats a confusing null-deref three layers down. */
export function useInboxAdapter(): InboxAdapter {
const adapter = useContext(InboxContext);
if (!adapter) throw new Error('useInboxAdapter must be used within an <InboxProvider>');
return adapter;
}