@burdenoff/microfe-comradecircle 2026.704.2 → 2026.713.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 (163) hide show
  1. package/dist/comradecircle/ComradeCircleRoot.d.ts +5 -0
  2. package/dist/comradecircle/ComradeCircleRoot.d.ts.map +1 -0
  3. package/dist/comradecircle/ComradeCircleRoutes.d.ts +4 -0
  4. package/dist/comradecircle/ComradeCircleRoutes.d.ts.map +1 -0
  5. package/dist/comradecircle/components/Avatar.d.ts +20 -0
  6. package/dist/comradecircle/components/Avatar.d.ts.map +1 -0
  7. package/dist/comradecircle/components/Avatar.js +40 -17
  8. package/dist/comradecircle/components/Avatar.js.map +1 -1
  9. package/dist/comradecircle/components/AvatarUploadField.d.ts +21 -0
  10. package/dist/comradecircle/components/AvatarUploadField.d.ts.map +1 -0
  11. package/dist/comradecircle/components/AvatarUploadField.js +25 -14
  12. package/dist/comradecircle/components/AvatarUploadField.js.map +1 -1
  13. package/dist/comradecircle/components/CircleCard.d.ts +10 -0
  14. package/dist/comradecircle/components/CircleCard.d.ts.map +1 -0
  15. package/dist/comradecircle/components/CircleCard.js +54 -52
  16. package/dist/comradecircle/components/CircleCard.js.map +1 -1
  17. package/dist/comradecircle/components/CommentThread.d.ts +14 -0
  18. package/dist/comradecircle/components/CommentThread.d.ts.map +1 -0
  19. package/dist/comradecircle/components/CommentThread.js +80 -64
  20. package/dist/comradecircle/components/CommentThread.js.map +1 -1
  21. package/dist/comradecircle/components/CreateCircleDialog.d.ts +10 -0
  22. package/dist/comradecircle/components/CreateCircleDialog.d.ts.map +1 -0
  23. package/dist/comradecircle/components/CreateEventDialog.d.ts +12 -0
  24. package/dist/comradecircle/components/CreateEventDialog.d.ts.map +1 -0
  25. package/dist/comradecircle/components/DataTable.d.ts +28 -0
  26. package/dist/comradecircle/components/DataTable.d.ts.map +1 -0
  27. package/dist/comradecircle/components/EmptyState.d.ts +18 -0
  28. package/dist/comradecircle/components/EmptyState.d.ts.map +1 -0
  29. package/dist/comradecircle/components/EventCard.d.ts +9 -0
  30. package/dist/comradecircle/components/EventCard.d.ts.map +1 -0
  31. package/dist/comradecircle/components/EventCard.js +84 -58
  32. package/dist/comradecircle/components/EventCard.js.map +1 -1
  33. package/dist/comradecircle/components/FileImage.d.ts +18 -0
  34. package/dist/comradecircle/components/FileImage.d.ts.map +1 -0
  35. package/dist/comradecircle/components/FilterTabs.d.ts +18 -0
  36. package/dist/comradecircle/components/FilterTabs.d.ts.map +1 -0
  37. package/dist/comradecircle/components/ImageAttachments.d.ts +20 -0
  38. package/dist/comradecircle/components/ImageAttachments.d.ts.map +1 -0
  39. package/dist/comradecircle/components/MemberIdentity.d.ts +16 -0
  40. package/dist/comradecircle/components/MemberIdentity.d.ts.map +1 -0
  41. package/dist/comradecircle/components/MemberIdentity.js +2 -1
  42. package/dist/comradecircle/components/MemberIdentity.js.map +1 -1
  43. package/dist/comradecircle/components/MetricsCard.d.ts +23 -0
  44. package/dist/comradecircle/components/MetricsCard.d.ts.map +1 -0
  45. package/dist/comradecircle/components/MiniBar.d.ts +16 -0
  46. package/dist/comradecircle/components/MiniBar.d.ts.map +1 -0
  47. package/dist/comradecircle/components/PageLayout.d.ts +16 -0
  48. package/dist/comradecircle/components/PageLayout.d.ts.map +1 -0
  49. package/dist/comradecircle/components/Pagination.d.ts +22 -0
  50. package/dist/comradecircle/components/Pagination.d.ts.map +1 -0
  51. package/dist/comradecircle/components/PollCard.d.ts +10 -0
  52. package/dist/comradecircle/components/PollCard.d.ts.map +1 -0
  53. package/dist/comradecircle/components/PollCard.js +65 -44
  54. package/dist/comradecircle/components/PollCard.js.map +1 -1
  55. package/dist/comradecircle/components/PostCard.d.ts +10 -0
  56. package/dist/comradecircle/components/PostCard.d.ts.map +1 -0
  57. package/dist/comradecircle/components/PostCard.js +1 -1
  58. package/dist/comradecircle/components/PostCard.js.map +1 -1
  59. package/dist/comradecircle/components/PostComposer.d.ts +16 -0
  60. package/dist/comradecircle/components/PostComposer.d.ts.map +1 -0
  61. package/dist/comradecircle/components/ReactionBar.d.ts +8 -0
  62. package/dist/comradecircle/components/ReactionBar.d.ts.map +1 -0
  63. package/dist/comradecircle/components/ReactionBar.js +1 -1
  64. package/dist/comradecircle/components/ReactionBar.js.map +1 -1
  65. package/dist/comradecircle/components/StateViews.d.ts +24 -0
  66. package/dist/comradecircle/components/StateViews.d.ts.map +1 -0
  67. package/dist/comradecircle/components/StateViews.js +1 -16
  68. package/dist/comradecircle/components/StateViews.js.map +1 -1
  69. package/dist/comradecircle/components/StatusBadge.d.ts +9 -0
  70. package/dist/comradecircle/components/StatusBadge.d.ts.map +1 -0
  71. package/dist/comradecircle/components/StatusBadge.js +1 -1
  72. package/dist/comradecircle/components/StatusBadge.js.map +1 -1
  73. package/dist/comradecircle/components/Tabs.d.ts +17 -0
  74. package/dist/comradecircle/components/Tabs.d.ts.map +1 -0
  75. package/dist/comradecircle/components/TagInput.d.ts +18 -0
  76. package/dist/comradecircle/components/TagInput.d.ts.map +1 -0
  77. package/dist/comradecircle/components/index.d.ts +30 -0
  78. package/dist/comradecircle/components/index.d.ts.map +1 -0
  79. package/dist/comradecircle/context/ComradeCircleContext.d.ts +16 -0
  80. package/dist/comradecircle/context/ComradeCircleContext.d.ts.map +1 -0
  81. package/dist/comradecircle/context/index.d.ts +6 -0
  82. package/dist/comradecircle/context/index.d.ts.map +1 -0
  83. package/dist/comradecircle/hooks/index.d.ts +8 -0
  84. package/dist/comradecircle/hooks/index.d.ts.map +1 -0
  85. package/dist/comradecircle/hooks/useComradeCircleApi.d.ts +103 -0
  86. package/dist/comradecircle/hooks/useComradeCircleApi.d.ts.map +1 -0
  87. package/dist/comradecircle/hooks/useComradeCircleApi.js +1 -1
  88. package/dist/comradecircle/hooks/useComradeCircleApi.js.map +1 -1
  89. package/dist/comradecircle/hooks/useCursorPager.d.ts +33 -0
  90. package/dist/comradecircle/hooks/useCursorPager.d.ts.map +1 -0
  91. package/dist/comradecircle/hooks/useFiles.d.ts +28 -0
  92. package/dist/comradecircle/hooks/useFiles.d.ts.map +1 -0
  93. package/dist/comradecircle/index.d.ts +10 -0
  94. package/dist/comradecircle/index.d.ts.map +1 -0
  95. package/dist/comradecircle/index.js +1 -0
  96. package/dist/comradecircle/lib/graphql-cache.d.ts +26 -0
  97. package/dist/comradecircle/lib/graphql-cache.d.ts.map +1 -0
  98. package/dist/comradecircle/lib/toast.d.ts +23 -0
  99. package/dist/comradecircle/lib/toast.d.ts.map +1 -0
  100. package/dist/comradecircle/pages/FeedPage.d.ts +10 -0
  101. package/dist/comradecircle/pages/FeedPage.d.ts.map +1 -0
  102. package/dist/comradecircle/pages/FeedPage.js +79 -33
  103. package/dist/comradecircle/pages/FeedPage.js.map +1 -1
  104. package/dist/comradecircle/pages/circles/CircleDetailPage.d.ts +11 -0
  105. package/dist/comradecircle/pages/circles/CircleDetailPage.d.ts.map +1 -0
  106. package/dist/comradecircle/pages/circles/CircleDetailPage.js +354 -243
  107. package/dist/comradecircle/pages/circles/CircleDetailPage.js.map +1 -1
  108. package/dist/comradecircle/pages/circles/CirclesPage.d.ts +11 -0
  109. package/dist/comradecircle/pages/circles/CirclesPage.d.ts.map +1 -0
  110. package/dist/comradecircle/pages/circles/CirclesPage.js +97 -47
  111. package/dist/comradecircle/pages/circles/CirclesPage.js.map +1 -1
  112. package/dist/comradecircle/pages/events/EventDetailPage.d.ts +11 -0
  113. package/dist/comradecircle/pages/events/EventDetailPage.d.ts.map +1 -0
  114. package/dist/comradecircle/pages/events/EventDetailPage.js +193 -126
  115. package/dist/comradecircle/pages/events/EventDetailPage.js.map +1 -1
  116. package/dist/comradecircle/pages/events/EventsPage.d.ts +11 -0
  117. package/dist/comradecircle/pages/events/EventsPage.d.ts.map +1 -0
  118. package/dist/comradecircle/pages/events/EventsPage.js +74 -34
  119. package/dist/comradecircle/pages/events/EventsPage.js.map +1 -1
  120. package/dist/comradecircle/pages/members/MemberProfilePage.d.ts +13 -0
  121. package/dist/comradecircle/pages/members/MemberProfilePage.d.ts.map +1 -0
  122. package/dist/comradecircle/pages/members/MemberProfilePage.js +154 -82
  123. package/dist/comradecircle/pages/members/MemberProfilePage.js.map +1 -1
  124. package/dist/comradecircle/pages/members/MembersPage.d.ts +13 -0
  125. package/dist/comradecircle/pages/members/MembersPage.d.ts.map +1 -0
  126. package/dist/comradecircle/pages/members/MembersPage.js +140 -71
  127. package/dist/comradecircle/pages/members/MembersPage.js.map +1 -1
  128. package/dist/comradecircle/pages/profile/MyProfilePage.d.ts +17 -0
  129. package/dist/comradecircle/pages/profile/MyProfilePage.d.ts.map +1 -0
  130. package/dist/comradecircle/pages/profile/MyProfilePage.js +298 -202
  131. package/dist/comradecircle/pages/profile/MyProfilePage.js.map +1 -1
  132. package/dist/comradecircle/types/index.d.ts +362 -0
  133. package/dist/comradecircle/types/index.d.ts.map +1 -0
  134. package/dist/comradecircle/utils/cn.d.ts +4 -0
  135. package/dist/comradecircle/utils/cn.d.ts.map +1 -0
  136. package/dist/comradecircle/utils/formatters.d.ts +26 -0
  137. package/dist/comradecircle/utils/formatters.d.ts.map +1 -0
  138. package/dist/comradecircle/utils/i18n.d.ts +6 -0
  139. package/dist/comradecircle/utils/i18n.d.ts.map +1 -0
  140. package/dist/comradecircle/utils/index.d.ts +10 -0
  141. package/dist/comradecircle/utils/index.d.ts.map +1 -0
  142. package/dist/comradecircle/utils/navigation.d.ts +14 -0
  143. package/dist/comradecircle/utils/navigation.d.ts.map +1 -0
  144. package/dist/comradecircle/utils/slug.d.ts +6 -0
  145. package/dist/comradecircle/utils/slug.d.ts.map +1 -0
  146. package/dist/dev/main.d.ts +2 -0
  147. package/dist/dev/main.d.ts.map +1 -0
  148. package/dist/generated/global-operations.d.ts +2 -0
  149. package/dist/generated/global-operations.d.ts.map +1 -0
  150. package/dist/generated/global-types.d.ts +18199 -0
  151. package/dist/generated/global-types.d.ts.map +1 -0
  152. package/dist/generated/wspace-operations.d.ts +2 -0
  153. package/dist/generated/wspace-operations.d.ts.map +1 -0
  154. package/dist/generated/wspace-types.d.ts +43046 -0
  155. package/dist/generated/wspace-types.d.ts.map +1 -0
  156. package/dist/index.d.ts +2 -0
  157. package/dist/index.d.ts.map +1 -0
  158. package/dist/index.js +2 -1
  159. package/dist/services/createSandboxAssistantTransport.d.ts +24 -0
  160. package/dist/services/createSandboxAssistantTransport.d.ts.map +1 -0
  161. package/dist/services/createSandboxAssistantTransport.js +12 -0
  162. package/dist/services/createSandboxAssistantTransport.js.map +1 -0
  163. package/package.json +6 -2
@@ -0,0 +1,20 @@
1
+ /**
2
+ * ImageAttachments — attach images to a post + render a post's attachments.
3
+ * @module comradecircle/components
4
+ *
5
+ * Uploads go to wspace-files-svc (reuse, no blob storage here); the composer
6
+ * keeps only file ids, which are persisted as ComPost.attachmentFileIds.
7
+ */
8
+ interface AttachmentPickerProps {
9
+ fileIds: string[];
10
+ onChange: (fileIds: string[]) => void;
11
+ disabled?: boolean;
12
+ }
13
+ export declare function AttachmentPicker({ fileIds, onChange, disabled }: AttachmentPickerProps): import("react/jsx-runtime").JSX.Element;
14
+ interface AttachmentGridProps {
15
+ fileIds: string[];
16
+ className?: string;
17
+ }
18
+ export declare function AttachmentGrid({ fileIds, className }: AttachmentGridProps): import("react/jsx-runtime").JSX.Element | null;
19
+ export {};
20
+ //# sourceMappingURL=ImageAttachments.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ImageAttachments.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/ImageAttachments.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAcH,UAAU,qBAAqB;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,QAAQ,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACtC,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED,wBAAgB,gBAAgB,CAAC,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAgB,EAAE,EAAE,qBAAqB,2CA2F9F;AAID,UAAU,mBAAmB;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAAE,OAAO,EAAE,SAAS,EAAE,EAAE,mBAAmB,kDAuBzE"}
@@ -0,0 +1,16 @@
1
+ import { FC } from 'react';
2
+
3
+ interface MemberIdentityProps {
4
+ memberId: string;
5
+ /** Layout: 'inline' = avatar + name only; 'row' = avatar + name + headline. */
6
+ variant?: 'inline' | 'row';
7
+ size?: 'xs' | 'sm' | 'md' | 'lg';
8
+ /** Suffix shown after the name (e.g. a timestamp) in inline variant. */
9
+ suffix?: string;
10
+ showPresence?: boolean;
11
+ onClick?: () => void;
12
+ className?: string;
13
+ }
14
+ export declare const MemberIdentity: FC<MemberIdentityProps>;
15
+ export {};
16
+ //# sourceMappingURL=MemberIdentity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MemberIdentity.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/MemberIdentity.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAOhC,UAAU,mBAAmB;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,+EAA+E;IAC/E,OAAO,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAC;IAC3B,IAAI,CAAC,EAAE,IAAI,GAAG,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC;IACjC,wEAAwE;IACxE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAUD,eAAO,MAAM,cAAc,EAAE,EAAE,CAAC,mBAAmB,CA+ClD,CAAC"}
@@ -17,6 +17,7 @@ var s = ({ memberId: s, variant: c = "inline", size: l = "sm", suffix: u, showPr
17
17
  className: t("flex items-center gap-2.5 text-left", f && "rounded-lg transition-colors hover:opacity-80", p),
18
18
  children: [/* @__PURE__ */ i(r, {
19
19
  name: v,
20
+ seed: s,
20
21
  size: l,
21
22
  presence: y,
22
23
  fileId: g?.avatar?.id
@@ -27,7 +28,7 @@ var s = ({ memberId: s, variant: c = "inline", size: l = "sm", suffix: u, showPr
27
28
  children: [/* @__PURE__ */ i("span", {
28
29
  className: "truncate text-sm font-medium text-text-primary",
29
30
  children: _
30
- }), u && c === "inline" && /* @__PURE__ */ a("span", {
31
+ }), u && /* @__PURE__ */ a("span", {
31
32
  className: "shrink-0 text-xs text-text-tertiary",
32
33
  children: ["· ", u]
33
34
  })]
@@ -1 +1 @@
1
- {"version":3,"file":"MemberIdentity.js","names":[],"sources":["../../../src/comradecircle/components/MemberIdentity.tsx"],"sourcesContent":["/**\n * MemberIdentity — resolve a `memberId` to a display name + avatar via the\n * product's own social-identity layer (`ComProfile`).\n * @module comradecircle/components\n *\n * The composed `User` entity resolves `id` only, so we never render a raw actor\n * id. Instead we look up the member's `ComProfile` (cached aggressively). The\n * calling user is labelled \"You\". If a member has not created a profile yet we\n * fall back to a neutral \"Member\" label with initials from the id.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\nimport { useComProfile } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { Avatar } from './Avatar';\nimport type { ComPresence } from '../types';\n\ninterface MemberIdentityProps {\n memberId: string;\n /** Layout: 'inline' = avatar + name only; 'row' = avatar + name + headline. */\n variant?: 'inline' | 'row';\n size?: 'xs' | 'sm' | 'md' | 'lg';\n /** Suffix shown after the name (e.g. a timestamp) in inline variant. */\n suffix?: string;\n showPresence?: boolean;\n onClick?: () => void;\n className?: string;\n}\n\nfunction labelForMember(\n displayName: string | undefined,\n isSelf: boolean\n): { name: string; presence?: ComPresence | null } {\n if (displayName) return { name: isSelf ? `${displayName} (You)` : displayName };\n return { name: isSelf ? 'You' : 'Member' };\n}\n\nexport const MemberIdentity: FC<MemberIdentityProps> = ({\n memberId,\n variant = 'inline',\n size = 'sm',\n suffix,\n showPresence = false,\n onClick,\n className,\n}) => {\n const { currentUser } = useComradeCircle();\n const isSelf = Boolean(currentUser?.id && currentUser.id === memberId);\n const { data: profile } = useComProfile(memberId);\n\n const { name } = labelForMember(profile?.displayName ?? undefined, isSelf);\n const nameForAvatar = profile?.displayName ?? (isSelf ? (currentUser?.name ?? 'You') : memberId);\n const presence = showPresence ? (profile?.presence ?? null) : null;\n\n const Wrapper = onClick ? 'button' : 'div';\n\n return (\n <Wrapper\n type={onClick ? 'button' : undefined}\n onClick={onClick}\n className={cn(\n 'flex items-center gap-2.5 text-left',\n onClick && 'rounded-lg transition-colors hover:opacity-80',\n className\n )}\n >\n <Avatar name={nameForAvatar} size={size} presence={presence} fileId={profile?.avatar?.id} />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"truncate text-sm font-medium text-text-primary\">{name}</span>\n {suffix && variant === 'inline' && (\n <span className=\"shrink-0 text-xs text-text-tertiary\">· {suffix}</span>\n )}\n </div>\n {variant === 'row' && profile?.headline && (\n <p className=\"truncate text-xs text-text-secondary\">{profile.headline}</p>\n )}\n </div>\n </Wrapper>\n );\n};\n"],"mappings":";;;;;;;;AA8BA,SAAS,EACP,GACA,GACiD;AAEjD,QADI,IAAoB,EAAE,MAAM,IAAS,GAAG,EAAY,UAAU,GAAa,GACxE,EAAE,MAAM,IAAS,QAAQ,UAAU;;AAG5C,IAAa,KAA2C,EACtD,aACA,aAAU,UACV,UAAO,MACP,WACA,kBAAe,IACf,YACA,mBACI;CACJ,IAAM,EAAE,mBAAgB,GAAkB,EACpC,IAAS,GAAQ,GAAa,MAAM,EAAY,OAAO,IACvD,EAAE,MAAM,MAAY,EAAc,EAAS,EAE3C,EAAE,YAAS,EAAe,GAAS,eAAe,KAAA,GAAW,EAAO,EACpE,IAAgB,GAAS,gBAAgB,IAAU,GAAa,QAAQ,QAAS,IACjF,IAAW,IAAgB,GAAS,YAAY,OAAQ;AAI9D,QACE,kBAHc,IAAU,WAAW,OAGnC;EACE,MAAM,IAAU,WAAW,KAAA;EAClB;EACT,WAAW,EACT,uCACA,KAAW,iDACX,EACD;YAPH,CASE,kBAAC,GAAD;GAAQ,MAAM;GAAqB;GAAgB;GAAU,QAAQ,GAAS,QAAQ;GAAM,CAAA,EAC5F,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAkD;KAAY,CAAA,EAC7E,KAAU,MAAY,YACrB,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAAsD,MAAG,EAAc;OAErE;OACL,MAAY,SAAS,GAAS,YAC7B,kBAAC,KAAD;IAAG,WAAU;cAAwC,EAAQ;IAAa,CAAA,CAExE;KACE"}
1
+ {"version":3,"file":"MemberIdentity.js","names":[],"sources":["../../../src/comradecircle/components/MemberIdentity.tsx"],"sourcesContent":["/**\n * MemberIdentity — resolve a `memberId` to a display name + avatar via the\n * product's own social-identity layer (`ComProfile`).\n * @module comradecircle/components\n *\n * The composed `User` entity resolves `id` only, so we never render a raw actor\n * id. Instead we look up the member's `ComProfile` (cached aggressively). The\n * calling user is labelled \"You\". If a member has not created a profile yet we\n * fall back to a neutral \"Member\" label with initials from the id.\n */\n\nimport type { FC } from 'react';\nimport { cn } from '../utils/cn';\nimport { useComProfile } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { Avatar } from './Avatar';\nimport type { ComPresence } from '../types';\n\ninterface MemberIdentityProps {\n memberId: string;\n /** Layout: 'inline' = avatar + name only; 'row' = avatar + name + headline. */\n variant?: 'inline' | 'row';\n size?: 'xs' | 'sm' | 'md' | 'lg';\n /** Suffix shown after the name (e.g. a timestamp) in inline variant. */\n suffix?: string;\n showPresence?: boolean;\n onClick?: () => void;\n className?: string;\n}\n\nfunction labelForMember(\n displayName: string | undefined,\n isSelf: boolean\n): { name: string; presence?: ComPresence | null } {\n if (displayName) return { name: isSelf ? `${displayName} (You)` : displayName };\n return { name: isSelf ? 'You' : 'Member' };\n}\n\nexport const MemberIdentity: FC<MemberIdentityProps> = ({\n memberId,\n variant = 'inline',\n size = 'sm',\n suffix,\n showPresence = false,\n onClick,\n className,\n}) => {\n const { currentUser } = useComradeCircle();\n const isSelf = Boolean(currentUser?.id && currentUser.id === memberId);\n const { data: profile } = useComProfile(memberId);\n\n const { name } = labelForMember(profile?.displayName ?? undefined, isSelf);\n const nameForAvatar = profile?.displayName ?? (isSelf ? (currentUser?.name ?? 'You') : memberId);\n const presence = showPresence ? (profile?.presence ?? null) : null;\n\n const Wrapper = onClick ? 'button' : 'div';\n\n return (\n <Wrapper\n type={onClick ? 'button' : undefined}\n onClick={onClick}\n className={cn(\n 'flex items-center gap-2.5 text-left',\n onClick && 'rounded-lg transition-colors hover:opacity-80',\n className\n )}\n >\n <Avatar\n name={nameForAvatar}\n seed={memberId}\n size={size}\n presence={presence}\n fileId={profile?.avatar?.id}\n />\n <div className=\"min-w-0\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"truncate text-sm font-medium text-text-primary\">{name}</span>\n {suffix && <span className=\"shrink-0 text-xs text-text-tertiary\">· {suffix}</span>}\n </div>\n {variant === 'row' && profile?.headline && (\n <p className=\"truncate text-xs text-text-secondary\">{profile.headline}</p>\n )}\n </div>\n </Wrapper>\n );\n};\n"],"mappings":";;;;;;;;AA8BA,SAAS,EACP,GACA,GACiD;AAEjD,QADI,IAAoB,EAAE,MAAM,IAAS,GAAG,EAAY,UAAU,GAAa,GACxE,EAAE,MAAM,IAAS,QAAQ,UAAU;;AAG5C,IAAa,KAA2C,EACtD,aACA,aAAU,UACV,UAAO,MACP,WACA,kBAAe,IACf,YACA,mBACI;CACJ,IAAM,EAAE,mBAAgB,GAAkB,EACpC,IAAS,GAAQ,GAAa,MAAM,EAAY,OAAO,IACvD,EAAE,MAAM,MAAY,EAAc,EAAS,EAE3C,EAAE,YAAS,EAAe,GAAS,eAAe,KAAA,GAAW,EAAO,EACpE,IAAgB,GAAS,gBAAgB,IAAU,GAAa,QAAQ,QAAS,IACjF,IAAW,IAAgB,GAAS,YAAY,OAAQ;AAI9D,QACE,kBAHc,IAAU,WAAW,OAGnC;EACE,MAAM,IAAU,WAAW,KAAA;EAClB;EACT,WAAW,EACT,uCACA,KAAW,iDACX,EACD;YAPH,CASE,kBAAC,GAAD;GACE,MAAM;GACN,MAAM;GACA;GACI;GACV,QAAQ,GAAS,QAAQ;GACzB,CAAA,EACF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAkD;KAAY,CAAA,EAC7E,KAAU,kBAAC,QAAD;KAAM,WAAU;eAAhB,CAAsD,MAAG,EAAc;OAC9E;OACL,MAAY,SAAS,GAAS,YAC7B,kBAAC,KAAD;IAAG,WAAU;cAAwC,EAAQ;IAAa,CAAA,CAExE;KACE"}
@@ -0,0 +1,23 @@
1
+ import { FC, ReactNode } from 'react';
2
+ import { TrendDirection } from '../types';
3
+
4
+ type Accent = 'success' | 'warning' | 'error';
5
+ interface MetricsCardProps {
6
+ icon: ReactNode;
7
+ value: string | number;
8
+ label: string;
9
+ trend?: TrendDirection;
10
+ trendValue?: string;
11
+ className?: string;
12
+ /** Render as the page's hero metric (glass treatment + accent glow). */
13
+ featured?: boolean;
14
+ /** Severity accent — tints the icon and adds a soft top-edge glow. */
15
+ accent?: Accent;
16
+ /** Optional pill shown in the top-right (e.g. "Critical"). */
17
+ pill?: ReactNode;
18
+ /** Optional slot under the value (e.g. a MiniBar or sub-label). */
19
+ footer?: ReactNode;
20
+ }
21
+ export declare const MetricsCard: FC<MetricsCardProps>;
22
+ export {};
23
+ //# sourceMappingURL=MetricsCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MetricsCard.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/MetricsCard.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAI3C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAE/C,KAAK,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAE9C,UAAU,gBAAgB;IACxB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,sEAAsE;IACtE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,mEAAmE;IACnE,MAAM,CAAC,EAAE,SAAS,CAAC;CACpB;AAoBD,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAsC5C,CAAC"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * MiniBar — a thin inline progress bar (target progress, completion, breach %).
3
+ * @module comradecircle/components
4
+ */
5
+ interface MiniBarProps {
6
+ /** 0–100. */
7
+ value: number;
8
+ className?: string;
9
+ /** Bar fill token class (defaults to accent). */
10
+ barClassName?: string;
11
+ /** Height in px. */
12
+ height?: number;
13
+ }
14
+ export declare function MiniBar({ value, className, barClassName, height, }: MiniBarProps): import("react/jsx-runtime").JSX.Element;
15
+ export {};
16
+ //# sourceMappingURL=MiniBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MiniBar.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/MiniBar.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,UAAU,YAAY;IACpB,aAAa;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB;IACpB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,wBAAgB,OAAO,CAAC,EACtB,KAAK,EACL,SAAS,EACT,YAA0B,EAC1B,MAAU,GACX,EAAE,YAAY,2CAiBd"}
@@ -0,0 +1,16 @@
1
+ import { FC, ReactNode } from 'react';
2
+
3
+ interface PageLayoutProps {
4
+ title: ReactNode;
5
+ description?: string;
6
+ actions?: ReactNode;
7
+ children: ReactNode;
8
+ className?: string;
9
+ /** Render the title as a gradient hero heading (reserve for the ONE primary page). */
10
+ titleGradient?: boolean;
11
+ /** Drop a subtle living-glass aurora behind the header (primary page only). */
12
+ aurora?: boolean;
13
+ }
14
+ export declare const PageLayout: FC<PageLayoutProps>;
15
+ export {};
16
+ //# sourceMappingURL=PageLayout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PageLayout.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/PageLayout.tsx"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAI3C,UAAU,eAAe;IACvB,KAAK,EAAE,SAAS,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,SAAS,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sFAAsF;IACtF,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,+EAA+E;IAC/E,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CAkC1C,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Pagination — cursor-aware Prev/Next control for Com* connection lists.
3
+ * @module comradecircle/components
4
+ *
5
+ * The svc list connections return `pageInfo { cursor, hasMore }` and paginate by
6
+ * cursor (offset is a no-op). Pages track a 0-indexed `page` for display and a
7
+ * cursor stack for navigation; this control exposes Prev/Next, disabling Prev on
8
+ * page 0 and Next when the server reports no more rows.
9
+ */
10
+ interface PaginationProps {
11
+ page: number;
12
+ pageSize: number;
13
+ /** Number of rows on the current page. */
14
+ itemCount: number;
15
+ /** Server flag: are there more rows after this page? */
16
+ hasMore: boolean;
17
+ onPageChange: (page: number) => void;
18
+ className?: string;
19
+ }
20
+ export declare function Pagination({ page, pageSize, itemCount, hasMore, onPageChange, className, }: PaginationProps): import("react/jsx-runtime").JSX.Element | null;
21
+ export {};
22
+ //# sourceMappingURL=Pagination.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Pagination.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/Pagination.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,wDAAwD;IACxD,OAAO,EAAE,OAAO,CAAC;IACjB,YAAY,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,QAAQ,EACR,SAAS,EACT,OAAO,EACP,YAAY,EACZ,SAAS,GACV,EAAE,eAAe,kDAuCjB"}
@@ -0,0 +1,10 @@
1
+ import { ComPoll } from '../types';
2
+
3
+ interface PollCardProps {
4
+ poll: ComPoll;
5
+ /** Whether the caller may close the poll (creator / circle admin). */
6
+ canManage?: boolean;
7
+ }
8
+ export declare function PollCard({ poll, canManage }: PollCardProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=PollCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PollCard.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/PollCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAWH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,UAAU,aAAa;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,sEAAsE;IACtE,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAED,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,SAAiB,EAAE,EAAE,aAAa,2CA0HlE"}
@@ -5,54 +5,69 @@ import { describeError as r, notify as i } from "../lib/toast.js";
5
5
  import { formatNumber as a, formatRelativeTime as o } from "../utils/formatters.js";
6
6
  import { StatusBadge as s } from "./StatusBadge.js";
7
7
  import { MiniBar as c } from "./MiniBar.js";
8
- import { CheckCircle2 as l, Lock as u } from "lucide-react";
9
- import { jsx as d, jsxs as f } from "react/jsx-runtime";
10
- import { Button as p, GlassCard as m } from "@burdenoff/fe-libs/ui";
8
+ import { useMemo as l, useState as u } from "react";
9
+ import { CheckCircle2 as d, Crown as f, Lock as p } from "lucide-react";
10
+ import { jsx as m, jsxs as h } from "react/jsx-runtime";
11
+ import { Button as g, GlassCard as _ } from "@burdenoff/fe-libs/ui";
11
12
  //#region src/comradecircle/components/PollCard.tsx
12
- function h({ poll: h, canManage: g = !1 }) {
13
- let _ = n(), v = t(), y = h.options.reduce((e, t) => e + t.voteCount, 0), b = h.status === "CLOSED", x = async (e) => {
14
- if (!b) try {
15
- await _.mutateAsync({
16
- pollId: h.id,
13
+ function v({ poll: v, canManage: y = !1 }) {
14
+ let b = n(), x = t(), [S, C] = u(/* @__PURE__ */ new Set()), w = v.options.reduce((e, t) => e + t.voteCount, 0), T = v.status === "CLOSED", E = l(() => v.options.reduce((e, t) => Math.max(e, t.voteCount), 0), [v.options]), D = S.size > 0, O = async (e) => {
15
+ if (!T) try {
16
+ await b.mutateAsync({
17
+ pollId: v.id,
17
18
  optionId: e,
18
- circleId: h.circleId
19
- }), i.success("Vote recorded");
19
+ circleId: v.circleId
20
+ }), C((t) => new Set(t).add(e)), i.success("Vote recorded");
20
21
  } catch (e) {
21
22
  i.error("Could not vote", r(e));
22
23
  }
23
- }, S = async () => {
24
+ }, k = async () => {
24
25
  try {
25
- await v.mutateAsync(h.id), i.success("Poll closed");
26
+ await x.mutateAsync(v.id), i.success("Poll closed");
26
27
  } catch (e) {
27
28
  i.error("Could not close poll", r(e));
28
29
  }
29
30
  };
30
- return /* @__PURE__ */ f(m, {
31
+ return /* @__PURE__ */ h(_, {
31
32
  treatment: "plain",
32
33
  className: "p-5",
33
34
  children: [
34
- /* @__PURE__ */ f("div", {
35
+ /* @__PURE__ */ h("div", {
35
36
  className: "flex items-start justify-between gap-3",
36
- children: [/* @__PURE__ */ d("h3", {
37
+ children: [/* @__PURE__ */ m("h3", {
37
38
  className: "text-base font-semibold text-text-primary",
38
- children: h.question
39
- }), /* @__PURE__ */ d(s, { status: h.status })]
39
+ children: v.question
40
+ }), /* @__PURE__ */ m(s, { status: v.status })]
40
41
  }),
41
- /* @__PURE__ */ d("div", {
42
+ /* @__PURE__ */ m("div", {
42
43
  className: "mt-4 flex flex-col gap-2.5",
43
- children: [...h.options].sort((e, t) => e.position - t.position).map((t) => {
44
- let n = y > 0 ? t.voteCount / y * 100 : 0;
45
- return /* @__PURE__ */ f("button", {
44
+ children: [...v.options].sort((e, t) => e.position - t.position).map((t) => {
45
+ let n = w > 0 ? t.voteCount / w * 100 : 0, r = w > 0 && t.voteCount === E, i = S.has(t.id);
46
+ return /* @__PURE__ */ h("button", {
46
47
  type: "button",
47
- onClick: () => x(t.id),
48
- disabled: b || _.isPending,
49
- className: e("group w-full rounded-xl border border-border-seam px-3 py-2.5 text-left transition-colors", !b && "hover:border-accent hover:bg-bg-sunken/60", b && "cursor-default"),
50
- children: [/* @__PURE__ */ f("div", {
48
+ onClick: () => O(t.id),
49
+ disabled: T || b.isPending,
50
+ "aria-pressed": i,
51
+ className: e("group w-full rounded-xl border px-3 py-2.5 text-left transition-all duration-200", i ? "border-accent bg-[var(--color-accent-soft)]/40" : "border-border-seam", !T && !i && "hover:-translate-y-px hover:border-accent hover:bg-bg-sunken/60", T && "cursor-default"),
52
+ children: [/* @__PURE__ */ h("div", {
51
53
  className: "flex items-center justify-between gap-2",
52
- children: [/* @__PURE__ */ d("span", {
53
- className: "text-sm font-medium text-text-primary",
54
- children: t.label
55
- }), /* @__PURE__ */ f("span", {
54
+ children: [/* @__PURE__ */ h("span", {
55
+ className: "flex min-w-0 items-center gap-1.5",
56
+ children: [
57
+ i && /* @__PURE__ */ m(d, {
58
+ className: "h-4 w-4 shrink-0 text-accent",
59
+ "aria-hidden": "true"
60
+ }),
61
+ /* @__PURE__ */ m("span", {
62
+ className: e("truncate text-sm", r ? "font-semibold text-text-primary" : "font-medium text-text-secondary"),
63
+ children: t.label
64
+ }),
65
+ r && w > 1 && /* @__PURE__ */ m(f, {
66
+ className: "h-3.5 w-3.5 shrink-0 text-status-warning-text",
67
+ "aria-label": "Leading"
68
+ })
69
+ ]
70
+ }), /* @__PURE__ */ h("span", {
56
71
  className: "shrink-0 text-xs tabular-nums text-text-secondary",
57
72
  children: [
58
73
  a(t.voteCount),
@@ -61,37 +76,43 @@ function h({ poll: h, canManage: g = !1 }) {
61
76
  "%"
62
77
  ]
63
78
  })]
64
- }), /* @__PURE__ */ d(c, {
79
+ }), /* @__PURE__ */ m(c, {
65
80
  value: n,
66
- className: "mt-2"
81
+ className: "mt-2",
82
+ barClassName: r ? "bg-gradient-to-r from-accent to-[var(--color-accent-soft)]" : "bg-accent/40"
67
83
  })]
68
84
  }, t.id);
69
85
  })
70
86
  }),
71
- /* @__PURE__ */ f("div", {
87
+ /* @__PURE__ */ h("div", {
72
88
  className: "mt-4 flex items-center justify-between gap-3 border-t border-border-seam pt-3 text-xs text-text-tertiary",
73
- children: [/* @__PURE__ */ f("span", {
74
- className: "inline-flex items-center gap-1.5",
89
+ children: [/* @__PURE__ */ h("span", {
90
+ className: "inline-flex flex-wrap items-center gap-1.5",
75
91
  children: [
76
- h.allowMultiple ? /* @__PURE__ */ d(l, { className: "h-3.5 w-3.5" }) : null,
77
- a(y),
92
+ v.allowMultiple ? /* @__PURE__ */ m(d, { className: "h-3.5 w-3.5" }) : null,
93
+ a(w),
78
94
  " vote",
79
- y === 1 ? "" : "s",
80
- h.isAnonymous ? " · anonymous" : "",
81
- h.closesAt ? ` · closes ${o(h.closesAt)}` : ""
95
+ w === 1 ? "" : "s",
96
+ v.allowMultiple ? " · multiple choice" : "",
97
+ v.isAnonymous ? " · anonymous" : "",
98
+ v.closesAt ? ` · closes ${o(v.closesAt)}` : "",
99
+ D && /* @__PURE__ */ m("span", {
100
+ className: "font-medium text-accent",
101
+ children: "· You voted"
102
+ })
82
103
  ]
83
- }), g && !b && /* @__PURE__ */ f(p, {
104
+ }), y && !T && /* @__PURE__ */ h(g, {
84
105
  size: "sm",
85
106
  variant: "outline",
86
- onClick: S,
87
- disabled: v.isPending,
88
- children: [/* @__PURE__ */ d(u, { className: "mr-1.5 h-3.5 w-3.5" }), " Close"]
107
+ onClick: k,
108
+ disabled: x.isPending,
109
+ children: [/* @__PURE__ */ m(p, { className: "mr-1.5 h-3.5 w-3.5" }), " Close"]
89
110
  })]
90
111
  })
91
112
  ]
92
113
  });
93
114
  }
94
115
  //#endregion
95
- export { h as PollCard };
116
+ export { v as PollCard };
96
117
 
97
118
  //# sourceMappingURL=PollCard.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"PollCard.js","names":[],"sources":["../../../src/comradecircle/components/PollCard.tsx"],"sourcesContent":["/**\n * PollCard — a circle poll with vote + live results.\n * @module comradecircle/components\n *\n * Writes `comVotePoll(pollId, optionId)` and `comClosePoll`. Options render as\n * clickable result bars; a closed poll (or one past its close time) is read-only.\n * The creator can close an open poll.\n */\n\nimport { CheckCircle2, Lock } from 'lucide-react';\nimport { GlassCard, Button } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { MiniBar } from './MiniBar';\nimport { StatusBadge } from './StatusBadge';\nimport { useComVotePoll, useComClosePoll } from '../hooks';\nimport { formatNumber, formatRelativeTime } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPoll } from '../types';\n\ninterface PollCardProps {\n poll: ComPoll;\n /** Whether the caller may close the poll (creator / circle admin). */\n canManage?: boolean;\n}\n\nexport function PollCard({ poll, canManage = false }: PollCardProps) {\n const vote = useComVotePoll();\n const close = useComClosePoll();\n\n const totalVotes = poll.options.reduce((sum, o) => sum + o.voteCount, 0);\n const isClosed = poll.status === 'CLOSED';\n\n const doVote = async (optionId: string) => {\n if (isClosed) return;\n try {\n await vote.mutateAsync({ pollId: poll.id, optionId, circleId: poll.circleId });\n notify.success('Vote recorded');\n } catch (err) {\n notify.error('Could not vote', describeError(err));\n }\n };\n\n const doClose = async () => {\n try {\n await close.mutateAsync(poll.id);\n notify.success('Poll closed');\n } catch (err) {\n notify.error('Could not close poll', describeError(err));\n }\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <h3 className=\"text-base font-semibold text-text-primary\">{poll.question}</h3>\n <StatusBadge status={poll.status} />\n </div>\n\n <div className=\"mt-4 flex flex-col gap-2.5\">\n {[...poll.options]\n .sort((a, b) => a.position - b.position)\n .map((opt) => {\n const pct = totalVotes > 0 ? (opt.voteCount / totalVotes) * 100 : 0;\n return (\n <button\n key={opt.id}\n type=\"button\"\n onClick={() => doVote(opt.id)}\n disabled={isClosed || vote.isPending}\n className={cn(\n 'group w-full rounded-xl border border-border-seam px-3 py-2.5 text-left transition-colors',\n !isClosed && 'hover:border-accent hover:bg-bg-sunken/60',\n isClosed && 'cursor-default'\n )}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"text-sm font-medium text-text-primary\">{opt.label}</span>\n <span className=\"shrink-0 text-xs tabular-nums text-text-secondary\">\n {formatNumber(opt.voteCount)} · {Math.round(pct)}%\n </span>\n </div>\n <MiniBar value={pct} className=\"mt-2\" />\n </button>\n );\n })}\n </div>\n\n <div className=\"mt-4 flex items-center justify-between gap-3 border-t border-border-seam pt-3 text-xs text-text-tertiary\">\n <span className=\"inline-flex items-center gap-1.5\">\n {poll.allowMultiple ? <CheckCircle2 className=\"h-3.5 w-3.5\" /> : null}\n {formatNumber(totalVotes)} vote{totalVotes === 1 ? '' : 's'}\n {poll.isAnonymous ? ' · anonymous' : ''}\n {poll.closesAt ? ` · closes ${formatRelativeTime(poll.closesAt)}` : ''}\n </span>\n {canManage && !isClosed && (\n <Button size=\"sm\" variant=\"outline\" onClick={doClose} disabled={close.isPending}>\n <Lock className=\"mr-1.5 h-3.5 w-3.5\" /> Close\n </Button>\n )}\n </div>\n </GlassCard>\n );\n}\n"],"mappings":";;;;;;;;;;;AAyBA,SAAgB,EAAS,EAAE,SAAM,eAAY,MAAwB;CACnE,IAAM,IAAO,GAAgB,EACvB,IAAQ,GAAiB,EAEzB,IAAa,EAAK,QAAQ,QAAQ,GAAK,MAAM,IAAM,EAAE,WAAW,EAAE,EAClE,IAAW,EAAK,WAAW,UAE3B,IAAS,OAAO,MAAqB;AACrC,SACJ,KAAI;AAEF,GADA,MAAM,EAAK,YAAY;IAAE,QAAQ,EAAK;IAAI;IAAU,UAAU,EAAK;IAAU,CAAC,EAC9E,EAAO,QAAQ,gBAAgB;WACxB,GAAK;AACZ,KAAO,MAAM,kBAAkB,EAAc,EAAI,CAAC;;IAIhD,IAAU,YAAY;AAC1B,MAAI;AAEF,GADA,MAAM,EAAM,YAAY,EAAK,GAAG,EAChC,EAAO,QAAQ,cAAc;WACtB,GAAK;AACZ,KAAO,MAAM,wBAAwB,EAAc,EAAI,CAAC;;;AAI5D,QACE,kBAAC,GAAD;EAAW,WAAU;EAAQ,WAAU;YAAvC;GACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAA6C,EAAK;KAAc,CAAA,EAC9E,kBAAC,GAAD,EAAa,QAAQ,EAAK,QAAU,CAAA,CAChC;;GAEN,kBAAC,OAAD;IAAK,WAAU;cACZ,CAAC,GAAG,EAAK,QAAQ,CACf,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,SAAS,CACvC,KAAK,MAAQ;KACZ,IAAM,IAAM,IAAa,IAAK,EAAI,YAAY,IAAc,MAAM;AAClE,YACE,kBAAC,UAAD;MAEE,MAAK;MACL,eAAe,EAAO,EAAI,GAAG;MAC7B,UAAU,KAAY,EAAK;MAC3B,WAAW,EACT,6FACA,CAAC,KAAY,6CACb,KAAY,iBACb;gBATH,CAWE,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,QAAD;QAAM,WAAU;kBAAyC,EAAI;QAAa,CAAA,EAC1E,kBAAC,QAAD;QAAM,WAAU;kBAAhB;SACG,EAAa,EAAI,UAAU;SAAC;SAAI,KAAK,MAAM,EAAI;SAAC;SAC5C;UACH;UACN,kBAAC,GAAD;OAAS,OAAO;OAAK,WAAU;OAAS,CAAA,CACjC;QAjBF,EAAI,GAiBF;MAEX;IACA,CAAA;GAEN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,EAAK,gBAAgB,kBAAC,GAAD,EAAc,WAAU,eAAgB,CAAA,GAAG;MAChE,EAAa,EAAW;MAAC;MAAM,MAAe,IAAI,KAAK;MACvD,EAAK,cAAc,iBAAiB;MACpC,EAAK,WAAW,aAAa,EAAmB,EAAK,SAAS,KAAK;MAC/D;QACN,KAAa,CAAC,KACb,kBAAC,GAAD;KAAQ,MAAK;KAAK,SAAQ;KAAU,SAAS;KAAS,UAAU,EAAM;eAAtE,CACE,kBAAC,GAAD,EAAM,WAAU,sBAAuB,CAAA,EAAA,SAChC;OAEP;;GACI"}
1
+ {"version":3,"file":"PollCard.js","names":[],"sources":["../../../src/comradecircle/components/PollCard.tsx"],"sourcesContent":["/**\n * PollCard — a circle poll with vote + live results.\n * @module comradecircle/components\n *\n * Writes `comVotePoll(pollId, optionId)` and `comClosePoll`. Options render as\n * clickable result bars; the leading option is emphasised with an accent\n * gradient fill and the caller's own voted option(s) are checkmarked (tracked\n * client-side — the poll query returns aggregate counts, not per-member votes).\n * A closed poll (or one past its close time) is read-only.\n */\n\nimport { useMemo, useState } from 'react';\nimport { CheckCircle2, Lock, Crown } from 'lucide-react';\nimport { GlassCard, Button } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { MiniBar } from './MiniBar';\nimport { StatusBadge } from './StatusBadge';\nimport { useComVotePoll, useComClosePoll } from '../hooks';\nimport { formatNumber, formatRelativeTime } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPoll } from '../types';\n\ninterface PollCardProps {\n poll: ComPoll;\n /** Whether the caller may close the poll (creator / circle admin). */\n canManage?: boolean;\n}\n\nexport function PollCard({ poll, canManage = false }: PollCardProps) {\n const vote = useComVotePoll();\n const close = useComClosePoll();\n // The poll query returns aggregate voteCounts, not per-member votes, so track\n // the options this caller has voted for in this session for the \"You voted\" cue.\n const [votedIds, setVotedIds] = useState<Set<string>>(new Set());\n\n const totalVotes = poll.options.reduce((sum, o) => sum + o.voteCount, 0);\n const isClosed = poll.status === 'CLOSED';\n const leadingCount = useMemo(\n () => poll.options.reduce((max, o) => Math.max(max, o.voteCount), 0),\n [poll.options]\n );\n const hasVoted = votedIds.size > 0;\n\n const doVote = async (optionId: string) => {\n if (isClosed) return;\n try {\n await vote.mutateAsync({ pollId: poll.id, optionId, circleId: poll.circleId });\n setVotedIds((prev) => new Set(prev).add(optionId));\n notify.success('Vote recorded');\n } catch (err) {\n notify.error('Could not vote', describeError(err));\n }\n };\n\n const doClose = async () => {\n try {\n await close.mutateAsync(poll.id);\n notify.success('Poll closed');\n } catch (err) {\n notify.error('Could not close poll', describeError(err));\n }\n };\n\n return (\n <GlassCard treatment=\"plain\" className=\"p-5\">\n <div className=\"flex items-start justify-between gap-3\">\n <h3 className=\"text-base font-semibold text-text-primary\">{poll.question}</h3>\n <StatusBadge status={poll.status} />\n </div>\n\n <div className=\"mt-4 flex flex-col gap-2.5\">\n {[...poll.options]\n .sort((a, b) => a.position - b.position)\n .map((opt) => {\n const pct = totalVotes > 0 ? (opt.voteCount / totalVotes) * 100 : 0;\n const isLeading = totalVotes > 0 && opt.voteCount === leadingCount;\n const isMine = votedIds.has(opt.id);\n return (\n <button\n key={opt.id}\n type=\"button\"\n onClick={() => doVote(opt.id)}\n disabled={isClosed || vote.isPending}\n aria-pressed={isMine}\n className={cn(\n 'group w-full rounded-xl border px-3 py-2.5 text-left transition-all duration-200',\n isMine ? 'border-accent bg-[var(--color-accent-soft)]/40' : 'border-border-seam',\n !isClosed &&\n !isMine &&\n 'hover:-translate-y-px hover:border-accent hover:bg-bg-sunken/60',\n isClosed && 'cursor-default'\n )}\n >\n <div className=\"flex items-center justify-between gap-2\">\n <span className=\"flex min-w-0 items-center gap-1.5\">\n {isMine && (\n <CheckCircle2 className=\"h-4 w-4 shrink-0 text-accent\" aria-hidden=\"true\" />\n )}\n <span\n className={cn(\n 'truncate text-sm',\n isLeading\n ? 'font-semibold text-text-primary'\n : 'font-medium text-text-secondary'\n )}\n >\n {opt.label}\n </span>\n {isLeading && totalVotes > 1 && (\n <Crown\n className=\"h-3.5 w-3.5 shrink-0 text-status-warning-text\"\n aria-label=\"Leading\"\n />\n )}\n </span>\n <span className=\"shrink-0 text-xs tabular-nums text-text-secondary\">\n {formatNumber(opt.voteCount)} · {Math.round(pct)}%\n </span>\n </div>\n <MiniBar\n value={pct}\n className=\"mt-2\"\n barClassName={\n isLeading\n ? 'bg-gradient-to-r from-accent to-[var(--color-accent-soft)]'\n : 'bg-accent/40'\n }\n />\n </button>\n );\n })}\n </div>\n\n <div className=\"mt-4 flex items-center justify-between gap-3 border-t border-border-seam pt-3 text-xs text-text-tertiary\">\n <span className=\"inline-flex flex-wrap items-center gap-1.5\">\n {poll.allowMultiple ? <CheckCircle2 className=\"h-3.5 w-3.5\" /> : null}\n {formatNumber(totalVotes)} vote{totalVotes === 1 ? '' : 's'}\n {poll.allowMultiple ? ' · multiple choice' : ''}\n {poll.isAnonymous ? ' · anonymous' : ''}\n {poll.closesAt ? ` · closes ${formatRelativeTime(poll.closesAt)}` : ''}\n {hasVoted && <span className=\"font-medium text-accent\">· You voted</span>}\n </span>\n {canManage && !isClosed && (\n <Button size=\"sm\" variant=\"outline\" onClick={doClose} disabled={close.isPending}>\n <Lock className=\"mr-1.5 h-3.5 w-3.5\" /> Close\n </Button>\n )}\n </div>\n </GlassCard>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import { ComPost } from '../types';
2
+
3
+ interface PostCardProps {
4
+ post: ComPost;
5
+ /** Start with the comment thread open. */
6
+ defaultExpanded?: boolean;
7
+ }
8
+ export declare function PostCard({ post, defaultExpanded }: PostCardProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
10
+ //# sourceMappingURL=PostCard.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostCard.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/PostCard.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAuBH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAExC,UAAU,aAAa;IACrB,IAAI,EAAE,OAAO,CAAC;IACd,0CAA0C;IAC1C,eAAe,CAAC,EAAE,OAAO,CAAC;CAC3B;AAED,wBAAgB,QAAQ,CAAC,EAAE,IAAI,EAAE,eAAuB,EAAE,EAAE,aAAa,2CAqKxE"}
@@ -18,7 +18,7 @@ function D({ post: D, defaultExpanded: O = !1 }) {
18
18
  let { currentUser: k } = e(), A = r(), j = n(), [M, N] = d(O), [P, F] = d(!1), [I, L] = d(D.body), R = !!(k?.id && k.id === D.authorId);
19
19
  return /* @__PURE__ */ v(T, {
20
20
  treatment: "plain",
21
- className: t("p-4", D.isPinned && "ring-1 ring-accent/40"),
21
+ className: t("p-4 transition-all duration-200 motion-safe:hover:-translate-y-0.5 hover:border-[var(--color-accent-soft)]", D.isPinned && "ring-1 ring-accent/40"),
22
22
  children: [
23
23
  /* @__PURE__ */ v("div", {
24
24
  className: "flex items-start justify-between gap-3",
@@ -1 +1 @@
1
- {"version":3,"file":"PostCard.js","names":[],"sources":["../../../src/comradecircle/components/PostCard.tsx"],"sourcesContent":["/**\n * PostCard — a single post with reactions and a collapsible comment thread.\n * @module comradecircle/components\n *\n * Reads its data from a `ComPost`; writes via `comUpdatePost` (edit / pin) and\n * `comRemovePost` (delete). Reactions + comments are delegated to\n * {@link ReactionBar} and {@link CommentThread}. Author-only actions (edit, pin,\n * delete) are gated on `authorId === currentUser.id`.\n */\n\nimport { useState } from 'react';\nimport { MessageSquare, MoreHorizontal, Pin, Pencil, Trash2 } from 'lucide-react';\nimport {\n GlassCard,\n Button,\n Textarea,\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n} from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { MemberIdentity } from './MemberIdentity';\nimport { ReactionBar } from './ReactionBar';\nimport { CommentThread } from './CommentThread';\nimport { AttachmentGrid } from './ImageAttachments';\nimport { useComUpdatePost, useComRemovePost } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPost } from '../types';\n\ninterface PostCardProps {\n post: ComPost;\n /** Start with the comment thread open. */\n defaultExpanded?: boolean;\n}\n\nexport function PostCard({ post, defaultExpanded = false }: PostCardProps) {\n const { currentUser } = useComradeCircle();\n const updatePost = useComUpdatePost();\n const removePost = useComRemovePost();\n const [expanded, setExpanded] = useState(defaultExpanded);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(post.body);\n\n const isAuthor = Boolean(currentUser?.id && currentUser.id === post.authorId);\n\n const saveEdit = async () => {\n const text = draft.trim();\n if (!text || text === post.body) {\n setEditing(false);\n return;\n }\n try {\n await updatePost.mutateAsync({ id: post.id, input: { body: text } });\n setEditing(false);\n notify.success('Post updated');\n } catch (err) {\n notify.error('Could not update post', describeError(err));\n }\n };\n\n const togglePin = async () => {\n try {\n await updatePost.mutateAsync({ id: post.id, input: { isPinned: !post.isPinned } });\n notify.success(post.isPinned ? 'Post unpinned' : 'Post pinned');\n } catch (err) {\n notify.error('Could not update post', describeError(err));\n }\n };\n\n const remove = async () => {\n try {\n await removePost.mutateAsync({ id: post.id, channelId: post.channelId });\n notify.success('Post removed');\n } catch (err) {\n notify.error('Could not remove post', describeError(err));\n }\n };\n\n return (\n <GlassCard treatment=\"plain\" className={cn('p-4', post.isPinned && 'ring-1 ring-accent/40')}>\n <div className=\"flex items-start justify-between gap-3\">\n <MemberIdentity\n memberId={post.authorId}\n variant=\"row\"\n size=\"md\"\n suffix={formatRelativeTime(post.createdAt)}\n showPresence\n />\n <div className=\"flex items-center gap-1\">\n {post.isPinned && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-primary\">\n <Pin className=\"h-3 w-3\" /> Pinned\n </span>\n )}\n {isAuthor && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Post actions\"\n className=\"rounded-lg p-1.5 text-text-tertiary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => setEditing(true)}>\n <Pencil className=\"mr-2 h-4 w-4\" /> Edit\n </DropdownMenuItem>\n <DropdownMenuItem onClick={togglePin}>\n <Pin className=\"mr-2 h-4 w-4\" /> {post.isPinned ? 'Unpin' : 'Pin'}\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={remove}\n className=\"text-status-error-text focus:text-status-error-text\"\n >\n <Trash2 className=\"mr-2 h-4 w-4\" /> Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n </div>\n\n {editing ? (\n <div className=\"mt-3\">\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n rows={3}\n className=\"resize-none\"\n />\n <div className=\"mt-2 flex justify-end gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n setDraft(post.body);\n setEditing(false);\n }}\n >\n Cancel\n </Button>\n <Button size=\"sm\" onClick={saveEdit} disabled={updatePost.isPending}>\n Save\n </Button>\n </div>\n </div>\n ) : (\n <p className=\"mt-3 whitespace-pre-wrap break-words text-sm text-text-primary\">\n {post.body}\n </p>\n )}\n\n {post.tags.length > 0 && (\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {post.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n {post.attachmentFileIds.length > 0 && <AttachmentGrid fileIds={post.attachmentFileIds} />}\n\n <div className=\"mt-3 flex items-center justify-between gap-3 border-t border-border-seam pt-3\">\n <ReactionBar post={post} />\n <button\n type=\"button\"\n onClick={() => setExpanded((s) => !s)}\n aria-expanded={expanded}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium transition-colors',\n expanded\n ? 'bg-[var(--color-accent-soft)] text-text-primary'\n : 'text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n <MessageSquare className=\"h-3.5 w-3.5\" />\n <span className=\"tabular-nums\">{post.commentCount}</span>\n <span className=\"hidden sm:inline\">\n {post.commentCount === 1 ? 'comment' : 'comments'}\n </span>\n </button>\n </div>\n\n {expanded && <CommentThread postId={post.id} />}\n </GlassCard>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"PostCard.js","names":[],"sources":["../../../src/comradecircle/components/PostCard.tsx"],"sourcesContent":["/**\n * PostCard — a single post with reactions and a collapsible comment thread.\n * @module comradecircle/components\n *\n * Reads its data from a `ComPost`; writes via `comUpdatePost` (edit / pin) and\n * `comRemovePost` (delete). Reactions + comments are delegated to\n * {@link ReactionBar} and {@link CommentThread}. Author-only actions (edit, pin,\n * delete) are gated on `authorId === currentUser.id`.\n */\n\nimport { useState } from 'react';\nimport { MessageSquare, MoreHorizontal, Pin, Pencil, Trash2 } from 'lucide-react';\nimport {\n GlassCard,\n Button,\n Textarea,\n DropdownMenu,\n DropdownMenuTrigger,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n} from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { MemberIdentity } from './MemberIdentity';\nimport { ReactionBar } from './ReactionBar';\nimport { CommentThread } from './CommentThread';\nimport { AttachmentGrid } from './ImageAttachments';\nimport { useComUpdatePost, useComRemovePost } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { formatRelativeTime } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPost } from '../types';\n\ninterface PostCardProps {\n post: ComPost;\n /** Start with the comment thread open. */\n defaultExpanded?: boolean;\n}\n\nexport function PostCard({ post, defaultExpanded = false }: PostCardProps) {\n const { currentUser } = useComradeCircle();\n const updatePost = useComUpdatePost();\n const removePost = useComRemovePost();\n const [expanded, setExpanded] = useState(defaultExpanded);\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState(post.body);\n\n const isAuthor = Boolean(currentUser?.id && currentUser.id === post.authorId);\n\n const saveEdit = async () => {\n const text = draft.trim();\n if (!text || text === post.body) {\n setEditing(false);\n return;\n }\n try {\n await updatePost.mutateAsync({ id: post.id, input: { body: text } });\n setEditing(false);\n notify.success('Post updated');\n } catch (err) {\n notify.error('Could not update post', describeError(err));\n }\n };\n\n const togglePin = async () => {\n try {\n await updatePost.mutateAsync({ id: post.id, input: { isPinned: !post.isPinned } });\n notify.success(post.isPinned ? 'Post unpinned' : 'Post pinned');\n } catch (err) {\n notify.error('Could not update post', describeError(err));\n }\n };\n\n const remove = async () => {\n try {\n await removePost.mutateAsync({ id: post.id, channelId: post.channelId });\n notify.success('Post removed');\n } catch (err) {\n notify.error('Could not remove post', describeError(err));\n }\n };\n\n return (\n <GlassCard\n treatment=\"plain\"\n className={cn(\n 'p-4 transition-all duration-200 motion-safe:hover:-translate-y-0.5 hover:border-[var(--color-accent-soft)]',\n post.isPinned && 'ring-1 ring-accent/40'\n )}\n >\n <div className=\"flex items-start justify-between gap-3\">\n <MemberIdentity\n memberId={post.authorId}\n variant=\"row\"\n size=\"md\"\n suffix={formatRelativeTime(post.createdAt)}\n showPresence\n />\n <div className=\"flex items-center gap-1\">\n {post.isPinned && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-[var(--color-accent-soft)] px-2 py-0.5 text-xs font-medium text-text-primary\">\n <Pin className=\"h-3 w-3\" /> Pinned\n </span>\n )}\n {isAuthor && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Post actions\"\n className=\"rounded-lg p-1.5 text-text-tertiary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <MoreHorizontal className=\"h-4 w-4\" />\n </button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem onClick={() => setEditing(true)}>\n <Pencil className=\"mr-2 h-4 w-4\" /> Edit\n </DropdownMenuItem>\n <DropdownMenuItem onClick={togglePin}>\n <Pin className=\"mr-2 h-4 w-4\" /> {post.isPinned ? 'Unpin' : 'Pin'}\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={remove}\n className=\"text-status-error-text focus:text-status-error-text\"\n >\n <Trash2 className=\"mr-2 h-4 w-4\" /> Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n </div>\n </div>\n\n {editing ? (\n <div className=\"mt-3\">\n <Textarea\n value={draft}\n onChange={(e) => setDraft(e.target.value)}\n rows={3}\n className=\"resize-none\"\n />\n <div className=\"mt-2 flex justify-end gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => {\n setDraft(post.body);\n setEditing(false);\n }}\n >\n Cancel\n </Button>\n <Button size=\"sm\" onClick={saveEdit} disabled={updatePost.isPending}>\n Save\n </Button>\n </div>\n </div>\n ) : (\n <p className=\"mt-3 whitespace-pre-wrap break-words text-sm text-text-primary\">\n {post.body}\n </p>\n )}\n\n {post.tags.length > 0 && (\n <div className=\"mt-2 flex flex-wrap gap-1.5\">\n {post.tags.map((tag) => (\n <span\n key={tag}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs font-medium text-text-secondary\"\n >\n #{tag}\n </span>\n ))}\n </div>\n )}\n\n {post.attachmentFileIds.length > 0 && <AttachmentGrid fileIds={post.attachmentFileIds} />}\n\n <div className=\"mt-3 flex items-center justify-between gap-3 border-t border-border-seam pt-3\">\n <ReactionBar post={post} />\n <button\n type=\"button\"\n onClick={() => setExpanded((s) => !s)}\n aria-expanded={expanded}\n className={cn(\n 'inline-flex items-center gap-1.5 rounded-full px-2 py-0.5 text-xs font-medium transition-colors',\n expanded\n ? 'bg-[var(--color-accent-soft)] text-text-primary'\n : 'text-text-secondary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n <MessageSquare className=\"h-3.5 w-3.5\" />\n <span className=\"tabular-nums\">{post.commentCount}</span>\n <span className=\"hidden sm:inline\">\n {post.commentCount === 1 ? 'comment' : 'comments'}\n </span>\n </button>\n </div>\n\n {expanded && <CommentThread postId={post.id} />}\n </GlassCard>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * PostComposer — compose and publish a post to a channel.
3
+ * @module comradecircle/components
4
+ *
5
+ * Writes `comCreatePost({ channelId, body, tags })`. Text + tags only (file
6
+ * attachments are a future enhancement — the svc accepts attachmentFileIds but
7
+ * file upload/preview is out of scope for v1).
8
+ */
9
+ interface PostComposerProps {
10
+ channelId: string;
11
+ channelName?: string;
12
+ onPosted?: () => void;
13
+ }
14
+ export declare function PostComposer({ channelId, channelName, onPosted }: PostComposerProps): import("react/jsx-runtime").JSX.Element;
15
+ export {};
16
+ //# sourceMappingURL=PostComposer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PostComposer.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/PostComposer.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAYH,UAAU,iBAAiB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,wBAAgB,YAAY,CAAC,EAAE,SAAS,EAAE,WAAW,EAAE,QAAQ,EAAE,EAAE,iBAAiB,2CA+EnF"}
@@ -0,0 +1,8 @@
1
+ import { ComPost } from '../types';
2
+
3
+ interface ReactionBarProps {
4
+ post: ComPost;
5
+ }
6
+ export declare function ReactionBar({ post }: ReactionBarProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
8
+ //# sourceMappingURL=ReactionBar.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ReactionBar.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/ReactionBar.tsx"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AASH,OAAO,KAAK,EAAE,OAAO,EAAmB,MAAM,UAAU,CAAC;AAWzD,UAAU,gBAAgB;IACxB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,wBAAgB,WAAW,CAAC,EAAE,IAAI,EAAE,EAAE,gBAAgB,2CAsFrD"}
@@ -71,7 +71,7 @@ function p({ post: p }) {
71
71
  disabled: h.isPending,
72
72
  "aria-pressed": n.mine,
73
73
  "aria-label": `${e.label} (${n.count})`,
74
- className: t("inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-colors", n.mine ? "border-accent bg-[var(--color-accent-soft)] text-text-primary" : "border-border-seam bg-bg-surface text-text-secondary hover:bg-bg-sunken"),
74
+ className: t("inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-transform duration-150 active:scale-90", n.mine ? "border-accent bg-[var(--color-accent-soft)] text-text-primary shadow-[0_0_0_2px_var(--color-accent-soft)]" : "border-border-seam bg-bg-surface text-text-secondary hover:-translate-y-px hover:bg-bg-sunken"),
75
75
  children: [/* @__PURE__ */ s("span", {
76
76
  "aria-hidden": "true",
77
77
  children: e.emoji
@@ -1 +1 @@
1
- {"version":3,"file":"ReactionBar.js","names":[],"sources":["../../../src/comradecircle/components/ReactionBar.tsx"],"sourcesContent":["/**\n * ReactionBar — Slack-style multi-reaction control for a post.\n * @module comradecircle/components\n *\n * `comReactToPost(postId, type)` is an idempotent toggle for one typed reaction,\n * so a member may hold several distinct reactions on a post. We render a chip\n * per reaction type that has a count (highlighted when the caller reacted) plus\n * a popover to add any of the six types.\n */\n\nimport { useState } from 'react';\nimport { SmilePlus } from 'lucide-react';\nimport { Popover, PopoverContent, PopoverTrigger } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { useComReactToPost } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPost, ComReactionType } from '../types';\n\nconst REACTIONS: { type: ComReactionType; emoji: string; label: string }[] = [\n { type: 'LIKE', emoji: '👍', label: 'Like' },\n { type: 'LOVE', emoji: '❤️', label: 'Love' },\n { type: 'CELEBRATE', emoji: '🎉', label: 'Celebrate' },\n { type: 'INSIGHTFUL', emoji: '💡', label: 'Insightful' },\n { type: 'LAUGH', emoji: '😄', label: 'Funny' },\n { type: 'SUPPORT', emoji: '🤝', label: 'Support' },\n];\n\ninterface ReactionBarProps {\n post: ComPost;\n}\n\nexport function ReactionBar({ post }: ReactionBarProps) {\n const { currentUser } = useComradeCircle();\n const react = useComReactToPost();\n const [open, setOpen] = useState(false);\n\n const meId = currentUser?.id;\n const counts = new Map<ComReactionType, { count: number; mine: boolean }>();\n for (const r of post.reactions ?? []) {\n const cur = counts.get(r.type) ?? { count: 0, mine: false };\n cur.count += 1;\n if (meId && r.memberId === meId) cur.mine = true;\n counts.set(r.type, cur);\n }\n\n const toggle = async (type: ComReactionType) => {\n setOpen(false);\n try {\n await react.mutateAsync({ postId: post.id, type });\n } catch (err) {\n notify.error('Could not react', describeError(err));\n }\n };\n\n const active = REACTIONS.filter((r) => (counts.get(r.type)?.count ?? 0) > 0);\n\n return (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {active.map((r) => {\n const info = counts.get(r.type)!;\n return (\n <button\n key={r.type}\n type=\"button\"\n onClick={() => toggle(r.type)}\n disabled={react.isPending}\n aria-pressed={info.mine}\n aria-label={`${r.label} (${info.count})`}\n className={cn(\n 'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-colors',\n info.mine\n ? 'border-accent bg-[var(--color-accent-soft)] text-text-primary'\n : 'border-border-seam bg-bg-surface text-text-secondary hover:bg-bg-sunken'\n )}\n >\n <span aria-hidden=\"true\">{r.emoji}</span>\n <span className=\"tabular-nums\">{info.count}</span>\n </button>\n );\n })}\n\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Add reaction\"\n className=\"inline-flex items-center gap-1 rounded-full border border-border-seam bg-bg-surface px-2 py-0.5 text-xs text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <SmilePlus className=\"h-3.5 w-3.5\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto p-1.5\" align=\"start\">\n <div className=\"flex items-center gap-0.5\">\n {REACTIONS.map((r) => {\n const mine = counts.get(r.type)?.mine ?? false;\n return (\n <button\n key={r.type}\n type=\"button\"\n onClick={() => toggle(r.type)}\n disabled={react.isPending}\n title={r.label}\n aria-label={r.label}\n className={cn(\n 'flex h-9 w-9 items-center justify-center rounded-lg text-lg transition-transform hover:scale-110 hover:bg-bg-sunken',\n mine && 'bg-[var(--color-accent-soft)]'\n )}\n >\n <span aria-hidden=\"true\">{r.emoji}</span>\n </button>\n );\n })}\n </div>\n </PopoverContent>\n </Popover>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAmBA,IAAM,IAAuE;CAC3E;EAAE,MAAM;EAAQ,OAAO;EAAM,OAAO;EAAQ;CAC5C;EAAE,MAAM;EAAQ,OAAO;EAAM,OAAO;EAAQ;CAC5C;EAAE,MAAM;EAAa,OAAO;EAAM,OAAO;EAAa;CACtD;EAAE,MAAM;EAAc,OAAO;EAAM,OAAO;EAAc;CACxD;EAAE,MAAM;EAAS,OAAO;EAAM,OAAO;EAAS;CAC9C;EAAE,MAAM;EAAW,OAAO;EAAM,OAAO;EAAW;CACnD;AAMD,SAAgB,EAAY,EAAE,WAA0B;CACtD,IAAM,EAAE,mBAAgB,GAAkB,EACpC,IAAQ,GAAmB,EAC3B,CAAC,GAAM,KAAW,EAAS,GAAM,EAEjC,IAAO,GAAa,IACpB,oBAAS,IAAI,KAAwD;AAC3E,MAAK,IAAM,KAAK,EAAK,aAAa,EAAE,EAAE;EACpC,IAAM,IAAM,EAAO,IAAI,EAAE,KAAK,IAAI;GAAE,OAAO;GAAG,MAAM;GAAO;AAG3D,EAFA,EAAI,SAAS,GACT,KAAQ,EAAE,aAAa,MAAM,EAAI,OAAO,KAC5C,EAAO,IAAI,EAAE,MAAM,EAAI;;CAGzB,IAAM,IAAS,OAAO,MAA0B;AAC9C,IAAQ,GAAM;AACd,MAAI;AACF,SAAM,EAAM,YAAY;IAAE,QAAQ,EAAK;IAAI;IAAM,CAAC;WAC3C,GAAK;AACZ,KAAO,MAAM,mBAAmB,EAAc,EAAI,CAAC;;;AAMvD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CAHa,EAAU,QAAQ,OAAO,EAAO,IAAI,EAAE,KAAK,EAAE,SAAS,KAAK,EAAE,CAIhE,KAAK,MAAM;GACjB,IAAM,IAAO,EAAO,IAAI,EAAE,KAAK;AAC/B,UACE,kBAAC,UAAD;IAEE,MAAK;IACL,eAAe,EAAO,EAAE,KAAK;IAC7B,UAAU,EAAM;IAChB,gBAAc,EAAK;IACnB,cAAY,GAAG,EAAE,MAAM,IAAI,EAAK,MAAM;IACtC,WAAW,EACT,wGACA,EAAK,OACD,kEACA,0EACL;cAZH,CAcE,kBAAC,QAAD;KAAM,eAAY;eAAQ,EAAE;KAAa,CAAA,EACzC,kBAAC,QAAD;KAAM,WAAU;eAAgB,EAAK;KAAa,CAAA,CAC3C;MAfF,EAAE,KAeA;IAEX,EAEF,kBAAC,GAAD;GAAe;GAAM,cAAc;aAAnC,CACE,kBAAC,GAAD;IAAgB,SAAA;cACd,kBAAC,UAAD;KACE,MAAK;KACL,cAAW;KACX,WAAU;eAEV,kBAAC,GAAD,EAAW,WAAU,eAAgB,CAAA;KAC9B,CAAA;IACM,CAAA,EACjB,kBAAC,GAAD;IAAgB,WAAU;IAAe,OAAM;cAC7C,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAU,KAAK,MAAM;MACpB,IAAM,IAAO,EAAO,IAAI,EAAE,KAAK,EAAE,QAAQ;AACzC,aACE,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAO,EAAE,KAAK;OAC7B,UAAU,EAAM;OAChB,OAAO,EAAE;OACT,cAAY,EAAE;OACd,WAAW,EACT,uHACA,KAAQ,gCACT;iBAED,kBAAC,QAAD;QAAM,eAAY;kBAAQ,EAAE;QAAa,CAAA;OAClC,EAZF,EAAE,KAYA;OAEX;KACE,CAAA;IACS,CAAA,CACT;KACN"}
1
+ {"version":3,"file":"ReactionBar.js","names":[],"sources":["../../../src/comradecircle/components/ReactionBar.tsx"],"sourcesContent":["/**\n * ReactionBar — Slack-style multi-reaction control for a post.\n * @module comradecircle/components\n *\n * `comReactToPost(postId, type)` is an idempotent toggle for one typed reaction,\n * so a member may hold several distinct reactions on a post. We render a chip\n * per reaction type that has a count (highlighted when the caller reacted) plus\n * a popover to add any of the six types.\n */\n\nimport { useState } from 'react';\nimport { SmilePlus } from 'lucide-react';\nimport { Popover, PopoverContent, PopoverTrigger } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { useComReactToPost } from '../hooks';\nimport { useComradeCircle } from '../context';\nimport { notify, describeError } from '../lib/toast';\nimport type { ComPost, ComReactionType } from '../types';\n\nconst REACTIONS: { type: ComReactionType; emoji: string; label: string }[] = [\n { type: 'LIKE', emoji: '👍', label: 'Like' },\n { type: 'LOVE', emoji: '❤️', label: 'Love' },\n { type: 'CELEBRATE', emoji: '🎉', label: 'Celebrate' },\n { type: 'INSIGHTFUL', emoji: '💡', label: 'Insightful' },\n { type: 'LAUGH', emoji: '😄', label: 'Funny' },\n { type: 'SUPPORT', emoji: '🤝', label: 'Support' },\n];\n\ninterface ReactionBarProps {\n post: ComPost;\n}\n\nexport function ReactionBar({ post }: ReactionBarProps) {\n const { currentUser } = useComradeCircle();\n const react = useComReactToPost();\n const [open, setOpen] = useState(false);\n\n const meId = currentUser?.id;\n const counts = new Map<ComReactionType, { count: number; mine: boolean }>();\n for (const r of post.reactions ?? []) {\n const cur = counts.get(r.type) ?? { count: 0, mine: false };\n cur.count += 1;\n if (meId && r.memberId === meId) cur.mine = true;\n counts.set(r.type, cur);\n }\n\n const toggle = async (type: ComReactionType) => {\n setOpen(false);\n try {\n await react.mutateAsync({ postId: post.id, type });\n } catch (err) {\n notify.error('Could not react', describeError(err));\n }\n };\n\n const active = REACTIONS.filter((r) => (counts.get(r.type)?.count ?? 0) > 0);\n\n return (\n <div className=\"flex flex-wrap items-center gap-1.5\">\n {active.map((r) => {\n const info = counts.get(r.type)!;\n return (\n <button\n key={r.type}\n type=\"button\"\n onClick={() => toggle(r.type)}\n disabled={react.isPending}\n aria-pressed={info.mine}\n aria-label={`${r.label} (${info.count})`}\n className={cn(\n 'inline-flex items-center gap-1 rounded-full border px-2 py-0.5 text-xs font-medium transition-transform duration-150 active:scale-90',\n info.mine\n ? 'border-accent bg-[var(--color-accent-soft)] text-text-primary shadow-[0_0_0_2px_var(--color-accent-soft)]'\n : 'border-border-seam bg-bg-surface text-text-secondary hover:-translate-y-px hover:bg-bg-sunken'\n )}\n >\n <span aria-hidden=\"true\">{r.emoji}</span>\n <span className=\"tabular-nums\">{info.count}</span>\n </button>\n );\n })}\n\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <button\n type=\"button\"\n aria-label=\"Add reaction\"\n className=\"inline-flex items-center gap-1 rounded-full border border-border-seam bg-bg-surface px-2 py-0.5 text-xs text-text-secondary transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <SmilePlus className=\"h-3.5 w-3.5\" />\n </button>\n </PopoverTrigger>\n <PopoverContent className=\"w-auto p-1.5\" align=\"start\">\n <div className=\"flex items-center gap-0.5\">\n {REACTIONS.map((r) => {\n const mine = counts.get(r.type)?.mine ?? false;\n return (\n <button\n key={r.type}\n type=\"button\"\n onClick={() => toggle(r.type)}\n disabled={react.isPending}\n title={r.label}\n aria-label={r.label}\n className={cn(\n 'flex h-9 w-9 items-center justify-center rounded-lg text-lg transition-transform hover:scale-110 hover:bg-bg-sunken',\n mine && 'bg-[var(--color-accent-soft)]'\n )}\n >\n <span aria-hidden=\"true\">{r.emoji}</span>\n </button>\n );\n })}\n </div>\n </PopoverContent>\n </Popover>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;AAmBA,IAAM,IAAuE;CAC3E;EAAE,MAAM;EAAQ,OAAO;EAAM,OAAO;EAAQ;CAC5C;EAAE,MAAM;EAAQ,OAAO;EAAM,OAAO;EAAQ;CAC5C;EAAE,MAAM;EAAa,OAAO;EAAM,OAAO;EAAa;CACtD;EAAE,MAAM;EAAc,OAAO;EAAM,OAAO;EAAc;CACxD;EAAE,MAAM;EAAS,OAAO;EAAM,OAAO;EAAS;CAC9C;EAAE,MAAM;EAAW,OAAO;EAAM,OAAO;EAAW;CACnD;AAMD,SAAgB,EAAY,EAAE,WAA0B;CACtD,IAAM,EAAE,mBAAgB,GAAkB,EACpC,IAAQ,GAAmB,EAC3B,CAAC,GAAM,KAAW,EAAS,GAAM,EAEjC,IAAO,GAAa,IACpB,oBAAS,IAAI,KAAwD;AAC3E,MAAK,IAAM,KAAK,EAAK,aAAa,EAAE,EAAE;EACpC,IAAM,IAAM,EAAO,IAAI,EAAE,KAAK,IAAI;GAAE,OAAO;GAAG,MAAM;GAAO;AAG3D,EAFA,EAAI,SAAS,GACT,KAAQ,EAAE,aAAa,MAAM,EAAI,OAAO,KAC5C,EAAO,IAAI,EAAE,MAAM,EAAI;;CAGzB,IAAM,IAAS,OAAO,MAA0B;AAC9C,IAAQ,GAAM;AACd,MAAI;AACF,SAAM,EAAM,YAAY;IAAE,QAAQ,EAAK;IAAI;IAAM,CAAC;WAC3C,GAAK;AACZ,KAAO,MAAM,mBAAmB,EAAc,EAAI,CAAC;;;AAMvD,QACE,kBAAC,OAAD;EAAK,WAAU;YAAf,CAHa,EAAU,QAAQ,OAAO,EAAO,IAAI,EAAE,KAAK,EAAE,SAAS,KAAK,EAAE,CAIhE,KAAK,MAAM;GACjB,IAAM,IAAO,EAAO,IAAI,EAAE,KAAK;AAC/B,UACE,kBAAC,UAAD;IAEE,MAAK;IACL,eAAe,EAAO,EAAE,KAAK;IAC7B,UAAU,EAAM;IAChB,gBAAc,EAAK;IACnB,cAAY,GAAG,EAAE,MAAM,IAAI,EAAK,MAAM;IACtC,WAAW,EACT,wIACA,EAAK,OACD,8GACA,gGACL;cAZH,CAcE,kBAAC,QAAD;KAAM,eAAY;eAAQ,EAAE;KAAa,CAAA,EACzC,kBAAC,QAAD;KAAM,WAAU;eAAgB,EAAK;KAAa,CAAA,CAC3C;MAfF,EAAE,KAeA;IAEX,EAEF,kBAAC,GAAD;GAAe;GAAM,cAAc;aAAnC,CACE,kBAAC,GAAD;IAAgB,SAAA;cACd,kBAAC,UAAD;KACE,MAAK;KACL,cAAW;KACX,WAAU;eAEV,kBAAC,GAAD,EAAW,WAAU,eAAgB,CAAA;KAC9B,CAAA;IACM,CAAA,EACjB,kBAAC,GAAD;IAAgB,WAAU;IAAe,OAAM;cAC7C,kBAAC,OAAD;KAAK,WAAU;eACZ,EAAU,KAAK,MAAM;MACpB,IAAM,IAAO,EAAO,IAAI,EAAE,KAAK,EAAE,QAAQ;AACzC,aACE,kBAAC,UAAD;OAEE,MAAK;OACL,eAAe,EAAO,EAAE,KAAK;OAC7B,UAAU,EAAM;OAChB,OAAO,EAAE;OACT,cAAY,EAAE;OACd,WAAW,EACT,uHACA,KAAQ,gCACT;iBAED,kBAAC,QAAD;QAAM,eAAY;kBAAQ,EAAE;QAAa,CAAA;OAClC,EAZF,EAAE,KAYA;OAEX;KACE,CAAA;IACS,CAAA,CACT;KACN"}
@@ -0,0 +1,24 @@
1
+ import { FC, ReactNode } from 'react';
2
+
3
+ interface LoadingViewProps {
4
+ label?: string;
5
+ className?: string;
6
+ }
7
+ export declare const LoadingView: FC<LoadingViewProps>;
8
+ interface ErrorViewProps {
9
+ title?: string;
10
+ message?: string;
11
+ onRetry?: () => void;
12
+ className?: string;
13
+ }
14
+ export declare const ErrorView: FC<ErrorViewProps>;
15
+ interface SectionProps {
16
+ title: string;
17
+ description?: string;
18
+ action?: ReactNode;
19
+ children: ReactNode;
20
+ className?: string;
21
+ }
22
+ export declare const Section: FC<SectionProps>;
23
+ export {};
24
+ //# sourceMappingURL=StateViews.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StateViews.d.ts","sourceRoot":"","sources":["../../../src/comradecircle/components/StateViews.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAK3C,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CAK5C,CAAC;AAEF,UAAU,cAAc;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,SAAS,EAAE,EAAE,CAAC,cAAc,CA2BxC,CAAC;AAEF,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,SAAS,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,eAAO,MAAM,OAAO,EAAE,EAAE,CAAC,YAAY,CAWpC,CAAC"}