@burdenoff/microfe-comradecircle 2026.1025.1 → 2026.1025.2

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.
@@ -319,7 +319,7 @@ function q({ circleId: e, canEdit: n }) {
319
319
  }),
320
320
  /* @__PURE__ */ N("p", {
321
321
  className: "mb-4 text-sm text-text-tertiary",
322
- children: n ? r("comradecircle.moderation.rule.explainer", "Content matching a flagged term or spam pattern is queued for review. Auto-hide applies to spam only and can always be overturned.") : r("comradecircle.moderation.rule.readOnly", "Only circle owners and admins can change these rules.")
322
+ children: n ? r("comradecircle.moderation.rule.explainer", "Posts and comments that match a flagged term or spam pattern are queued for review. With auto-hide on, matching posts are hidden as spam right away and can always be restored.") : r("comradecircle.moderation.rule.readOnly", "Only circle owners and admins can change these rules.")
323
323
  }),
324
324
  o ? /* @__PURE__ */ N(m, { label: r("comradecircle.moderation.rule.loading", "Loading rules…") }) : s || !a ? /* @__PURE__ */ N(p, {
325
325
  title: r("comradecircle.moderation.rule.errorTitle", "Could not load the rules"),
@@ -400,7 +400,7 @@ function J({ rule: e, circleId: n, canEdit: r }) {
400
400
  className: "flex items-center justify-between gap-4",
401
401
  children: [/* @__PURE__ */ P("div", { children: [/* @__PURE__ */ N(I, {
402
402
  htmlFor: "rule-autohide",
403
- children: i("comradecircle.moderation.rule.autoHide", "Auto-hide matching spam posts")
403
+ children: i("comradecircle.moderation.rule.autoHide", "Auto-hide posts that match these rules")
404
404
  }), /* @__PURE__ */ N("p", {
405
405
  className: "text-xs text-text-tertiary",
406
406
  children: i("comradecircle.moderation.rule.autoHideHint", "Hidden posts stay in the review queue and can be restored. Harassment and toxicity always go to a human.")
@@ -1 +1 @@
1
- {"version":3,"file":"ModerationTab.js","names":[],"sources":["../../../../src/comradecircle/pages/circles/ModerationTab.tsx"],"sourcesContent":["/**\n * ModerationTab — the moderator console for one circle (BOFF-4451).\n * @module comradecircle/pages/circles\n *\n * Three panels, each backed by a real `com*` field:\n * - Activity summary → `comCircleActivitySummary(circleId, days)`\n * - Review queue → `comListVerdicts(circleId, reviewed)` + `comReviewVerdict`\n * - Auto-moderation → `comModerationRule(circleId)` + `comUpdateModerationRule`\n *\n * The triage agent only ever suggests, except for one reversible action: it\n * may auto-hide high-confidence spam when a circle admin opts in. Every other\n * outcome waits here for a human decision. MODERATOR+ can review; only\n * ADMIN+ can change the rule (the svc enforces both).\n */\n\nimport { useState } from 'react';\nimport {\n Activity,\n AlertTriangle,\n CheckCircle2,\n EyeOff,\n FileText,\n MessageCircle,\n RotateCcw,\n ShieldAlert,\n ShieldCheck,\n Sparkles,\n UserPlus,\n Users,\n} from 'lucide-react';\nimport { Button, Label, Switch, Textarea } from '@burdenoff/fe-libs/ui';\nimport { EmptyState } from '../../components/EmptyState';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { TagInput } from '../../components/TagInput';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport {\n useComCircleActivitySummary,\n useComCircleVerdicts,\n useComModerationRule,\n useComPost,\n useComReviewVerdict,\n useComUpdateModerationRule,\n useTr,\n} from '../../hooks';\nimport { formatDateTime, formatEnumLabel, formatNumber } from '../../utils/formatters';\nimport { notify, describeError } from '../../lib/toast';\nimport type {\n ComCircle,\n ComModerationReviewDecision,\n ComModerationRule,\n ComModerationVerdict,\n} from '../../types';\n\n/** Mirrors the svc caps in moderation-rule-validation.ts. */\nconst MAX_FLAGGED_TERMS = 100;\nconst MAX_SPAM_PATTERNS = 25;\n\ntype QueueFilter = 'pending' | 'reviewed' | 'all';\ntype SummaryWindow = '7' | '30';\n\nexport function ModerationTab({\n circle,\n canEditRules,\n}: {\n circle: ComCircle;\n canEditRules: boolean;\n}) {\n return (\n <div className=\"flex flex-col gap-6\" data-testid=\"moderation-tab\">\n <ActivitySummaryPanel circleId={circle.id} />\n <ReviewQueuePanel circleId={circle.id} />\n <RulePanel circleId={circle.id} canEdit={canEditRules} />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Activity summary\n// ---------------------------------------------------------------------------\n\nfunction ActivitySummaryPanel({ circleId }: { circleId: string }) {\n const tr = useTr();\n const [window, setWindow] = useState<SummaryWindow>('7');\n const { data, isLoading, isError, refetch } = useComCircleActivitySummary(\n circleId,\n Number(window)\n );\n\n const windowOptions: FilterOption<SummaryWindow>[] = [\n { value: '7', label: tr('comradecircle.moderation.summary.last7', 'Last 7 days') },\n { value: '30', label: tr('comradecircle.moderation.summary.last30', 'Last 30 days') },\n ];\n\n const countOf = (verdict: string) =>\n data?.verdictCounts.find((v) => v.verdict === verdict)?.count ?? 0;\n\n return (\n <section\n className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\"\n aria-labelledby=\"moderation-summary-heading\"\n >\n <div className=\"mb-4 flex flex-wrap items-center justify-between gap-3\">\n <h2\n id=\"moderation-summary-heading\"\n className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\"\n >\n <Activity className=\"h-5 w-5\" />\n {tr('comradecircle.moderation.summary.title', 'Circle activity')}\n </h2>\n <FilterTabs options={windowOptions} value={window} onChange={setWindow} />\n </div>\n\n {isLoading ? (\n <LoadingView label={tr('comradecircle.moderation.summary.loading', 'Loading activity…')} />\n ) : isError || !data ? (\n <ErrorView\n title={tr('comradecircle.moderation.summary.errorTitle', 'Could not load activity')}\n onRetry={() => refetch()}\n />\n ) : (\n <>\n <p className=\"mb-4 text-sm text-text-secondary\" data-testid=\"moderation-summary-text\">\n {data.summaryText}\n </p>\n <div className=\"grid grid-cols-2 gap-3 md:grid-cols-4\">\n <MetricsCard\n icon={<FileText className=\"h-5 w-5\" />}\n value={formatNumber(data.totalPosts)}\n label={tr('comradecircle.moderation.summary.posts', 'Posts')}\n />\n <MetricsCard\n icon={<MessageCircle className=\"h-5 w-5\" />}\n value={formatNumber(data.totalComments)}\n label={tr('comradecircle.moderation.summary.comments', 'Comments')}\n />\n <MetricsCard\n icon={<UserPlus className=\"h-5 w-5\" />}\n value={formatNumber(data.newMembers)}\n label={tr('comradecircle.moderation.summary.newMembers', 'New members')}\n />\n <MetricsCard\n icon={<Users className=\"h-5 w-5\" />}\n value={formatNumber(data.activeMembers)}\n label={tr('comradecircle.moderation.summary.activeMembers', 'Active members')}\n />\n </div>\n <div className=\"mt-3 flex flex-wrap gap-2 text-xs text-text-secondary\">\n <span className=\"rounded-full bg-bg-sunken px-2.5 py-1\">\n {tr('comradecircle.moderation.summary.flagged', '{{count}} flagged', {\n count: countOf('FLAG'),\n })}\n </span>\n <span className=\"rounded-full bg-bg-sunken px-2.5 py-1\">\n {tr('comradecircle.moderation.summary.hidden', '{{count}} auto-hidden', {\n count: countOf('HIDE'),\n })}\n </span>\n <span className=\"rounded-full bg-bg-sunken px-2.5 py-1\">\n {tr('comradecircle.moderation.summary.escalated', '{{count}} escalated', {\n count: countOf('ESCALATE'),\n })}\n </span>\n </div>\n </>\n )}\n </section>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Review queue\n// ---------------------------------------------------------------------------\n\nfunction ReviewQueuePanel({ circleId }: { circleId: string }) {\n const tr = useTr();\n const [filter, setFilter] = useState<QueueFilter>('pending');\n const reviewed = filter === 'pending' ? false : filter === 'reviewed' ? true : undefined;\n const { data, isLoading, isError, refetch } = useComCircleVerdicts(circleId, { reviewed });\n\n // ALLOW verdicts need nothing from a human; keep them out of the pending queue.\n const items = (data ?? []).filter((v) => filter !== 'pending' || v.verdict !== 'ALLOW');\n\n const filterOptions: FilterOption<QueueFilter>[] = [\n { value: 'pending', label: tr('comradecircle.moderation.queue.pending', 'Needs review') },\n { value: 'reviewed', label: tr('comradecircle.moderation.queue.reviewed', 'Reviewed') },\n { value: 'all', label: tr('comradecircle.moderation.queue.all', 'All decisions') },\n ];\n\n return (\n <section\n className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\"\n aria-labelledby=\"moderation-queue-heading\"\n >\n <div className=\"mb-2 flex flex-wrap items-center justify-between gap-3\">\n <h2\n id=\"moderation-queue-heading\"\n className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\"\n >\n <ShieldAlert className=\"h-5 w-5\" />\n {tr('comradecircle.moderation.queue.title', 'Review queue')}\n </h2>\n <FilterTabs options={filterOptions} value={filter} onChange={setFilter} />\n </div>\n <p className=\"mb-4 text-sm text-text-tertiary\">\n {tr(\n 'comradecircle.moderation.queue.explainer',\n 'The moderation agent triages every new post and comment and suggests an action. It never removes content or bans members: anything beyond hiding obvious spam waits for you.'\n )}\n </p>\n\n {isLoading ? (\n <LoadingView label={tr('comradecircle.moderation.queue.loading', 'Loading queue…')} />\n ) : isError ? (\n <ErrorView\n title={tr('comradecircle.moderation.queue.errorTitle', 'Could not load the queue')}\n onRetry={() => refetch()}\n />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<ShieldCheck className=\"h-6 w-6\" />}\n title={\n filter === 'pending'\n ? tr('comradecircle.moderation.queue.emptyPending', 'Nothing needs review')\n : tr('comradecircle.moderation.queue.empty', 'No moderation decisions yet')\n }\n description={tr(\n 'comradecircle.moderation.queue.emptyDescription',\n 'Flagged, hidden and escalated content will appear here.'\n )}\n />\n ) : (\n <ul className=\"flex flex-col gap-3\" data-testid=\"moderation-queue\">\n {items.map((verdict) => (\n <VerdictCard key={verdict.id} verdict={verdict} circleId={circleId} />\n ))}\n </ul>\n )}\n </section>\n );\n}\n\nfunction suggestedAction(\n verdict: ComModerationVerdict,\n tr: ReturnType<typeof useTr>\n): { text: string; tone: 'warning' | 'error' | 'neutral' } {\n switch (verdict.verdict) {\n case 'HIDE':\n return verdict.actedAutomatically\n ? {\n text: tr(\n 'comradecircle.moderation.suggest.hideActed',\n 'Hidden automatically as high-confidence spam. Uphold to keep it hidden, or overturn to restore it.'\n ),\n tone: 'warning',\n }\n : {\n text: tr(\n 'comradecircle.moderation.suggest.hide',\n 'Suggests hiding this content. No action was taken.'\n ),\n tone: 'warning',\n };\n case 'ESCALATE':\n return {\n text: tr(\n 'comradecircle.moderation.suggest.escalate',\n 'Needs a human decision. Possible harassment is never acted on automatically.'\n ),\n tone: 'error',\n };\n case 'FLAG':\n return {\n text: tr('comradecircle.moderation.suggest.flag', 'Suggests a review. Nothing was hidden.'),\n tone: 'warning',\n };\n default:\n return {\n text: tr('comradecircle.moderation.suggest.allow', 'No action suggested.'),\n tone: 'neutral',\n };\n }\n}\n\nfunction VerdictCard({ verdict, circleId }: { verdict: ComModerationVerdict; circleId: string }) {\n const tr = useTr();\n const review = useComReviewVerdict(circleId);\n const [note, setNote] = useState('');\n const suggestion = suggestedAction(verdict, tr);\n const isReviewed = Boolean(verdict.reviewedAt);\n\n const submit = (decision: ComModerationReviewDecision) =>\n review\n .mutateAsync({ verdictId: verdict.id, decision, note: note.trim() })\n .then(() => {\n setNote('');\n notify.success(\n decision === 'OVERTURN'\n ? tr('comradecircle.moderation.review.overturned', 'Decision overturned')\n : tr('comradecircle.moderation.review.upheld', 'Decision upheld')\n );\n })\n .catch((e) =>\n notify.error(\n tr('comradecircle.moderation.review.error', 'Could not record the review'),\n describeError(e)\n )\n );\n\n return (\n <li\n className=\"rounded-xl border border-border-seam bg-bg-base p-4\"\n data-testid=\"moderation-verdict\"\n data-verdict-id={verdict.id}\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={verdict.verdict} />\n <span className=\"text-xs font-medium text-text-secondary\">\n {formatEnumLabel(verdict.targetType)}\n </span>\n {verdict.categories.map((category) => (\n <span\n key={category}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {formatEnumLabel(category)}\n </span>\n ))}\n <span className=\"text-xs text-text-tertiary\">\n {tr('comradecircle.moderation.verdict.confidence', '{{value}}% confidence', {\n value: Math.round(verdict.confidence * 100),\n })}\n </span>\n {verdict.actedAutomatically && (\n <span className=\"inline-flex items-center gap-1 text-xs text-status-warning-text\">\n <EyeOff className=\"h-3.5 w-3.5\" />\n {tr('comradecircle.moderation.verdict.autoHidden', 'Auto-hidden')}\n </span>\n )}\n {verdict.injectionDetected && (\n <span className=\"inline-flex items-center gap-1 text-xs text-status-error-text\">\n <AlertTriangle className=\"h-3.5 w-3.5\" />\n {tr('comradecircle.moderation.verdict.injection', 'Prompt-injection attempt')}\n </span>\n )}\n <span className=\"ml-auto text-xs text-text-tertiary\">\n {formatDateTime(verdict.createdAt)}\n </span>\n </div>\n\n {verdict.targetType === 'POST' ? (\n <PostPreview postId={verdict.targetId} />\n ) : (\n <p className=\"mt-3 text-sm text-text-tertiary\">\n {tr('comradecircle.moderation.verdict.targetRef', '{{type}} {{id}}', {\n type: formatEnumLabel(verdict.targetType),\n id: verdict.targetId,\n })}\n </p>\n )}\n\n <div className=\"mt-3 flex items-start gap-2 rounded-lg bg-bg-sunken p-3 text-sm\">\n <Sparkles className=\"mt-0.5 h-4 w-4 shrink-0 text-text-secondary\" />\n <div>\n <p\n className={\n suggestion.tone === 'error'\n ? 'font-medium text-status-error-text'\n : suggestion.tone === 'warning'\n ? 'font-medium text-status-warning-text'\n : 'font-medium text-text-primary'\n }\n >\n {suggestion.text}\n </p>\n <p className=\"mt-1 text-text-secondary\">{verdict.rationale}</p>\n </div>\n </div>\n\n {isReviewed ? (\n <p className=\"mt-3 flex items-center gap-1.5 text-sm text-text-secondary\">\n {verdict.overturned ? (\n <RotateCcw className=\"h-4 w-4\" />\n ) : (\n <CheckCircle2 className=\"h-4 w-4\" />\n )}\n {verdict.overturned\n ? tr('comradecircle.moderation.verdict.wasOverturned', 'Overturned {{when}}', {\n when: formatDateTime(verdict.reviewedAt),\n })\n : tr('comradecircle.moderation.verdict.wasUpheld', 'Upheld {{when}}', {\n when: formatDateTime(verdict.reviewedAt),\n })}\n {verdict.reviewNote ? ` · ${verdict.reviewNote}` : ''}\n </p>\n ) : (\n <div className=\"mt-3 flex flex-col gap-2\">\n <Label htmlFor={`review-note-${verdict.id}`} className=\"sr-only\">\n {tr('comradecircle.moderation.review.noteLabel', 'Review note')}\n </Label>\n <Textarea\n id={`review-note-${verdict.id}`}\n value={note}\n onChange={(e) => setNote(e.target.value)}\n placeholder={tr(\n 'comradecircle.moderation.review.notePlaceholder',\n 'Optional note for the audit trail'\n )}\n rows={2}\n maxLength={500}\n />\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => submit('OVERTURN')}\n disabled={review.isPending}\n data-testid=\"moderation-overturn\"\n >\n <RotateCcw className=\"mr-1.5 h-4 w-4\" />\n {verdict.actedAutomatically\n ? tr('comradecircle.moderation.review.overturnRestore', 'Overturn and restore')\n : tr('comradecircle.moderation.review.overturn', 'Overturn')}\n </Button>\n <Button\n size=\"sm\"\n onClick={() => submit('UPHOLD')}\n disabled={review.isPending}\n data-testid=\"moderation-uphold\"\n >\n <CheckCircle2 className=\"mr-1.5 h-4 w-4\" />\n {tr('comradecircle.moderation.review.uphold', 'Uphold')}\n </Button>\n </div>\n </div>\n )}\n </li>\n );\n}\n\nfunction PostPreview({ postId }: { postId: string }) {\n const tr = useTr();\n const { data: post, isLoading } = useComPost(postId);\n if (isLoading) {\n return (\n <p className=\"mt-3 text-sm text-text-tertiary\">\n {tr('comradecircle.moderation.verdict.loadingPost', 'Loading post…')}\n </p>\n );\n }\n if (!post) {\n return (\n <p className=\"mt-3 text-sm text-text-tertiary\">\n {tr('comradecircle.moderation.verdict.postGone', 'This post is no longer available.')}\n </p>\n );\n }\n return (\n <div className=\"mt-3\">\n <div className=\"mb-1 flex items-center gap-2\">\n <StatusBadge status={post.status} />\n </div>\n <p className=\"line-clamp-4 whitespace-pre-wrap break-words text-sm text-text-primary\">\n {post.body}\n </p>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Auto-moderation rule\n// ---------------------------------------------------------------------------\n\nfunction RulePanel({ circleId, canEdit }: { circleId: string; canEdit: boolean }) {\n const tr = useTr();\n const { data: rule, isLoading, isError, refetch } = useComModerationRule(circleId);\n\n return (\n <section\n className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\"\n aria-labelledby=\"moderation-rule-heading\"\n >\n <h2\n id=\"moderation-rule-heading\"\n className=\"mb-1 flex items-center gap-2 text-lg font-semibold text-text-primary\"\n >\n <ShieldCheck className=\"h-5 w-5\" />\n {tr('comradecircle.moderation.rule.title', 'Auto-moderation rules')}\n </h2>\n <p className=\"mb-4 text-sm text-text-tertiary\">\n {canEdit\n ? tr(\n 'comradecircle.moderation.rule.explainer',\n 'Content matching a flagged term or spam pattern is queued for review. Auto-hide applies to spam only and can always be overturned.'\n )\n : tr(\n 'comradecircle.moderation.rule.readOnly',\n 'Only circle owners and admins can change these rules.'\n )}\n </p>\n\n {isLoading ? (\n <LoadingView label={tr('comradecircle.moderation.rule.loading', 'Loading rules…')} />\n ) : isError || !rule ? (\n <ErrorView\n title={tr('comradecircle.moderation.rule.errorTitle', 'Could not load the rules')}\n onRetry={() => refetch()}\n />\n ) : (\n // Re-mount on save so the form always starts from the stored rule.\n <RuleForm key={rule.updatedAt} rule={rule} circleId={circleId} canEdit={canEdit} />\n )}\n </section>\n );\n}\n\nfunction RuleForm({\n rule,\n circleId,\n canEdit,\n}: {\n rule: ComModerationRule;\n circleId: string;\n canEdit: boolean;\n}) {\n const tr = useTr();\n const update = useComUpdateModerationRule(circleId);\n const [enabled, setEnabled] = useState(rule.enabled);\n const [autoHideSpam, setAutoHideSpam] = useState(rule.autoHideSpam);\n const [flaggedTerms, setFlaggedTerms] = useState<string[]>(rule.flaggedTerms);\n const [patternsText, setPatternsText] = useState(rule.spamPatterns.join('\\n'));\n\n const spamPatterns = patternsText\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean);\n const tooManyPatterns = spamPatterns.length > MAX_SPAM_PATTERNS;\n\n const save = () =>\n update\n .mutateAsync({ enabled, autoHideSpam, flaggedTerms, spamPatterns })\n .then(() =>\n notify.success(tr('comradecircle.moderation.rule.saved', 'Moderation rules saved'))\n )\n .catch((e) =>\n notify.error(\n tr('comradecircle.moderation.rule.saveError', 'Could not save the rules'),\n describeError(e)\n )\n );\n\n return (\n <form\n className=\"flex flex-col gap-5\"\n data-testid=\"moderation-rule-form\"\n onSubmit={(e) => {\n e.preventDefault();\n void save();\n }}\n >\n <div className=\"flex items-center justify-between gap-4\">\n <div>\n <Label htmlFor=\"rule-enabled\">\n {tr('comradecircle.moderation.rule.enabled', 'Apply circle rules')}\n </Label>\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'comradecircle.moderation.rule.enabledHint',\n 'Built-in spam, harassment and prompt-injection checks always run.'\n )}\n </p>\n </div>\n <Switch\n id=\"rule-enabled\"\n checked={enabled}\n onCheckedChange={setEnabled}\n disabled={!canEdit}\n />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"rule-terms\">\n {tr('comradecircle.moderation.rule.terms', 'Flagged terms')}\n </Label>\n <TagInput\n id=\"rule-terms\"\n value={flaggedTerms}\n onChange={canEdit ? setFlaggedTerms : () => undefined}\n max={MAX_FLAGGED_TERMS}\n placeholder={tr(\n 'comradecircle.moderation.rule.termsPlaceholder',\n 'Type a word or phrase and press Enter'\n )}\n />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"rule-patterns\">\n {tr('comradecircle.moderation.rule.patterns', 'Spam patterns (one per line)')}\n </Label>\n <Textarea\n id=\"rule-patterns\"\n value={patternsText}\n onChange={(e) => setPatternsText(e.target.value)}\n rows={4}\n disabled={!canEdit}\n className=\"font-mono text-sm\"\n placeholder={'promo-code-[0-9]{4}\\nhttps?://bit\\\\.ly/\\\\w+'}\n />\n <p\n className={\n tooManyPatterns ? 'text-xs text-status-error-text' : 'text-xs text-text-tertiary'\n }\n >\n {tr(\n 'comradecircle.moderation.rule.patternsHint',\n 'Case-insensitive regular expressions, up to {{max}}. Nested quantifiers, backreferences and lookarounds are rejected to keep triage fast.',\n { max: MAX_SPAM_PATTERNS }\n )}\n </p>\n </div>\n\n <div className=\"flex items-center justify-between gap-4\">\n <div>\n <Label htmlFor=\"rule-autohide\">\n {tr('comradecircle.moderation.rule.autoHide', 'Auto-hide matching spam posts')}\n </Label>\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'comradecircle.moderation.rule.autoHideHint',\n 'Hidden posts stay in the review queue and can be restored. Harassment and toxicity always go to a human.'\n )}\n </p>\n </div>\n <Switch\n id=\"rule-autohide\"\n checked={autoHideSpam}\n onCheckedChange={setAutoHideSpam}\n disabled={!canEdit}\n />\n </div>\n\n {canEdit && (\n <div className=\"flex justify-end\">\n <Button\n type=\"submit\"\n disabled={update.isPending || tooManyPatterns}\n data-testid=\"moderation-rule-save\"\n >\n {update.isPending\n ? tr('comradecircle.moderation.rule.saving', 'Saving…')\n : tr('comradecircle.moderation.rule.save', 'Save rules')}\n </Button>\n </div>\n )}\n </form>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ModerationTab.js","names":[],"sources":["../../../../src/comradecircle/pages/circles/ModerationTab.tsx"],"sourcesContent":["/**\n * ModerationTab — the moderator console for one circle (BOFF-4451).\n * @module comradecircle/pages/circles\n *\n * Three panels, each backed by a real `com*` field:\n * - Activity summary → `comCircleActivitySummary(circleId, days)`\n * - Review queue → `comListVerdicts(circleId, reviewed)` + `comReviewVerdict`\n * - Auto-moderation → `comModerationRule(circleId)` + `comUpdateModerationRule`\n *\n * The triage agent only ever suggests, except for one reversible action: it\n * may auto-hide high-confidence spam when a circle admin opts in. Every other\n * outcome waits here for a human decision. MODERATOR+ can review; only\n * ADMIN+ can change the rule (the svc enforces both).\n */\n\nimport { useState } from 'react';\nimport {\n Activity,\n AlertTriangle,\n CheckCircle2,\n EyeOff,\n FileText,\n MessageCircle,\n RotateCcw,\n ShieldAlert,\n ShieldCheck,\n Sparkles,\n UserPlus,\n Users,\n} from 'lucide-react';\nimport { Button, Label, Switch, Textarea } from '@burdenoff/fe-libs/ui';\nimport { EmptyState } from '../../components/EmptyState';\nimport { FilterTabs, type FilterOption } from '../../components/FilterTabs';\nimport { MetricsCard } from '../../components/MetricsCard';\nimport { StatusBadge } from '../../components/StatusBadge';\nimport { TagInput } from '../../components/TagInput';\nimport { LoadingView, ErrorView } from '../../components/StateViews';\nimport {\n useComCircleActivitySummary,\n useComCircleVerdicts,\n useComModerationRule,\n useComPost,\n useComReviewVerdict,\n useComUpdateModerationRule,\n useTr,\n} from '../../hooks';\nimport { formatDateTime, formatEnumLabel, formatNumber } from '../../utils/formatters';\nimport { notify, describeError } from '../../lib/toast';\nimport type {\n ComCircle,\n ComModerationReviewDecision,\n ComModerationRule,\n ComModerationVerdict,\n} from '../../types';\n\n/** Mirrors the svc caps in moderation-rule-validation.ts. */\nconst MAX_FLAGGED_TERMS = 100;\nconst MAX_SPAM_PATTERNS = 25;\n\ntype QueueFilter = 'pending' | 'reviewed' | 'all';\ntype SummaryWindow = '7' | '30';\n\nexport function ModerationTab({\n circle,\n canEditRules,\n}: {\n circle: ComCircle;\n canEditRules: boolean;\n}) {\n return (\n <div className=\"flex flex-col gap-6\" data-testid=\"moderation-tab\">\n <ActivitySummaryPanel circleId={circle.id} />\n <ReviewQueuePanel circleId={circle.id} />\n <RulePanel circleId={circle.id} canEdit={canEditRules} />\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Activity summary\n// ---------------------------------------------------------------------------\n\nfunction ActivitySummaryPanel({ circleId }: { circleId: string }) {\n const tr = useTr();\n const [window, setWindow] = useState<SummaryWindow>('7');\n const { data, isLoading, isError, refetch } = useComCircleActivitySummary(\n circleId,\n Number(window)\n );\n\n const windowOptions: FilterOption<SummaryWindow>[] = [\n { value: '7', label: tr('comradecircle.moderation.summary.last7', 'Last 7 days') },\n { value: '30', label: tr('comradecircle.moderation.summary.last30', 'Last 30 days') },\n ];\n\n const countOf = (verdict: string) =>\n data?.verdictCounts.find((v) => v.verdict === verdict)?.count ?? 0;\n\n return (\n <section\n className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\"\n aria-labelledby=\"moderation-summary-heading\"\n >\n <div className=\"mb-4 flex flex-wrap items-center justify-between gap-3\">\n <h2\n id=\"moderation-summary-heading\"\n className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\"\n >\n <Activity className=\"h-5 w-5\" />\n {tr('comradecircle.moderation.summary.title', 'Circle activity')}\n </h2>\n <FilterTabs options={windowOptions} value={window} onChange={setWindow} />\n </div>\n\n {isLoading ? (\n <LoadingView label={tr('comradecircle.moderation.summary.loading', 'Loading activity…')} />\n ) : isError || !data ? (\n <ErrorView\n title={tr('comradecircle.moderation.summary.errorTitle', 'Could not load activity')}\n onRetry={() => refetch()}\n />\n ) : (\n <>\n <p className=\"mb-4 text-sm text-text-secondary\" data-testid=\"moderation-summary-text\">\n {data.summaryText}\n </p>\n <div className=\"grid grid-cols-2 gap-3 md:grid-cols-4\">\n <MetricsCard\n icon={<FileText className=\"h-5 w-5\" />}\n value={formatNumber(data.totalPosts)}\n label={tr('comradecircle.moderation.summary.posts', 'Posts')}\n />\n <MetricsCard\n icon={<MessageCircle className=\"h-5 w-5\" />}\n value={formatNumber(data.totalComments)}\n label={tr('comradecircle.moderation.summary.comments', 'Comments')}\n />\n <MetricsCard\n icon={<UserPlus className=\"h-5 w-5\" />}\n value={formatNumber(data.newMembers)}\n label={tr('comradecircle.moderation.summary.newMembers', 'New members')}\n />\n <MetricsCard\n icon={<Users className=\"h-5 w-5\" />}\n value={formatNumber(data.activeMembers)}\n label={tr('comradecircle.moderation.summary.activeMembers', 'Active members')}\n />\n </div>\n <div className=\"mt-3 flex flex-wrap gap-2 text-xs text-text-secondary\">\n <span className=\"rounded-full bg-bg-sunken px-2.5 py-1\">\n {tr('comradecircle.moderation.summary.flagged', '{{count}} flagged', {\n count: countOf('FLAG'),\n })}\n </span>\n <span className=\"rounded-full bg-bg-sunken px-2.5 py-1\">\n {tr('comradecircle.moderation.summary.hidden', '{{count}} auto-hidden', {\n count: countOf('HIDE'),\n })}\n </span>\n <span className=\"rounded-full bg-bg-sunken px-2.5 py-1\">\n {tr('comradecircle.moderation.summary.escalated', '{{count}} escalated', {\n count: countOf('ESCALATE'),\n })}\n </span>\n </div>\n </>\n )}\n </section>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Review queue\n// ---------------------------------------------------------------------------\n\nfunction ReviewQueuePanel({ circleId }: { circleId: string }) {\n const tr = useTr();\n const [filter, setFilter] = useState<QueueFilter>('pending');\n const reviewed = filter === 'pending' ? false : filter === 'reviewed' ? true : undefined;\n const { data, isLoading, isError, refetch } = useComCircleVerdicts(circleId, { reviewed });\n\n // ALLOW verdicts need nothing from a human; keep them out of the pending queue.\n const items = (data ?? []).filter((v) => filter !== 'pending' || v.verdict !== 'ALLOW');\n\n const filterOptions: FilterOption<QueueFilter>[] = [\n { value: 'pending', label: tr('comradecircle.moderation.queue.pending', 'Needs review') },\n { value: 'reviewed', label: tr('comradecircle.moderation.queue.reviewed', 'Reviewed') },\n { value: 'all', label: tr('comradecircle.moderation.queue.all', 'All decisions') },\n ];\n\n return (\n <section\n className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\"\n aria-labelledby=\"moderation-queue-heading\"\n >\n <div className=\"mb-2 flex flex-wrap items-center justify-between gap-3\">\n <h2\n id=\"moderation-queue-heading\"\n className=\"flex items-center gap-2 text-lg font-semibold text-text-primary\"\n >\n <ShieldAlert className=\"h-5 w-5\" />\n {tr('comradecircle.moderation.queue.title', 'Review queue')}\n </h2>\n <FilterTabs options={filterOptions} value={filter} onChange={setFilter} />\n </div>\n <p className=\"mb-4 text-sm text-text-tertiary\">\n {tr(\n 'comradecircle.moderation.queue.explainer',\n 'The moderation agent triages every new post and comment and suggests an action. It never removes content or bans members: anything beyond hiding obvious spam waits for you.'\n )}\n </p>\n\n {isLoading ? (\n <LoadingView label={tr('comradecircle.moderation.queue.loading', 'Loading queue…')} />\n ) : isError ? (\n <ErrorView\n title={tr('comradecircle.moderation.queue.errorTitle', 'Could not load the queue')}\n onRetry={() => refetch()}\n />\n ) : items.length === 0 ? (\n <EmptyState\n icon={<ShieldCheck className=\"h-6 w-6\" />}\n title={\n filter === 'pending'\n ? tr('comradecircle.moderation.queue.emptyPending', 'Nothing needs review')\n : tr('comradecircle.moderation.queue.empty', 'No moderation decisions yet')\n }\n description={tr(\n 'comradecircle.moderation.queue.emptyDescription',\n 'Flagged, hidden and escalated content will appear here.'\n )}\n />\n ) : (\n <ul className=\"flex flex-col gap-3\" data-testid=\"moderation-queue\">\n {items.map((verdict) => (\n <VerdictCard key={verdict.id} verdict={verdict} circleId={circleId} />\n ))}\n </ul>\n )}\n </section>\n );\n}\n\nfunction suggestedAction(\n verdict: ComModerationVerdict,\n tr: ReturnType<typeof useTr>\n): { text: string; tone: 'warning' | 'error' | 'neutral' } {\n switch (verdict.verdict) {\n case 'HIDE':\n return verdict.actedAutomatically\n ? {\n text: tr(\n 'comradecircle.moderation.suggest.hideActed',\n 'Hidden automatically as high-confidence spam. Uphold to keep it hidden, or overturn to restore it.'\n ),\n tone: 'warning',\n }\n : {\n text: tr(\n 'comradecircle.moderation.suggest.hide',\n 'Suggests hiding this content. No action was taken.'\n ),\n tone: 'warning',\n };\n case 'ESCALATE':\n return {\n text: tr(\n 'comradecircle.moderation.suggest.escalate',\n 'Needs a human decision. Possible harassment is never acted on automatically.'\n ),\n tone: 'error',\n };\n case 'FLAG':\n return {\n text: tr('comradecircle.moderation.suggest.flag', 'Suggests a review. Nothing was hidden.'),\n tone: 'warning',\n };\n default:\n return {\n text: tr('comradecircle.moderation.suggest.allow', 'No action suggested.'),\n tone: 'neutral',\n };\n }\n}\n\nfunction VerdictCard({ verdict, circleId }: { verdict: ComModerationVerdict; circleId: string }) {\n const tr = useTr();\n const review = useComReviewVerdict(circleId);\n const [note, setNote] = useState('');\n const suggestion = suggestedAction(verdict, tr);\n const isReviewed = Boolean(verdict.reviewedAt);\n\n const submit = (decision: ComModerationReviewDecision) =>\n review\n .mutateAsync({ verdictId: verdict.id, decision, note: note.trim() })\n .then(() => {\n setNote('');\n notify.success(\n decision === 'OVERTURN'\n ? tr('comradecircle.moderation.review.overturned', 'Decision overturned')\n : tr('comradecircle.moderation.review.upheld', 'Decision upheld')\n );\n })\n .catch((e) =>\n notify.error(\n tr('comradecircle.moderation.review.error', 'Could not record the review'),\n describeError(e)\n )\n );\n\n return (\n <li\n className=\"rounded-xl border border-border-seam bg-bg-base p-4\"\n data-testid=\"moderation-verdict\"\n data-verdict-id={verdict.id}\n >\n <div className=\"flex flex-wrap items-center gap-2\">\n <StatusBadge status={verdict.verdict} />\n <span className=\"text-xs font-medium text-text-secondary\">\n {formatEnumLabel(verdict.targetType)}\n </span>\n {verdict.categories.map((category) => (\n <span\n key={category}\n className=\"rounded-full bg-bg-sunken px-2 py-0.5 text-xs text-text-secondary\"\n >\n {formatEnumLabel(category)}\n </span>\n ))}\n <span className=\"text-xs text-text-tertiary\">\n {tr('comradecircle.moderation.verdict.confidence', '{{value}}% confidence', {\n value: Math.round(verdict.confidence * 100),\n })}\n </span>\n {verdict.actedAutomatically && (\n <span className=\"inline-flex items-center gap-1 text-xs text-status-warning-text\">\n <EyeOff className=\"h-3.5 w-3.5\" />\n {tr('comradecircle.moderation.verdict.autoHidden', 'Auto-hidden')}\n </span>\n )}\n {verdict.injectionDetected && (\n <span className=\"inline-flex items-center gap-1 text-xs text-status-error-text\">\n <AlertTriangle className=\"h-3.5 w-3.5\" />\n {tr('comradecircle.moderation.verdict.injection', 'Prompt-injection attempt')}\n </span>\n )}\n <span className=\"ml-auto text-xs text-text-tertiary\">\n {formatDateTime(verdict.createdAt)}\n </span>\n </div>\n\n {verdict.targetType === 'POST' ? (\n <PostPreview postId={verdict.targetId} />\n ) : (\n <p className=\"mt-3 text-sm text-text-tertiary\">\n {tr('comradecircle.moderation.verdict.targetRef', '{{type}} {{id}}', {\n type: formatEnumLabel(verdict.targetType),\n id: verdict.targetId,\n })}\n </p>\n )}\n\n <div className=\"mt-3 flex items-start gap-2 rounded-lg bg-bg-sunken p-3 text-sm\">\n <Sparkles className=\"mt-0.5 h-4 w-4 shrink-0 text-text-secondary\" />\n <div>\n <p\n className={\n suggestion.tone === 'error'\n ? 'font-medium text-status-error-text'\n : suggestion.tone === 'warning'\n ? 'font-medium text-status-warning-text'\n : 'font-medium text-text-primary'\n }\n >\n {suggestion.text}\n </p>\n <p className=\"mt-1 text-text-secondary\">{verdict.rationale}</p>\n </div>\n </div>\n\n {isReviewed ? (\n <p className=\"mt-3 flex items-center gap-1.5 text-sm text-text-secondary\">\n {verdict.overturned ? (\n <RotateCcw className=\"h-4 w-4\" />\n ) : (\n <CheckCircle2 className=\"h-4 w-4\" />\n )}\n {verdict.overturned\n ? tr('comradecircle.moderation.verdict.wasOverturned', 'Overturned {{when}}', {\n when: formatDateTime(verdict.reviewedAt),\n })\n : tr('comradecircle.moderation.verdict.wasUpheld', 'Upheld {{when}}', {\n when: formatDateTime(verdict.reviewedAt),\n })}\n {verdict.reviewNote ? ` · ${verdict.reviewNote}` : ''}\n </p>\n ) : (\n <div className=\"mt-3 flex flex-col gap-2\">\n <Label htmlFor={`review-note-${verdict.id}`} className=\"sr-only\">\n {tr('comradecircle.moderation.review.noteLabel', 'Review note')}\n </Label>\n <Textarea\n id={`review-note-${verdict.id}`}\n value={note}\n onChange={(e) => setNote(e.target.value)}\n placeholder={tr(\n 'comradecircle.moderation.review.notePlaceholder',\n 'Optional note for the audit trail'\n )}\n rows={2}\n maxLength={500}\n />\n <div className=\"flex flex-wrap justify-end gap-2\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n onClick={() => submit('OVERTURN')}\n disabled={review.isPending}\n data-testid=\"moderation-overturn\"\n >\n <RotateCcw className=\"mr-1.5 h-4 w-4\" />\n {verdict.actedAutomatically\n ? tr('comradecircle.moderation.review.overturnRestore', 'Overturn and restore')\n : tr('comradecircle.moderation.review.overturn', 'Overturn')}\n </Button>\n <Button\n size=\"sm\"\n onClick={() => submit('UPHOLD')}\n disabled={review.isPending}\n data-testid=\"moderation-uphold\"\n >\n <CheckCircle2 className=\"mr-1.5 h-4 w-4\" />\n {tr('comradecircle.moderation.review.uphold', 'Uphold')}\n </Button>\n </div>\n </div>\n )}\n </li>\n );\n}\n\nfunction PostPreview({ postId }: { postId: string }) {\n const tr = useTr();\n const { data: post, isLoading } = useComPost(postId);\n if (isLoading) {\n return (\n <p className=\"mt-3 text-sm text-text-tertiary\">\n {tr('comradecircle.moderation.verdict.loadingPost', 'Loading post…')}\n </p>\n );\n }\n if (!post) {\n return (\n <p className=\"mt-3 text-sm text-text-tertiary\">\n {tr('comradecircle.moderation.verdict.postGone', 'This post is no longer available.')}\n </p>\n );\n }\n return (\n <div className=\"mt-3\">\n <div className=\"mb-1 flex items-center gap-2\">\n <StatusBadge status={post.status} />\n </div>\n <p className=\"line-clamp-4 whitespace-pre-wrap break-words text-sm text-text-primary\">\n {post.body}\n </p>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Auto-moderation rule\n// ---------------------------------------------------------------------------\n\nfunction RulePanel({ circleId, canEdit }: { circleId: string; canEdit: boolean }) {\n const tr = useTr();\n const { data: rule, isLoading, isError, refetch } = useComModerationRule(circleId);\n\n return (\n <section\n className=\"rounded-2xl border border-border-seam bg-bg-surface p-5\"\n aria-labelledby=\"moderation-rule-heading\"\n >\n <h2\n id=\"moderation-rule-heading\"\n className=\"mb-1 flex items-center gap-2 text-lg font-semibold text-text-primary\"\n >\n <ShieldCheck className=\"h-5 w-5\" />\n {tr('comradecircle.moderation.rule.title', 'Auto-moderation rules')}\n </h2>\n <p className=\"mb-4 text-sm text-text-tertiary\">\n {canEdit\n ? tr(\n 'comradecircle.moderation.rule.explainer',\n 'Posts and comments that match a flagged term or spam pattern are queued for review. With auto-hide on, matching posts are hidden as spam right away and can always be restored.'\n )\n : tr(\n 'comradecircle.moderation.rule.readOnly',\n 'Only circle owners and admins can change these rules.'\n )}\n </p>\n\n {isLoading ? (\n <LoadingView label={tr('comradecircle.moderation.rule.loading', 'Loading rules…')} />\n ) : isError || !rule ? (\n <ErrorView\n title={tr('comradecircle.moderation.rule.errorTitle', 'Could not load the rules')}\n onRetry={() => refetch()}\n />\n ) : (\n // Re-mount on save so the form always starts from the stored rule.\n <RuleForm key={rule.updatedAt} rule={rule} circleId={circleId} canEdit={canEdit} />\n )}\n </section>\n );\n}\n\nfunction RuleForm({\n rule,\n circleId,\n canEdit,\n}: {\n rule: ComModerationRule;\n circleId: string;\n canEdit: boolean;\n}) {\n const tr = useTr();\n const update = useComUpdateModerationRule(circleId);\n const [enabled, setEnabled] = useState(rule.enabled);\n const [autoHideSpam, setAutoHideSpam] = useState(rule.autoHideSpam);\n const [flaggedTerms, setFlaggedTerms] = useState<string[]>(rule.flaggedTerms);\n const [patternsText, setPatternsText] = useState(rule.spamPatterns.join('\\n'));\n\n const spamPatterns = patternsText\n .split('\\n')\n .map((line) => line.trim())\n .filter(Boolean);\n const tooManyPatterns = spamPatterns.length > MAX_SPAM_PATTERNS;\n\n const save = () =>\n update\n .mutateAsync({ enabled, autoHideSpam, flaggedTerms, spamPatterns })\n .then(() =>\n notify.success(tr('comradecircle.moderation.rule.saved', 'Moderation rules saved'))\n )\n .catch((e) =>\n notify.error(\n tr('comradecircle.moderation.rule.saveError', 'Could not save the rules'),\n describeError(e)\n )\n );\n\n return (\n <form\n className=\"flex flex-col gap-5\"\n data-testid=\"moderation-rule-form\"\n onSubmit={(e) => {\n e.preventDefault();\n void save();\n }}\n >\n <div className=\"flex items-center justify-between gap-4\">\n <div>\n <Label htmlFor=\"rule-enabled\">\n {tr('comradecircle.moderation.rule.enabled', 'Apply circle rules')}\n </Label>\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'comradecircle.moderation.rule.enabledHint',\n 'Built-in spam, harassment and prompt-injection checks always run.'\n )}\n </p>\n </div>\n <Switch\n id=\"rule-enabled\"\n checked={enabled}\n onCheckedChange={setEnabled}\n disabled={!canEdit}\n />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"rule-terms\">\n {tr('comradecircle.moderation.rule.terms', 'Flagged terms')}\n </Label>\n <TagInput\n id=\"rule-terms\"\n value={flaggedTerms}\n onChange={canEdit ? setFlaggedTerms : () => undefined}\n max={MAX_FLAGGED_TERMS}\n placeholder={tr(\n 'comradecircle.moderation.rule.termsPlaceholder',\n 'Type a word or phrase and press Enter'\n )}\n />\n </div>\n\n <div className=\"flex flex-col gap-1.5\">\n <Label htmlFor=\"rule-patterns\">\n {tr('comradecircle.moderation.rule.patterns', 'Spam patterns (one per line)')}\n </Label>\n <Textarea\n id=\"rule-patterns\"\n value={patternsText}\n onChange={(e) => setPatternsText(e.target.value)}\n rows={4}\n disabled={!canEdit}\n className=\"font-mono text-sm\"\n placeholder={'promo-code-[0-9]{4}\\nhttps?://bit\\\\.ly/\\\\w+'}\n />\n <p\n className={\n tooManyPatterns ? 'text-xs text-status-error-text' : 'text-xs text-text-tertiary'\n }\n >\n {tr(\n 'comradecircle.moderation.rule.patternsHint',\n 'Case-insensitive regular expressions, up to {{max}}. Nested quantifiers, backreferences and lookarounds are rejected to keep triage fast.',\n { max: MAX_SPAM_PATTERNS }\n )}\n </p>\n </div>\n\n <div className=\"flex items-center justify-between gap-4\">\n <div>\n <Label htmlFor=\"rule-autohide\">\n {tr('comradecircle.moderation.rule.autoHide', 'Auto-hide posts that match these rules')}\n </Label>\n <p className=\"text-xs text-text-tertiary\">\n {tr(\n 'comradecircle.moderation.rule.autoHideHint',\n 'Hidden posts stay in the review queue and can be restored. Harassment and toxicity always go to a human.'\n )}\n </p>\n </div>\n <Switch\n id=\"rule-autohide\"\n checked={autoHideSpam}\n onCheckedChange={setAutoHideSpam}\n disabled={!canEdit}\n />\n </div>\n\n {canEdit && (\n <div className=\"flex justify-end\">\n <Button\n type=\"submit\"\n disabled={update.isPending || tooManyPatterns}\n data-testid=\"moderation-rule-save\"\n >\n {update.isPending\n ? tr('comradecircle.moderation.rule.saving', 'Saving…')\n : tr('comradecircle.moderation.rule.save', 'Save rules')}\n </Button>\n </div>\n )}\n </form>\n );\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-comradecircle",
3
- "version": "2026.1025.1",
3
+ "version": "2026.1025.2",
4
4
  "description": "Burdenoff ComradeCircle Microfrontend",
5
5
  "type": "module",
6
6
  "files": [