@burdenoff/microfe-conversations 2026.527.1 → 2026.528.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/conversations/ConversationsUserRoutes.js +6 -6
  2. package/dist/conversations/ConversationsUserRoutes.js.map +1 -1
  3. package/dist/conversations/components/ConversationRow.js +176 -0
  4. package/dist/conversations/components/ConversationRow.js.map +1 -0
  5. package/dist/conversations/components/EmptyState.js +10 -21
  6. package/dist/conversations/components/EmptyState.js.map +1 -1
  7. package/dist/conversations/components/InboxLayout.js +185 -0
  8. package/dist/conversations/components/InboxLayout.js.map +1 -0
  9. package/dist/conversations/components/LazyPageBoundary.js.map +1 -1
  10. package/dist/conversations/components/PageLayout.js +16 -34
  11. package/dist/conversations/components/PageLayout.js.map +1 -1
  12. package/dist/conversations/components/PageSection.js +10 -19
  13. package/dist/conversations/components/PageSection.js.map +1 -1
  14. package/dist/conversations/components/lazyRetry.js.map +1 -1
  15. package/dist/conversations/hooks/useConversationQueries.js +48 -34
  16. package/dist/conversations/hooks/useConversationQueries.js.map +1 -1
  17. package/dist/conversations/hooks/useMessageQueries.js.map +1 -1
  18. package/dist/conversations/pages/ConversationAdminPages.js.map +1 -1
  19. package/dist/conversations/pages/ConversationListPage.js +187 -226
  20. package/dist/conversations/pages/ConversationListPage.js.map +1 -1
  21. package/dist/conversations/pages/ConversationsLandingPage.js +93 -103
  22. package/dist/conversations/pages/ConversationsLandingPage.js.map +1 -1
  23. package/dist/conversations/pages/ConversationsSettingsPage.js +201 -182
  24. package/dist/conversations/pages/ConversationsSettingsPage.js.map +1 -1
  25. package/dist/generated/wspace-operations.js +72 -56
  26. package/dist/generated/wspace-operations.js.map +1 -1
  27. package/package.json +2 -2
  28. package/dist/conversations/components/ConversationCard.js +0 -119
  29. package/dist/conversations/components/ConversationCard.js.map +0 -1
@@ -1,119 +1,109 @@
1
1
  import { useTr as e } from "../utils/i18n.js";
2
- import { PermissionGuard as t } from "../components/PermissionGuard.js";
3
- import { CONVERSATIONS_PERMISSIONS as n } from "../constants/permissions.js";
4
- import { ConversationListPage as r } from "./ConversationListPage.js";
5
- import { NewConversationPage as i } from "./NewConversationPage.js";
6
- import { ConversationsSettingsPage as a } from "./ConversationsSettingsPage.js";
2
+ import { useConversationsStore as t } from "../store/conversationsStore.js";
3
+ import { PermissionGuard as n } from "../components/PermissionGuard.js";
4
+ import { CONVERSATIONS_PERMISSIONS as r } from "../constants/permissions.js";
5
+ import { ConversationListPage as i } from "./ConversationListPage.js";
6
+ import { InboxLayout as a } from "../components/InboxLayout.js";
7
7
  import { useUserFeatureFlags as o } from "../hooks/useUserFeatureFlags.js";
8
- import { useEffect as s, useMemo as c } from "react";
9
- import { useSearchParams as l } from "react-router-dom";
10
- import { jsx as u, jsxs as d } from "react/jsx-runtime";
11
- import { Tabs as f } from "@burdenoff/fe-libs/chrome";
8
+ import { Suspense as s, lazy as c, useCallback as l, useEffect as u, useMemo as d } from "react";
9
+ import { Filter as f, Loader2 as p, Pin as m } from "lucide-react";
10
+ import { useParams as h, useSearchParams as g } from "react-router-dom";
11
+ import { jsx as _, jsxs as v } from "react/jsx-runtime";
12
+ import { PageContainer as y } from "@burdenoff/fe-libs/ui";
12
13
  //#region src/conversations/pages/ConversationsLandingPage.tsx
13
- var p = "all";
14
- function m({ title: t }) {
15
- let n = e();
16
- return /* @__PURE__ */ u("div", {
17
- className: "flex h-full items-center justify-center p-8 text-center",
18
- children: /* @__PURE__ */ d("div", {
19
- className: "space-y-2",
20
- children: [/* @__PURE__ */ u("h3", {
21
- className: "text-lg font-semibold text-foreground",
22
- children: t
23
- }), /* @__PURE__ */ u("p", {
24
- className: "text-sm text-muted-foreground",
25
- children: n("conversations.comingSoon", "Coming soon.")
26
- })]
27
- })
28
- });
29
- }
30
- function h() {
31
- let [h, g] = l(), _ = e(), { archivedEnabled: v } = o(), y = c(() => [
32
- {
33
- id: p,
34
- label: _("conversations.allConversations", "All Conversations")
35
- },
36
- {
37
- id: "channels",
38
- label: _("conversations.channels", "Channels")
39
- },
40
- {
41
- id: "direct",
42
- label: _("conversations.directMessages", "Direct Messages")
43
- },
44
- {
45
- id: "groups",
46
- label: _("conversations.groups", "Groups")
47
- },
48
- ...v ? [{
49
- id: "archived",
50
- label: _("conversations.archived", "Archived")
51
- }] : [],
52
- {
53
- id: "new",
54
- label: _("conversations.new", "New")
55
- },
56
- {
57
- id: "settings",
58
- label: _("conversations.settings", "Settings")
59
- }
60
- ], [_, v]), b = h.get("tab"), x = b === "archived" && !v ? p : b ?? p;
61
- return s(() => {
62
- !v && b === "archived" && g((e) => {
14
+ var b = c(() => import("./ConversationDetailPage.js").then((e) => ({ default: e.ConversationDetailPage }))), x = "all", S = {
15
+ all: void 0,
16
+ direct: "direct",
17
+ groups: "group",
18
+ channels: "channel",
19
+ archived: "archived"
20
+ };
21
+ function C() {
22
+ let [c, C] = g(), { conversationId: w } = h(), T = e(), { archivedEnabled: E } = o(), { searchQuery: D, setSearchQuery: O, conversationFilter: k, updateConversationFilter: A } = t(), j = c.get("tab") ?? x, M = j === "archived" && !E ? x : j;
23
+ u(() => {
24
+ !E && j === "archived" && C((e) => {
63
25
  let t = new URLSearchParams(e);
64
26
  return t.delete("tab"), t;
65
27
  }, { replace: !0 });
66
28
  }, [
67
- v,
68
- b,
69
- g
70
- ]), /* @__PURE__ */ u(t, {
71
- permission: n.CONVERSATION_READ,
29
+ E,
30
+ j,
31
+ C
32
+ ]);
33
+ let N = l((e) => {
34
+ C((t) => {
35
+ let n = new URLSearchParams(t);
36
+ return e === x ? n.delete("tab") : n.set("tab", e), n;
37
+ }, { replace: !0 });
38
+ }, [C]), P = d(() => {
39
+ if (M === "archived") return [];
40
+ let e = k.hasUnread === !0, t = k.isPinned === !0;
41
+ return [{
42
+ id: "unread",
43
+ label: T("conversations.unread", "Unread"),
44
+ active: e,
45
+ onToggle: () => A({ hasUnread: e ? void 0 : !0 }),
46
+ icon: /* @__PURE__ */ _(f, {
47
+ size: 11,
48
+ "aria-hidden": "true"
49
+ })
50
+ }, {
51
+ id: "pinned",
52
+ label: T("conversations.pinned", "Pinned"),
53
+ active: t,
54
+ onToggle: () => A({ isPinned: t ? void 0 : !0 }),
55
+ icon: /* @__PURE__ */ _(m, {
56
+ size: 11,
57
+ "aria-hidden": "true"
58
+ })
59
+ }];
60
+ }, [
61
+ M,
62
+ k.hasUnread,
63
+ k.isPinned,
64
+ T,
65
+ A
66
+ ]), F = S[M];
67
+ return /* @__PURE__ */ _(n, {
68
+ permission: r.CONVERSATION_READ,
72
69
  allowUnavailablePermissions: !0,
73
- children: /* @__PURE__ */ d("main", {
70
+ children: /* @__PURE__ */ v(y, {
74
71
  "data-tour": "conversations-landing",
75
- className: "flex flex-col h-full",
76
- "aria-labelledby": "conversations-page-heading",
77
- children: [
78
- /* @__PURE__ */ u("h1", {
79
- id: "conversations-page-heading",
80
- className: "sr-only",
81
- children: _("conversations.title", "Conversations")
72
+ padded: !1,
73
+ className: "h-full min-h-0",
74
+ children: [/* @__PURE__ */ _("h1", {
75
+ id: "conversations-page-heading",
76
+ className: "sr-only",
77
+ children: T("conversations.title", "Conversations")
78
+ }), /* @__PURE__ */ _(a, {
79
+ activeTab: M,
80
+ onTabChange: N,
81
+ archivedEnabled: E,
82
+ searchQuery: D,
83
+ onSearchChange: O,
84
+ filterChips: P,
85
+ listSlot: /* @__PURE__ */ _(i, {
86
+ embedded: !0,
87
+ forcedFilterParam: F
82
88
  }),
83
- /* @__PURE__ */ u("div", {
84
- "data-tour": "conversations-tabs",
85
- className: "border-b border-border-subtle px-4 pt-3",
86
- children: /* @__PURE__ */ u(f, {
87
- items: y,
88
- activeTabId: x,
89
- onTabChange: (e) => {
90
- g((t) => {
91
- let n = new URLSearchParams(t);
92
- return e === p ? n.delete("tab") : n.set("tab", e), n;
93
- }, { replace: !0 });
94
- },
95
- variant: "underline"
96
- })
97
- }),
98
- /* @__PURE__ */ d("section", {
99
- className: "flex-1 min-h-0 overflow-auto",
100
- "aria-live": "polite",
101
- "aria-label": _("conversations.activePanel", "Active conversations panel"),
102
- children: [
103
- x === "all" && /* @__PURE__ */ u(r, {}),
104
- x === "channels" && /* @__PURE__ */ u(r, { forcedFilterParam: "channel" }),
105
- x === "direct" && /* @__PURE__ */ u(r, { forcedFilterParam: "direct" }),
106
- x === "groups" && /* @__PURE__ */ u(r, { forcedFilterParam: "group" }),
107
- x === "archived" && /* @__PURE__ */ u(m, { title: _("conversations.archivedTitle", "Archived Conversations") }),
108
- x === "new" && /* @__PURE__ */ u(i, {}),
109
- x === "settings" && /* @__PURE__ */ u(a, {})
110
- ]
111
- })
112
- ]
89
+ detailSlot: w ? /* @__PURE__ */ _(s, {
90
+ fallback: /* @__PURE__ */ v("div", {
91
+ role: "status",
92
+ "aria-live": "polite",
93
+ className: "flex h-full items-center justify-center gap-2 text-sm text-text-secondary",
94
+ children: [/* @__PURE__ */ _(p, {
95
+ size: 16,
96
+ className: "animate-spin",
97
+ "aria-hidden": "true"
98
+ }), T("conversations.detailLoading", "Loading conversation…")]
99
+ }),
100
+ children: /* @__PURE__ */ _(b, {})
101
+ }) : void 0
102
+ })]
113
103
  })
114
104
  });
115
105
  }
116
106
  //#endregion
117
- export { h as ConversationsLandingPage };
107
+ export { C as ConversationsLandingPage };
118
108
 
119
109
  //# sourceMappingURL=ConversationsLandingPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"ConversationsLandingPage.js","names":[],"sources":["../../../src/conversations/pages/ConversationsLandingPage.tsx"],"sourcesContent":["import { useEffect, useMemo } from \"react\";\nimport { useSearchParams } from \"react-router-dom\";\nimport { Tabs, type TabItem } from \"@burdenoff/fe-libs/chrome\";\nimport { ConversationListPage } from \"./ConversationListPage\";\nimport { NewConversationPage } from \"./NewConversationPage\";\nimport { ConversationsSettingsPage } from \"./ConversationsSettingsPage\";\nimport { PermissionGuard } from \"../components/PermissionGuard\";\nimport { CONVERSATIONS_PERMISSIONS } from \"../constants/permissions\";\nimport { useUserFeatureFlags } from \"../hooks/useUserFeatureFlags\";\nimport { useTr } from \"../utils/i18n\";\n\nconst DEFAULT_TAB_ID = \"all\";\n\n/**\n * Coming-soon placeholder shown for tabs whose dedicated pages\n * are not yet implemented in this microfrontend.\n */\nfunction ComingSoon({ title }: { title: string }) {\n const tr = useTr();\n\n return (\n <div className=\"flex h-full items-center justify-center p-8 text-center\">\n <div className=\"space-y-2\">\n <h3 className=\"text-lg font-semibold text-foreground\">{title}</h3>\n <p className=\"text-sm text-muted-foreground\">\n {tr(\"conversations.comingSoon\", \"Coming soon.\")}\n </p>\n </div>\n </div>\n );\n}\n\n/**\n * Tabbed landing page for the Conversations microfrontend.\n *\n * Renders an underline tab bar at the top and swaps the active panel\n * based on the `?tab=...` URL search param. The first tab (\"all\") uses\n * a clean URL with no `tab` param.\n */\nexport function ConversationsLandingPage() {\n const [params, setParams] = useSearchParams();\n const tr = useTr();\n\n const { archivedEnabled } = useUserFeatureFlags();\n\n const tabs: TabItem[] = useMemo(\n () => [\n {\n id: DEFAULT_TAB_ID,\n label: tr(\"conversations.allConversations\", \"All Conversations\"),\n },\n { id: \"channels\", label: tr(\"conversations.channels\", \"Channels\") },\n {\n id: \"direct\",\n label: tr(\"conversations.directMessages\", \"Direct Messages\"),\n },\n { id: \"groups\", label: tr(\"conversations.groups\", \"Groups\") },\n ...(archivedEnabled\n ? [{ id: \"archived\", label: tr(\"conversations.archived\", \"Archived\") }]\n : []),\n { id: \"new\", label: tr(\"conversations.new\", \"New\") },\n { id: \"settings\", label: tr(\"conversations.settings\", \"Settings\") },\n ],\n [tr, archivedEnabled],\n );\n\n const currentTab = params.get(\"tab\");\n // Render `activeId` correctly for the archived-off case; the effect below cleans the URL lazily.\n const activeId =\n currentTab === \"archived\" && !archivedEnabled\n ? DEFAULT_TAB_ID\n : (currentTab ?? DEFAULT_TAB_ID);\n\n useEffect(() => {\n if (!archivedEnabled && currentTab === \"archived\") {\n // Updater form reads the freshest params at flush time to preserve concurrent query-param changes.\n setParams(\n (prev) => {\n const next = new URLSearchParams(prev);\n next.delete(\"tab\");\n return next;\n },\n { replace: true },\n );\n }\n }, [archivedEnabled, currentTab, setParams]);\n\n const handleTabChange = (id: string) => {\n setParams(\n (prev) => {\n const next = new URLSearchParams(prev);\n if (id === DEFAULT_TAB_ID) {\n next.delete(\"tab\");\n } else {\n next.set(\"tab\", id);\n }\n return next;\n },\n { replace: true },\n );\n };\n\n return (\n <PermissionGuard\n permission={CONVERSATIONS_PERMISSIONS.CONVERSATION_READ}\n allowUnavailablePermissions\n >\n <main\n data-tour=\"conversations-landing\"\n className=\"flex flex-col h-full\"\n aria-labelledby=\"conversations-page-heading\"\n >\n <h1 id=\"conversations-page-heading\" className=\"sr-only\">\n {tr(\"conversations.title\", \"Conversations\")}\n </h1>\n <div\n data-tour=\"conversations-tabs\"\n className=\"border-b border-border-subtle px-4 pt-3\"\n >\n <Tabs\n items={tabs}\n activeTabId={activeId}\n onTabChange={handleTabChange}\n variant=\"underline\"\n />\n </div>\n <section\n className=\"flex-1 min-h-0 overflow-auto\"\n aria-live=\"polite\"\n aria-label={tr(\n \"conversations.activePanel\",\n \"Active conversations panel\",\n )}\n >\n {activeId === \"all\" && <ConversationListPage />}\n {activeId === \"channels\" && (\n <ConversationListPage forcedFilterParam=\"channel\" />\n )}\n {activeId === \"direct\" && (\n <ConversationListPage forcedFilterParam=\"direct\" />\n )}\n {activeId === \"groups\" && (\n <ConversationListPage forcedFilterParam=\"group\" />\n )}\n {activeId === \"archived\" && (\n <ComingSoon\n title={tr(\n \"conversations.archivedTitle\",\n \"Archived Conversations\",\n )}\n />\n )}\n {activeId === \"new\" && <NewConversationPage />}\n {activeId === \"settings\" && <ConversationsSettingsPage />}\n </section>\n </main>\n </PermissionGuard>\n );\n}\n"],"mappings":";;;;;;;;;;;;AAWA,IAAM,IAAiB;AAMvB,SAAS,EAAW,EAAE,YAA4B;CAChD,IAAM,IAAK,GAAO;AAElB,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,MAAD;IAAI,WAAU;cAAyC;IAAW,CAAA,EAClE,kBAAC,KAAD;IAAG,WAAU;cACV,EAAG,4BAA4B,eAAe;IAC7C,CAAA,CACA;;EACF,CAAA;;AAWV,SAAgB,IAA2B;CACzC,IAAM,CAAC,GAAQ,KAAa,GAAiB,EACvC,IAAK,GAAO,EAEZ,EAAE,uBAAoB,GAAqB,EAE3C,IAAkB,QAChB;EACJ;GACE,IAAI;GACJ,OAAO,EAAG,kCAAkC,oBAAoB;GACjE;EACD;GAAE,IAAI;GAAY,OAAO,EAAG,0BAA0B,WAAW;GAAE;EACnE;GACE,IAAI;GACJ,OAAO,EAAG,gCAAgC,kBAAkB;GAC7D;EACD;GAAE,IAAI;GAAU,OAAO,EAAG,wBAAwB,SAAS;GAAE;EAC7D,GAAI,IACA,CAAC;GAAE,IAAI;GAAY,OAAO,EAAG,0BAA0B,WAAW;GAAE,CAAC,GACrE,EAAE;EACN;GAAE,IAAI;GAAO,OAAO,EAAG,qBAAqB,MAAM;GAAE;EACpD;GAAE,IAAI;GAAY,OAAO,EAAG,0BAA0B,WAAW;GAAE;EACpE,EACD,CAAC,GAAI,EAAgB,CACtB,EAEK,IAAa,EAAO,IAAI,MAAM,EAE9B,IACJ,MAAe,cAAc,CAAC,IAC1B,IACC,KAAc;AA+BrB,QA7BA,QAAgB;AACd,EAAI,CAAC,KAAmB,MAAe,cAErC,GACG,MAAS;GACR,IAAM,IAAO,IAAI,gBAAgB,EAAK;AAEtC,UADA,EAAK,OAAO,MAAM,EACX;KAET,EAAE,SAAS,IAAM,CAClB;IAEF;EAAC;EAAiB;EAAY;EAAU,CAAC,EAkB1C,kBAAC,GAAD;EACE,YAAY,EAA0B;EACtC,6BAAA;YAEA,kBAAC,QAAD;GACE,aAAU;GACV,WAAU;GACV,mBAAgB;aAHlB;IAKE,kBAAC,MAAD;KAAI,IAAG;KAA6B,WAAU;eAC3C,EAAG,uBAAuB,gBAAgB;KACxC,CAAA;IACL,kBAAC,OAAD;KACE,aAAU;KACV,WAAU;eAEV,kBAAC,GAAD;MACE,OAAO;MACP,aAAa;MACb,cAnCe,MAAe;AACtC,UACG,MAAS;QACR,IAAM,IAAO,IAAI,gBAAgB,EAAK;AAMtC,eALI,MAAO,IACT,EAAK,OAAO,MAAM,GAElB,EAAK,IAAI,OAAO,EAAG,EAEd;UAET,EAAE,SAAS,IAAM,CAClB;;MAwBO,SAAQ;MACR,CAAA;KACE,CAAA;IACN,kBAAC,WAAD;KACE,WAAU;KACV,aAAU;KACV,cAAY,EACV,6BACA,6BACD;eANH;MAQG,MAAa,SAAS,kBAAC,GAAD,EAAwB,CAAA;MAC9C,MAAa,cACZ,kBAAC,GAAD,EAAsB,mBAAkB,WAAY,CAAA;MAErD,MAAa,YACZ,kBAAC,GAAD,EAAsB,mBAAkB,UAAW,CAAA;MAEpD,MAAa,YACZ,kBAAC,GAAD,EAAsB,mBAAkB,SAAU,CAAA;MAEnD,MAAa,cACZ,kBAAC,GAAD,EACE,OAAO,EACL,+BACA,yBACD,EACD,CAAA;MAEH,MAAa,SAAS,kBAAC,GAAD,EAAuB,CAAA;MAC7C,MAAa,cAAc,kBAAC,GAAD,EAA6B,CAAA;MACjD;;IACL;;EACS,CAAA"}
1
+ {"version":3,"file":"ConversationsLandingPage.js","names":[],"sources":["../../../src/conversations/pages/ConversationsLandingPage.tsx"],"sourcesContent":["import { Suspense, useCallback, useEffect, useMemo, lazy } from \"react\";\nimport { useParams, useSearchParams } from \"react-router-dom\";\nimport { PageContainer } from \"@burdenoff/fe-libs/ui\";\nimport { Pin, Filter, Loader2 } from \"lucide-react\";\nimport { ConversationListPage } from \"./ConversationListPage\";\nimport { PermissionGuard } from \"../components/PermissionGuard\";\nimport {\n InboxLayout,\n type FilterChipDescriptor,\n type InboxTabId,\n} from \"../components/InboxLayout\";\nimport { CONVERSATIONS_PERMISSIONS } from \"../constants/permissions\";\nimport { useUserFeatureFlags } from \"../hooks/useUserFeatureFlags\";\nimport { useConversationsStore } from \"../store/conversationsStore\";\nimport { useTr } from \"../utils/i18n\";\n\n// Lazy-load the detail page so the inbox bundle stays small for users who\n// never open a conversation in this session. The detail subtree is the\n// heaviest by far (~19kB gz including MessageBubble).\nconst ConversationDetailPage = lazy(() =>\n import(\"./ConversationDetailPage\").then((m) => ({\n default: m.ConversationDetailPage,\n })),\n);\n\nconst DEFAULT_TAB_ID: InboxTabId = \"all\";\n\n// Maps the inbox tab id to the `forcedFilterParam` prop accepted by\n// ConversationListPage. The \"all\" tab passes undefined so the list shows\n// every conversation type. The archived tab triggers the BE state filter\n// (states: [ARCHIVED]) handled inside ConversationListPage.\nconst TAB_TO_LIST_FILTER: Record<\n InboxTabId,\n \"channel\" | \"direct\" | \"group\" | \"archived\" | undefined\n> = {\n all: undefined,\n direct: \"direct\",\n groups: \"group\",\n channels: \"channel\",\n archived: \"archived\",\n};\n\n/**\n * Landing page for `/conversations`.\n *\n * Renders the canonical inbox: two-pane shell with the conversation list on\n * the left and an empty detail panel on the right. The detail panel is\n * filled in by the router on `/conversations/:conversationId`; this page\n * itself only renders the chrome and the list.\n *\n * Tabs (All / Direct / Groups / Channels / Archived) are stored in the\n * `?tab=` URL search param so refreshing the page keeps the user on the\n * same view.\n */\nexport function ConversationsLandingPage() {\n const [params, setParams] = useSearchParams();\n const { conversationId } = useParams<{ conversationId?: string }>();\n const tr = useTr();\n const { archivedEnabled } = useUserFeatureFlags();\n const {\n searchQuery,\n setSearchQuery,\n conversationFilter,\n updateConversationFilter,\n } = useConversationsStore();\n\n const requestedTab = (params.get(\"tab\") ?? DEFAULT_TAB_ID) as InboxTabId;\n // Strip the archived tab when the feature flag is off — the side effect\n // below also lazily cleans the URL so a deep link doesn't leave the user\n // staring at an unknown tab.\n const activeTab: InboxTabId =\n requestedTab === \"archived\" && !archivedEnabled\n ? DEFAULT_TAB_ID\n : requestedTab;\n\n useEffect(() => {\n if (!archivedEnabled && requestedTab === \"archived\") {\n // Updater form reads the freshest params at flush time to preserve\n // concurrent query-param changes elsewhere in the tree.\n setParams(\n (prev) => {\n const next = new URLSearchParams(prev);\n next.delete(\"tab\");\n return next;\n },\n { replace: true },\n );\n }\n }, [archivedEnabled, requestedTab, setParams]);\n\n const handleTabChange = useCallback(\n (id: InboxTabId) => {\n setParams(\n (prev) => {\n const next = new URLSearchParams(prev);\n if (id === DEFAULT_TAB_ID) {\n next.delete(\"tab\");\n } else {\n next.set(\"tab\", id);\n }\n return next;\n },\n { replace: true },\n );\n },\n [setParams],\n );\n\n // Filter chips are only meaningful on tabs where they can hide items. On\n // the Archived tab the only sane secondary toggle is \"Pinned\" (unread\n // doesn't make sense for archived conversations); on the inbox tabs we\n // expose both unread + pinned.\n const filterChips = useMemo<FilterChipDescriptor[]>(() => {\n if (activeTab === \"archived\") return [];\n const unreadActive = conversationFilter.hasUnread === true;\n const pinnedActive = conversationFilter.isPinned === true;\n return [\n {\n id: \"unread\",\n label: tr(\"conversations.unread\", \"Unread\"),\n active: unreadActive,\n onToggle: () =>\n updateConversationFilter({\n hasUnread: unreadActive ? undefined : true,\n }),\n icon: <Filter size={11} aria-hidden=\"true\" />,\n },\n {\n id: \"pinned\",\n label: tr(\"conversations.pinned\", \"Pinned\"),\n active: pinnedActive,\n onToggle: () =>\n updateConversationFilter({\n isPinned: pinnedActive ? undefined : true,\n }),\n icon: <Pin size={11} aria-hidden=\"true\" />,\n },\n ];\n }, [\n activeTab,\n conversationFilter.hasUnread,\n conversationFilter.isPinned,\n tr,\n updateConversationFilter,\n ]);\n\n const forcedFilter = TAB_TO_LIST_FILTER[activeTab];\n\n return (\n <PermissionGuard\n permission={CONVERSATIONS_PERMISSIONS.CONVERSATION_READ}\n allowUnavailablePermissions\n >\n <PageContainer\n data-tour=\"conversations-landing\"\n padded={false}\n className=\"h-full min-h-0\"\n >\n <h1 id=\"conversations-page-heading\" className=\"sr-only\">\n {tr(\"conversations.title\", \"Conversations\")}\n </h1>\n <InboxLayout\n activeTab={activeTab}\n onTabChange={handleTabChange}\n archivedEnabled={archivedEnabled}\n searchQuery={searchQuery}\n onSearchChange={setSearchQuery}\n filterChips={filterChips}\n listSlot={\n <ConversationListPage embedded forcedFilterParam={forcedFilter} />\n }\n detailSlot={\n conversationId ? (\n <Suspense\n fallback={\n <div\n role=\"status\"\n aria-live=\"polite\"\n className=\"flex h-full items-center justify-center gap-2 text-sm text-text-secondary\"\n >\n <Loader2\n size={16}\n className=\"animate-spin\"\n aria-hidden=\"true\"\n />\n {tr(\"conversations.detailLoading\", \"Loading conversation…\")}\n </div>\n }\n >\n <ConversationDetailPage />\n </Suspense>\n ) : undefined\n }\n />\n </PageContainer>\n </PermissionGuard>\n );\n}\n"],"mappings":";;;;;;;;;;;;;AAmBA,IAAM,IAAyB,QAC7B,OAAO,+BAA4B,MAAM,OAAO,EAC9C,SAAS,EAAE,wBACZ,EAAE,CACJ,EAEK,IAA6B,OAM7B,IAGF;CACF,KAAK,KAAA;CACL,QAAQ;CACR,QAAQ;CACR,UAAU;CACV,UAAU;CACX;AAcD,SAAgB,IAA2B;CACzC,IAAM,CAAC,GAAQ,KAAa,GAAiB,EACvC,EAAE,sBAAmB,GAAwC,EAC7D,IAAK,GAAO,EACZ,EAAE,uBAAoB,GAAqB,EAC3C,EACJ,gBACA,mBACA,uBACA,gCACE,GAAuB,EAErB,IAAgB,EAAO,IAAI,MAAM,IAAI,GAIrC,IACJ,MAAiB,cAAc,CAAC,IAC5B,IACA;AAEN,SAAgB;AACd,EAAI,CAAC,KAAmB,MAAiB,cAGvC,GACG,MAAS;GACR,IAAM,IAAO,IAAI,gBAAgB,EAAK;AAEtC,UADA,EAAK,OAAO,MAAM,EACX;KAET,EAAE,SAAS,IAAM,CAClB;IAEF;EAAC;EAAiB;EAAc;EAAU,CAAC;CAE9C,IAAM,IAAkB,GACrB,MAAmB;AAClB,KACG,MAAS;GACR,IAAM,IAAO,IAAI,gBAAgB,EAAK;AAMtC,UALI,MAAO,IACT,EAAK,OAAO,MAAM,GAElB,EAAK,IAAI,OAAO,EAAG,EAEd;KAET,EAAE,SAAS,IAAM,CAClB;IAEH,CAAC,EAAU,CACZ,EAMK,IAAc,QAAsC;AACxD,MAAI,MAAc,WAAY,QAAO,EAAE;EACvC,IAAM,IAAe,EAAmB,cAAc,IAChD,IAAe,EAAmB,aAAa;AACrD,SAAO,CACL;GACE,IAAI;GACJ,OAAO,EAAG,wBAAwB,SAAS;GAC3C,QAAQ;GACR,gBACE,EAAyB,EACvB,WAAW,IAAe,KAAA,IAAY,IACvC,CAAC;GACJ,MAAM,kBAAC,GAAD;IAAQ,MAAM;IAAI,eAAY;IAAS,CAAA;GAC9C,EACD;GACE,IAAI;GACJ,OAAO,EAAG,wBAAwB,SAAS;GAC3C,QAAQ;GACR,gBACE,EAAyB,EACvB,UAAU,IAAe,KAAA,IAAY,IACtC,CAAC;GACJ,MAAM,kBAAC,GAAD;IAAK,MAAM;IAAI,eAAY;IAAS,CAAA;GAC3C,CACF;IACA;EACD;EACA,EAAmB;EACnB,EAAmB;EACnB;EACA;EACD,CAAC,EAEI,IAAe,EAAmB;AAExC,QACE,kBAAC,GAAD;EACE,YAAY,EAA0B;EACtC,6BAAA;YAEA,kBAAC,GAAD;GACE,aAAU;GACV,QAAQ;GACR,WAAU;aAHZ,CAKE,kBAAC,MAAD;IAAI,IAAG;IAA6B,WAAU;cAC3C,EAAG,uBAAuB,gBAAgB;IACxC,CAAA,EACL,kBAAC,GAAD;IACa;IACX,aAAa;IACI;IACJ;IACb,gBAAgB;IACH;IACb,UACE,kBAAC,GAAD;KAAsB,UAAA;KAAS,mBAAmB;KAAgB,CAAA;IAEpE,YACE,IACE,kBAAC,GAAD;KACE,UACE,kBAAC,OAAD;MACE,MAAK;MACL,aAAU;MACV,WAAU;gBAHZ,CAKE,kBAAC,GAAD;OACE,MAAM;OACN,WAAU;OACV,eAAY;OACZ,CAAA,EACD,EAAG,+BAA+B,wBAAwB,CACvD;;eAGR,kBAAC,GAAD,EAA0B,CAAA;KACjB,CAAA,GACT,KAAA;IAEN,CAAA,CACY;;EACA,CAAA"}