@burdenoff/microfe-theculturepost 2026.802.1 → 2026.827.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.
@@ -21,7 +21,8 @@ interface CitationListProps {
21
21
  storyId: string;
22
22
  /** Narrow reading-rail rendering: smaller cards, icon-only segments, no legend. */
23
23
  compact?: boolean;
24
+ className?: string;
24
25
  }
25
- export declare function CitationList({ storyId, compact }: CitationListProps): import("react/jsx-runtime").JSX.Element;
26
+ export declare function CitationList({ storyId, compact, className }: CitationListProps): import("react/jsx-runtime").JSX.Element;
26
27
  export {};
27
28
  //# sourceMappingURL=CitationList.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"CitationList.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/components/CitationList.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAiBL,KAAK,UAAU,EAChB,MAAM,cAAc,CAAC;AAUtB,OAAO,KAAK,EAAe,aAAa,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAMlF,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,aAAa,EAAE,UAAU,CAS9D,CAAC;AAEF,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,UAAU,CAE9D;AAED,KAAK,kBAAkB,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;AAE9E,UAAU,qBAAqB;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,qBAAqB,EAAE,qBAAqB,CAyBlF,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,aAAa,EAAE,qBAAqB,EAKhD,CAAC;AAyOF,UAAU,iBAAiB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,OAAe,EAAE,EAAE,iBAAiB,2CA0B3E"}
1
+ {"version":3,"file":"CitationList.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/components/CitationList.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,EAiBL,KAAK,UAAU,EAChB,MAAM,cAAc,CAAC;AAWtB,OAAO,KAAK,EAAe,aAAa,EAAE,qBAAqB,EAAE,MAAM,UAAU,CAAC;AAMlF,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,EAAE,MAAM,CAAC,aAAa,EAAE,UAAU,CAS9D,CAAC;AAEF,8EAA8E;AAC9E,wBAAgB,cAAc,CAAC,IAAI,EAAE,aAAa,GAAG,UAAU,CAE9D;AAED,KAAK,kBAAkB,GAAG,SAAS,GAAG,SAAS,GAAG,aAAa,GAAG,WAAW,CAAC;AAE9E,UAAU,qBAAqB;IAC7B,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,kBAAkB,CAAC;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;CACf;AAED,iEAAiE;AACjE,eAAO,MAAM,iBAAiB,EAAE,MAAM,CAAC,qBAAqB,EAAE,qBAAqB,CAyBlF,CAAC;AAEF,0DAA0D;AAC1D,eAAO,MAAM,aAAa,EAAE,qBAAqB,EAKhD,CAAC;AAiPF,UAAU,iBAAiB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,YAAY,CAAC,EAAE,OAAO,EAAE,OAAe,EAAE,SAAS,EAAE,EAAE,iBAAiB,2CAiCtF"}
@@ -6,68 +6,69 @@ import { useTcpCitations as o, useTcpVerifyCitation as s } from "../hooks/useTcp
6
6
  import { describeError as c, notify as l } from "../lib/toast.js";
7
7
  import "../hooks/index.js";
8
8
  import { EmptyState as u } from "./EmptyState.js";
9
- import { createElement as d } from "react";
10
- import { AlertCircle as f, BookMarked as p, BookOpen as m, Box as h, CheckCircle2 as g, ExternalLink as _, FileText as v, Globe as y, GraduationCap as b, HelpCircle as x, Landmark as S, Mic as C, Newspaper as w, Quote as T, ShieldCheck as E, XCircle as D } from "lucide-react";
11
- import { jsx as O, jsxs as k } from "react/jsx-runtime";
12
- import { Spinner as A } from "@burdenoff/fe-libs/ui";
9
+ import { SourceScanLink as d } from "./SourceScanLink.js";
10
+ import { createElement as f } from "react";
11
+ import { AlertCircle as p, BookMarked as m, BookOpen as h, Box as g, CheckCircle2 as _, ExternalLink as v, FileText as y, Globe as b, GraduationCap as x, HelpCircle as S, Landmark as C, Mic as w, Newspaper as T, Quote as E, ShieldCheck as D, XCircle as O } from "lucide-react";
12
+ import { jsx as k, jsxs as A } from "react/jsx-runtime";
13
+ import { Spinner as j } from "@burdenoff/fe-libs/ui";
13
14
  //#region src/theculturepost/components/CitationList.tsx
14
- var j = {
15
- ACADEMIC: b,
16
- ARCHIVAL_RECORD: S,
17
- ARTIFACT: h,
18
- BOOK: m,
19
- INTERVIEW: C,
20
- PERIODICAL: w,
21
- PRIMARY_DOCUMENT: v,
22
- WEB: y
15
+ var M = {
16
+ ACADEMIC: x,
17
+ ARCHIVAL_RECORD: C,
18
+ ARTIFACT: g,
19
+ BOOK: h,
20
+ INTERVIEW: w,
21
+ PERIODICAL: T,
22
+ PRIMARY_DOCUMENT: y,
23
+ WEB: b
23
24
  };
24
- function M(e) {
25
- return j[e] ?? v;
25
+ function N(e) {
26
+ return M[e] ?? y;
26
27
  }
27
- var N = {
28
+ var P = {
28
29
  CORROBORATED: {
29
- icon: g,
30
+ icon: _,
30
31
  accent: "success",
31
32
  label: "Corroborated",
32
33
  blurb: "Strong agreement among reliable sources."
33
34
  },
34
35
  DISPUTED: {
35
- icon: f,
36
+ icon: p,
36
37
  accent: "warning",
37
38
  label: "Disputed",
38
39
  blurb: "Conflicting or insufficient evidence."
39
40
  },
40
41
  RETRACTED: {
41
- icon: D,
42
+ icon: O,
42
43
  accent: "destructive",
43
44
  label: "Retracted",
44
45
  blurb: "The source has since been withdrawn."
45
46
  },
46
47
  UNVERIFIED: {
47
- icon: x,
48
+ icon: S,
48
49
  accent: "secondary",
49
50
  label: "Unverified",
50
51
  blurb: "Not yet reviewed by an editor."
51
52
  }
52
- }, P = [
53
+ }, F = [
53
54
  "CORROBORATED",
54
55
  "DISPUTED",
55
56
  "RETRACTED",
56
57
  "UNVERIFIED"
57
- ], F = {
58
+ ], I = {
58
59
  success: "bg-status-success-bg-subtle text-status-success-text shadow-elevation-1",
59
60
  warning: "bg-status-warning-bg-subtle text-status-warning-text shadow-elevation-1",
60
61
  destructive: "bg-status-error-bg-subtle text-status-error-text shadow-elevation-1",
61
62
  secondary: "bg-bg-sunken text-text-primary shadow-elevation-1"
62
63
  };
63
- function I({ value: t, onChange: n, pending: r = !1, compact: i = !1, className: a }) {
64
- return /* @__PURE__ */ O("div", {
64
+ function L({ value: t, onChange: n, pending: r = !1, compact: i = !1, className: a }) {
65
+ return /* @__PURE__ */ k("div", {
65
66
  role: "radiogroup",
66
67
  "aria-label": "Verification status",
67
68
  className: e("grid grid-cols-2 gap-1 rounded-xl border border-border-seam bg-bg-surface p-1", i ? "grid-cols-4" : "sm:inline-grid sm:auto-cols-max sm:grid-flow-col", a),
68
- children: P.map((a) => {
69
- let o = N[a], s = o.icon, c = a === t;
70
- return /* @__PURE__ */ k("button", {
69
+ children: F.map((a) => {
70
+ let o = P[a], s = o.icon, c = a === t;
71
+ return /* @__PURE__ */ A("button", {
71
72
  type: "button",
72
73
  role: "radio",
73
74
  "aria-checked": c,
@@ -75,11 +76,11 @@ function I({ value: t, onChange: n, pending: r = !1, compact: i = !1, className:
75
76
  title: o.label,
76
77
  disabled: r,
77
78
  onClick: () => n(a),
78
- className: e("inline-flex min-h-9 items-center justify-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium transition-colors disabled:opacity-60", c ? F[o.accent] : "text-text-tertiary hover:bg-bg-sunken hover:text-text-primary"),
79
- children: [/* @__PURE__ */ O(s, {
79
+ className: e("inline-flex min-h-9 items-center justify-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium transition-colors disabled:opacity-60", c ? I[o.accent] : "text-text-tertiary hover:bg-bg-sunken hover:text-text-primary"),
80
+ children: [/* @__PURE__ */ k(s, {
80
81
  className: "h-3.5 w-3.5 shrink-0",
81
82
  "aria-hidden": "true"
82
- }), /* @__PURE__ */ O("span", {
83
+ }), /* @__PURE__ */ k("span", {
83
84
  className: i ? "sr-only" : "hidden sm:inline",
84
85
  children: o.label
85
86
  })]
@@ -87,7 +88,7 @@ function I({ value: t, onChange: n, pending: r = !1, compact: i = !1, className:
87
88
  })
88
89
  });
89
90
  }
90
- function L({ citation: t, compact: n }) {
91
+ function R({ citation: t, compact: n }) {
91
92
  let o = s(), u = async (e) => {
92
93
  if (e !== t.verification) try {
93
94
  await o.mutateAsync({
@@ -97,48 +98,48 @@ function L({ citation: t, compact: n }) {
97
98
  } catch (e) {
98
99
  l.error("Could not update verification", c(e));
99
100
  }
100
- }, f = [
101
+ }, p = [
101
102
  t.author,
102
103
  t.publisher,
103
104
  r(t.publishedOn)
104
105
  ].filter((e) => e && e !== "—").join(" · ");
105
- return /* @__PURE__ */ k("li", {
106
+ return /* @__PURE__ */ A("li", {
106
107
  className: e("rounded-2xl border border-border-seam bg-bg-surface transition-colors hover:border-border-strong", n ? "p-3.5" : "p-4 sm:p-5"),
107
- children: [/* @__PURE__ */ k("div", {
108
+ children: [/* @__PURE__ */ A("div", {
108
109
  className: e("flex gap-3", n ? "items-start" : "sm:gap-4"),
109
- children: [/* @__PURE__ */ O("span", {
110
+ children: [/* @__PURE__ */ k("span", {
110
111
  className: e("grid shrink-0 place-items-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary", n ? "h-9 w-9" : "h-12 w-12"),
111
112
  "aria-hidden": "true",
112
- children: d(M(t.sourceType), { className: n ? "h-4 w-4" : "h-6 w-6" })
113
- }), /* @__PURE__ */ k("div", {
113
+ children: f(N(t.sourceType), { className: n ? "h-4 w-4" : "h-6 w-6" })
114
+ }), /* @__PURE__ */ A("div", {
114
115
  className: "min-w-0 flex-1",
115
116
  children: [
116
- /* @__PURE__ */ k("div", {
117
+ /* @__PURE__ */ A("div", {
117
118
  className: "mb-1 flex flex-wrap items-center gap-1.5",
118
- children: [/* @__PURE__ */ O(a, {
119
+ children: [/* @__PURE__ */ k(a, {
119
120
  status: t.sourceType,
120
- icon: d(M(t.sourceType), { className: "h-3 w-3" })
121
- }), n && /* @__PURE__ */ O(a, { status: t.verification })]
121
+ icon: f(N(t.sourceType), { className: "h-3 w-3" })
122
+ }), n && /* @__PURE__ */ k(a, { status: t.verification })]
122
123
  }),
123
- /* @__PURE__ */ O("p", {
124
+ /* @__PURE__ */ k("p", {
124
125
  className: e("font-display font-semibold leading-snug text-text-primary", n ? "text-sm" : "text-base"),
125
126
  children: t.title
126
127
  }),
127
- f && /* @__PURE__ */ O("p", {
128
+ p && /* @__PURE__ */ k("p", {
128
129
  className: "mt-0.5 text-xs text-text-tertiary",
129
- children: f
130
+ children: p
130
131
  }),
131
- t.excerpt && !n && /* @__PURE__ */ k("blockquote", {
132
+ t.excerpt && !n && /* @__PURE__ */ A("blockquote", {
132
133
  className: "mt-3 flex gap-2 rounded-lg bg-bg-sunken/60 px-3 py-2 text-sm text-text-secondary",
133
- children: [/* @__PURE__ */ O(T, {
134
+ children: [/* @__PURE__ */ k(E, {
134
135
  className: "h-3.5 w-3.5 shrink-0 text-text-tertiary",
135
136
  "aria-hidden": "true"
136
- }), /* @__PURE__ */ O("span", { children: t.excerpt })]
137
+ }), /* @__PURE__ */ k("span", { children: t.excerpt })]
137
138
  }),
138
- t.provenance && !n && /* @__PURE__ */ k("p", {
139
+ t.provenance && !n && /* @__PURE__ */ A("p", {
139
140
  className: "mt-2 text-xs text-text-tertiary",
140
141
  children: [
141
- /* @__PURE__ */ O("span", {
142
+ /* @__PURE__ */ k("span", {
142
143
  className: "font-medium text-text-secondary",
143
144
  children: "Provenance:"
144
145
  }),
@@ -146,34 +147,40 @@ function L({ citation: t, compact: n }) {
146
147
  t.provenance
147
148
  ]
148
149
  }),
149
- t.url && /* @__PURE__ */ k("a", {
150
- href: t.url,
151
- target: "_blank",
152
- rel: "noreferrer noopener",
153
- className: "mt-2 inline-flex max-w-full items-center gap-1 text-xs font-medium text-accent hover:underline",
154
- children: [/* @__PURE__ */ O("span", {
155
- className: "truncate",
156
- children: n ? "View source" : t.url
157
- }), /* @__PURE__ */ O(_, {
158
- className: "h-3 w-3 shrink-0",
159
- "aria-hidden": "true"
150
+ (t.url || t.sourceFile) && /* @__PURE__ */ A("div", {
151
+ className: "mt-2 flex flex-wrap items-center gap-x-3 gap-y-1",
152
+ children: [t.url && /* @__PURE__ */ A("a", {
153
+ href: t.url,
154
+ target: "_blank",
155
+ rel: "noreferrer noopener",
156
+ className: "inline-flex max-w-full items-center gap-1 text-xs font-medium text-accent hover:underline",
157
+ children: [/* @__PURE__ */ k("span", {
158
+ className: "truncate",
159
+ children: n ? "View source" : t.url
160
+ }), /* @__PURE__ */ k(v, {
161
+ className: "h-3 w-3 shrink-0",
162
+ "aria-hidden": "true"
163
+ })]
164
+ }), /* @__PURE__ */ k(d, {
165
+ fileId: t.sourceFile?.id,
166
+ label: n ? "View scan" : "View source scan"
160
167
  })]
161
168
  })
162
169
  ]
163
170
  })]
164
- }), /* @__PURE__ */ k("div", {
171
+ }), /* @__PURE__ */ A("div", {
165
172
  className: e("mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 border-t border-border-seam pt-3", n ? "" : "sm:justify-between"),
166
- children: [/* @__PURE__ */ k("span", {
173
+ children: [/* @__PURE__ */ A("span", {
167
174
  className: "inline-flex items-center gap-1.5 text-xs font-medium text-text-secondary",
168
175
  children: [
169
- /* @__PURE__ */ O(E, {
176
+ /* @__PURE__ */ k(D, {
170
177
  className: "h-3.5 w-3.5 text-text-tertiary",
171
178
  "aria-hidden": "true"
172
179
  }),
173
180
  "Verification",
174
- o.isPending && /* @__PURE__ */ O(A, { size: "sm" })
181
+ o.isPending && /* @__PURE__ */ k(j, { size: "sm" })
175
182
  ]
176
- }), /* @__PURE__ */ O(I, {
183
+ }), /* @__PURE__ */ k(L, {
177
184
  value: t.verification,
178
185
  onChange: u,
179
186
  pending: o.isPending,
@@ -182,32 +189,32 @@ function L({ citation: t, compact: n }) {
182
189
  })]
183
190
  });
184
191
  }
185
- function R() {
186
- return /* @__PURE__ */ k("div", {
192
+ function z() {
193
+ return /* @__PURE__ */ A("div", {
187
194
  className: "mt-4 rounded-2xl border border-border-seam bg-bg-sunken/50 p-4 sm:p-5",
188
- children: [/* @__PURE__ */ k("div", {
195
+ children: [/* @__PURE__ */ A("div", {
189
196
  className: "flex items-start gap-3",
190
- children: [/* @__PURE__ */ O("span", {
197
+ children: [/* @__PURE__ */ k("span", {
191
198
  className: "grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary",
192
199
  "aria-hidden": "true",
193
- children: /* @__PURE__ */ O(E, { className: "h-5 w-5" })
194
- }), /* @__PURE__ */ O("p", {
200
+ children: /* @__PURE__ */ k(D, { className: "h-5 w-5" })
201
+ }), /* @__PURE__ */ k("p", {
195
202
  className: "text-sm text-text-secondary",
196
203
  children: "Every source is evaluated for accuracy, relevance, and cultural sensitivity through a transparent, multi-step review by editors and subject-area contributors."
197
204
  })]
198
- }), /* @__PURE__ */ O("dl", {
205
+ }), /* @__PURE__ */ k("dl", {
199
206
  className: "mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4",
200
- children: P.map((t) => {
201
- let n = N[t], r = n.icon;
202
- return /* @__PURE__ */ k("div", {
207
+ children: F.map((t) => {
208
+ let n = P[t], r = n.icon;
209
+ return /* @__PURE__ */ A("div", {
203
210
  className: "flex items-start gap-2",
204
- children: [/* @__PURE__ */ O(r, {
211
+ children: [/* @__PURE__ */ k(r, {
205
212
  className: e("mt-0.5 h-4 w-4 shrink-0", n.accent === "success" && "text-status-success-text", n.accent === "warning" && "text-status-warning-text", n.accent === "destructive" && "text-status-error-text", n.accent === "secondary" && "text-text-tertiary"),
206
213
  "aria-hidden": "true"
207
- }), /* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("dt", {
214
+ }), /* @__PURE__ */ A("div", { children: [/* @__PURE__ */ k("dt", {
208
215
  className: "text-xs font-semibold text-text-primary",
209
216
  children: n.label
210
- }), /* @__PURE__ */ O("dd", {
217
+ }), /* @__PURE__ */ k("dd", {
211
218
  className: "mt-0.5 text-xs text-text-tertiary",
212
219
  children: n.blurb
213
220
  })] })]
@@ -216,24 +223,36 @@ function R() {
216
223
  })]
217
224
  });
218
225
  }
219
- function z({ storyId: r, compact: i = !1 }) {
220
- let { data: a, isLoading: s, isError: c, refetch: l } = o(r);
221
- return s ? /* @__PURE__ */ O(n, { label: "Loading citations…" }) : c ? /* @__PURE__ */ O(t, {
222
- message: "We couldn't load citations.",
223
- onRetry: () => l()
224
- }) : !a || a.length === 0 ? /* @__PURE__ */ O(u, {
225
- icon: /* @__PURE__ */ O(p, { className: "h-9 w-9" }),
226
- title: "No citations yet",
227
- description: "Add verifiable sources to ground this story in the record."
228
- }) : /* @__PURE__ */ k("div", { children: [/* @__PURE__ */ O("ul", {
229
- className: e("flex flex-col", i ? "gap-3" : "gap-3 sm:gap-4"),
230
- children: a.map((e) => /* @__PURE__ */ O(L, {
231
- citation: e,
232
- compact: i
233
- }, e.id))
234
- }), !i && /* @__PURE__ */ O(R, {})] });
226
+ function B({ storyId: r, compact: i = !1, className: a }) {
227
+ let { data: s, isLoading: c, isError: l, refetch: d } = o(r);
228
+ return c ? /* @__PURE__ */ k(n, {
229
+ label: "Loading citations…",
230
+ className: a
231
+ }) : l ? /* @__PURE__ */ k("div", {
232
+ className: a,
233
+ children: /* @__PURE__ */ k(t, {
234
+ message: "We couldn't load citations.",
235
+ onRetry: () => d()
236
+ })
237
+ }) : !s || s.length === 0 ? /* @__PURE__ */ k("div", {
238
+ className: a,
239
+ children: /* @__PURE__ */ k(u, {
240
+ icon: /* @__PURE__ */ k(m, { className: "h-9 w-9" }),
241
+ title: "No citations yet",
242
+ description: "Add verifiable sources to ground this story in the record."
243
+ })
244
+ }) : /* @__PURE__ */ A("div", {
245
+ className: a,
246
+ children: [/* @__PURE__ */ k("ul", {
247
+ className: e("flex flex-col", i ? "gap-3" : "gap-3 sm:gap-4"),
248
+ children: s.map((e) => /* @__PURE__ */ k(R, {
249
+ citation: e,
250
+ compact: i
251
+ }, e.id))
252
+ }), !i && /* @__PURE__ */ k(z, {})]
253
+ });
235
254
  }
236
255
  //#endregion
237
- export { z as CitationList, P as VERIFICATIONS, N as VERIFICATION_META, M as sourceTypeIcon };
256
+ export { B as CitationList, F as VERIFICATIONS, P as VERIFICATION_META, N as sourceTypeIcon };
238
257
 
239
258
  //# sourceMappingURL=CitationList.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"CitationList.js","names":[],"sources":["../../../src/theculturepost/components/CitationList.tsx"],"sourcesContent":["/**\n * CitationList — the verifiable sources that ground a story (tcp-05).\n * @module theculturepost/components\n *\n * Reads `tcpListCitations(storyId)`; each entry can be moved through its\n * verification state (`tcpVerifyCitation`) via a **segmented control** — the\n * archival-integrity control at the heart of a serious cultural publication.\n *\n * Each source shows a source-type icon, its bibliographic detail, an optional\n * excerpt + link, and (in the full variant) closes with a static methodology\n * legend. There is deliberately NO \"evidence trail\" mapping citations to\n * individual claims — the backend exposes no such linkage, so we never fake it.\n *\n * Two layouts, same data:\n * - default (full): roomy cards + labelled segments + methodology legend.\n * - `compact`: narrow-rail cards + icon-only segments, no legend.\n */\n\nimport {\n BookOpen,\n GraduationCap,\n Landmark,\n FileText,\n Globe,\n Newspaper,\n Mic,\n Box,\n ExternalLink,\n Quote,\n ShieldCheck,\n CheckCircle2,\n AlertCircle,\n XCircle,\n HelpCircle,\n BookMarked,\n type LucideIcon,\n} from 'lucide-react';\nimport { createElement } from 'react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { LoadingView, ErrorView } from './StateViews';\nimport { EmptyState } from './EmptyState';\nimport { useTcpCitations, useTcpVerifyCitation } from '../hooks';\nimport { cn } from '../utils/cn';\nimport { formatDate, formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpCitation, TcpSourceType, TcpVerificationStatus } from '../types';\n\n// ---------------------------------------------------------------------------\n// Source-type + verification metadata (exported for reuse in the editorial table)\n// ---------------------------------------------------------------------------\n\n/** A distinct lucide motif per source type (icon, not hue — per the design rules). */\nexport const SOURCE_TYPE_ICON: Record<TcpSourceType, LucideIcon> = {\n ACADEMIC: GraduationCap,\n ARCHIVAL_RECORD: Landmark,\n ARTIFACT: Box,\n BOOK: BookOpen,\n INTERVIEW: Mic,\n PERIODICAL: Newspaper,\n PRIMARY_DOCUMENT: FileText,\n WEB: Globe,\n};\n\n/** Resolve a source-type's motif icon, falling back to a neutral document. */\nexport function sourceTypeIcon(type: TcpSourceType): LucideIcon {\n return SOURCE_TYPE_ICON[type] ?? FileText;\n}\n\ntype VerificationAccent = 'success' | 'warning' | 'destructive' | 'secondary';\n\ninterface VerificationMetaEntry {\n icon: LucideIcon;\n accent: VerificationAccent;\n label: string;\n /** Static, factual description of what the state means — shown in the legend. */\n blurb: string;\n}\n\n/** Ordered, tone-mapped metadata for each verification state. */\nexport const VERIFICATION_META: Record<TcpVerificationStatus, VerificationMetaEntry> = {\n CORROBORATED: {\n icon: CheckCircle2,\n accent: 'success',\n label: 'Corroborated',\n blurb: 'Strong agreement among reliable sources.',\n },\n DISPUTED: {\n icon: AlertCircle,\n accent: 'warning',\n label: 'Disputed',\n blurb: 'Conflicting or insufficient evidence.',\n },\n RETRACTED: {\n icon: XCircle,\n accent: 'destructive',\n label: 'Retracted',\n blurb: 'The source has since been withdrawn.',\n },\n UNVERIFIED: {\n icon: HelpCircle,\n accent: 'secondary',\n label: 'Unverified',\n blurb: 'Not yet reviewed by an editor.',\n },\n};\n\n/** The order the segmented control presents states in. */\nexport const VERIFICATIONS: TcpVerificationStatus[] = [\n 'CORROBORATED',\n 'DISPUTED',\n 'RETRACTED',\n 'UNVERIFIED',\n];\n\n/** Active-segment styling per accent — semantic tokens only. */\nconst SEGMENT_ACTIVE: Record<VerificationAccent, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text shadow-elevation-1',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text shadow-elevation-1',\n destructive: 'bg-status-error-bg-subtle text-status-error-text shadow-elevation-1',\n secondary: 'bg-bg-sunken text-text-primary shadow-elevation-1',\n};\n\n// ---------------------------------------------------------------------------\n// Segmented verification control\n// ---------------------------------------------------------------------------\n\ninterface VerificationSegmentsProps {\n value: TcpVerificationStatus;\n onChange: (value: TcpVerificationStatus) => void;\n pending?: boolean;\n /** Icon-only segments (for the narrow reading rail). */\n compact?: boolean;\n className?: string;\n}\n\nfunction VerificationSegments({\n value,\n onChange,\n pending = false,\n compact = false,\n className,\n}: VerificationSegmentsProps) {\n return (\n <div\n role=\"radiogroup\"\n aria-label=\"Verification status\"\n className={cn(\n 'grid grid-cols-2 gap-1 rounded-xl border border-border-seam bg-bg-surface p-1',\n compact ? 'grid-cols-4' : 'sm:inline-grid sm:auto-cols-max sm:grid-flow-col',\n className\n )}\n >\n {VERIFICATIONS.map((v) => {\n const meta = VERIFICATION_META[v];\n const Icon = meta.icon;\n const active = v === value;\n return (\n <button\n key={v}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n aria-label={meta.label}\n title={meta.label}\n disabled={pending}\n onClick={() => onChange(v)}\n className={cn(\n 'inline-flex min-h-9 items-center justify-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium transition-colors disabled:opacity-60',\n active\n ? SEGMENT_ACTIVE[meta.accent]\n : 'text-text-tertiary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n <Icon className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className={compact ? 'sr-only' : 'hidden sm:inline'}>{meta.label}</span>\n </button>\n );\n })}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Citation row\n// ---------------------------------------------------------------------------\n\nfunction CitationRow({ citation, compact }: { citation: TcpCitation; compact: boolean }) {\n const verify = useTcpVerifyCitation();\n\n const changeVerification = async (verification: TcpVerificationStatus) => {\n if (verification === citation.verification) return;\n try {\n await verify.mutateAsync({ id: citation.id, verification });\n notify.success('Verification updated', formatEnumLabel(verification));\n } catch (err) {\n notify.error('Could not update verification', describeError(err));\n }\n };\n\n const meta = [citation.author, citation.publisher, formatDate(citation.publishedOn)]\n .filter((v) => v && v !== '—')\n .join(' · ');\n\n return (\n <li\n className={cn(\n 'rounded-2xl border border-border-seam bg-bg-surface transition-colors hover:border-border-strong',\n compact ? 'p-3.5' : 'p-4 sm:p-5'\n )}\n >\n <div className={cn('flex gap-3', compact ? 'items-start' : 'sm:gap-4')}>\n {/* Source-type motif tile */}\n <span\n className={cn(\n 'grid shrink-0 place-items-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary',\n compact ? 'h-9 w-9' : 'h-12 w-12'\n )}\n aria-hidden=\"true\"\n >\n {createElement(sourceTypeIcon(citation.sourceType), {\n className: compact ? 'h-4 w-4' : 'h-6 w-6',\n })}\n </span>\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"mb-1 flex flex-wrap items-center gap-1.5\">\n <StatusBadge\n status={citation.sourceType}\n icon={createElement(sourceTypeIcon(citation.sourceType), { className: 'h-3 w-3' })}\n />\n {compact && <StatusBadge status={citation.verification} />}\n </div>\n\n <p\n className={cn(\n 'font-display font-semibold leading-snug text-text-primary',\n compact ? 'text-sm' : 'text-base'\n )}\n >\n {citation.title}\n </p>\n {meta && <p className=\"mt-0.5 text-xs text-text-tertiary\">{meta}</p>}\n\n {citation.excerpt && !compact && (\n <blockquote className=\"mt-3 flex gap-2 rounded-lg bg-bg-sunken/60 px-3 py-2 text-sm text-text-secondary\">\n <Quote className=\"h-3.5 w-3.5 shrink-0 text-text-tertiary\" aria-hidden=\"true\" />\n <span>{citation.excerpt}</span>\n </blockquote>\n )}\n\n {citation.provenance && !compact && (\n <p className=\"mt-2 text-xs text-text-tertiary\">\n <span className=\"font-medium text-text-secondary\">Provenance:</span>{' '}\n {citation.provenance}\n </p>\n )}\n\n {citation.url && (\n <a\n href={citation.url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className=\"mt-2 inline-flex max-w-full items-center gap-1 text-xs font-medium text-accent hover:underline\"\n >\n <span className=\"truncate\">{compact ? 'View source' : citation.url}</span>\n <ExternalLink className=\"h-3 w-3 shrink-0\" aria-hidden=\"true\" />\n </a>\n )}\n </div>\n </div>\n\n {/* Verification control */}\n <div\n className={cn(\n 'mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 border-t border-border-seam pt-3',\n compact ? '' : 'sm:justify-between'\n )}\n >\n <span className=\"inline-flex items-center gap-1.5 text-xs font-medium text-text-secondary\">\n <ShieldCheck className=\"h-3.5 w-3.5 text-text-tertiary\" aria-hidden=\"true\" />\n Verification\n {verify.isPending && <Spinner size=\"sm\" />}\n </span>\n <VerificationSegments\n value={citation.verification}\n onChange={changeVerification}\n pending={verify.isPending}\n compact={compact}\n />\n </div>\n </li>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Methodology legend (static — no fabricated data)\n// ---------------------------------------------------------------------------\n\nfunction MethodologyLegend() {\n return (\n <div className=\"mt-4 rounded-2xl border border-border-seam bg-bg-sunken/50 p-4 sm:p-5\">\n <div className=\"flex items-start gap-3\">\n <span\n className=\"grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\"\n aria-hidden=\"true\"\n >\n <ShieldCheck className=\"h-5 w-5\" />\n </span>\n <p className=\"text-sm text-text-secondary\">\n Every source is evaluated for accuracy, relevance, and cultural sensitivity through a\n transparent, multi-step review by editors and subject-area contributors.\n </p>\n </div>\n <dl className=\"mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n {VERIFICATIONS.map((v) => {\n const m = VERIFICATION_META[v];\n const Icon = m.icon;\n return (\n <div key={v} className=\"flex items-start gap-2\">\n <Icon\n className={cn(\n 'mt-0.5 h-4 w-4 shrink-0',\n m.accent === 'success' && 'text-status-success-text',\n m.accent === 'warning' && 'text-status-warning-text',\n m.accent === 'destructive' && 'text-status-error-text',\n m.accent === 'secondary' && 'text-text-tertiary'\n )}\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs font-semibold text-text-primary\">{m.label}</dt>\n <dd className=\"mt-0.5 text-xs text-text-tertiary\">{m.blurb}</dd>\n </div>\n </div>\n );\n })}\n </dl>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// CitationList\n// ---------------------------------------------------------------------------\n\ninterface CitationListProps {\n storyId: string;\n /** Narrow reading-rail rendering: smaller cards, icon-only segments, no legend. */\n compact?: boolean;\n}\n\nexport function CitationList({ storyId, compact = false }: CitationListProps) {\n const { data: citations, isLoading, isError, refetch } = useTcpCitations(storyId);\n\n if (isLoading) return <LoadingView label=\"Loading citations…\" />;\n if (isError) return <ErrorView message=\"We couldn't load citations.\" onRetry={() => refetch()} />;\n\n if (!citations || citations.length === 0) {\n return (\n <EmptyState\n icon={<BookMarked className=\"h-9 w-9\" />}\n title=\"No citations yet\"\n description=\"Add verifiable sources to ground this story in the record.\"\n />\n );\n }\n\n return (\n <div>\n <ul className={cn('flex flex-col', compact ? 'gap-3' : 'gap-3 sm:gap-4')}>\n {citations.map((citation) => (\n <CitationRow key={citation.id} citation={citation} compact={compact} />\n ))}\n </ul>\n {!compact && <MethodologyLegend />}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"CitationList.js","names":[],"sources":["../../../src/theculturepost/components/CitationList.tsx"],"sourcesContent":["/**\n * CitationList — the verifiable sources that ground a story (tcp-05).\n * @module theculturepost/components\n *\n * Reads `tcpListCitations(storyId)`; each entry can be moved through its\n * verification state (`tcpVerifyCitation`) via a **segmented control** — the\n * archival-integrity control at the heart of a serious cultural publication.\n *\n * Each source shows a source-type icon, its bibliographic detail, an optional\n * excerpt + link, and (in the full variant) closes with a static methodology\n * legend. There is deliberately NO \"evidence trail\" mapping citations to\n * individual claims — the backend exposes no such linkage, so we never fake it.\n *\n * Two layouts, same data:\n * - default (full): roomy cards + labelled segments + methodology legend.\n * - `compact`: narrow-rail cards + icon-only segments, no legend.\n */\n\nimport {\n BookOpen,\n GraduationCap,\n Landmark,\n FileText,\n Globe,\n Newspaper,\n Mic,\n Box,\n ExternalLink,\n Quote,\n ShieldCheck,\n CheckCircle2,\n AlertCircle,\n XCircle,\n HelpCircle,\n BookMarked,\n type LucideIcon,\n} from 'lucide-react';\nimport { createElement } from 'react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { StatusBadge } from './StatusBadge';\nimport { SourceScanLink } from './SourceScanLink';\nimport { LoadingView, ErrorView } from './StateViews';\nimport { EmptyState } from './EmptyState';\nimport { useTcpCitations, useTcpVerifyCitation } from '../hooks';\nimport { cn } from '../utils/cn';\nimport { formatDate, formatEnumLabel } from '../utils/formatters';\nimport { notify, describeError } from '../lib/toast';\nimport type { TcpCitation, TcpSourceType, TcpVerificationStatus } from '../types';\n\n// ---------------------------------------------------------------------------\n// Source-type + verification metadata (exported for reuse in the editorial table)\n// ---------------------------------------------------------------------------\n\n/** A distinct lucide motif per source type (icon, not hue — per the design rules). */\nexport const SOURCE_TYPE_ICON: Record<TcpSourceType, LucideIcon> = {\n ACADEMIC: GraduationCap,\n ARCHIVAL_RECORD: Landmark,\n ARTIFACT: Box,\n BOOK: BookOpen,\n INTERVIEW: Mic,\n PERIODICAL: Newspaper,\n PRIMARY_DOCUMENT: FileText,\n WEB: Globe,\n};\n\n/** Resolve a source-type's motif icon, falling back to a neutral document. */\nexport function sourceTypeIcon(type: TcpSourceType): LucideIcon {\n return SOURCE_TYPE_ICON[type] ?? FileText;\n}\n\ntype VerificationAccent = 'success' | 'warning' | 'destructive' | 'secondary';\n\ninterface VerificationMetaEntry {\n icon: LucideIcon;\n accent: VerificationAccent;\n label: string;\n /** Static, factual description of what the state means — shown in the legend. */\n blurb: string;\n}\n\n/** Ordered, tone-mapped metadata for each verification state. */\nexport const VERIFICATION_META: Record<TcpVerificationStatus, VerificationMetaEntry> = {\n CORROBORATED: {\n icon: CheckCircle2,\n accent: 'success',\n label: 'Corroborated',\n blurb: 'Strong agreement among reliable sources.',\n },\n DISPUTED: {\n icon: AlertCircle,\n accent: 'warning',\n label: 'Disputed',\n blurb: 'Conflicting or insufficient evidence.',\n },\n RETRACTED: {\n icon: XCircle,\n accent: 'destructive',\n label: 'Retracted',\n blurb: 'The source has since been withdrawn.',\n },\n UNVERIFIED: {\n icon: HelpCircle,\n accent: 'secondary',\n label: 'Unverified',\n blurb: 'Not yet reviewed by an editor.',\n },\n};\n\n/** The order the segmented control presents states in. */\nexport const VERIFICATIONS: TcpVerificationStatus[] = [\n 'CORROBORATED',\n 'DISPUTED',\n 'RETRACTED',\n 'UNVERIFIED',\n];\n\n/** Active-segment styling per accent — semantic tokens only. */\nconst SEGMENT_ACTIVE: Record<VerificationAccent, string> = {\n success: 'bg-status-success-bg-subtle text-status-success-text shadow-elevation-1',\n warning: 'bg-status-warning-bg-subtle text-status-warning-text shadow-elevation-1',\n destructive: 'bg-status-error-bg-subtle text-status-error-text shadow-elevation-1',\n secondary: 'bg-bg-sunken text-text-primary shadow-elevation-1',\n};\n\n// ---------------------------------------------------------------------------\n// Segmented verification control\n// ---------------------------------------------------------------------------\n\ninterface VerificationSegmentsProps {\n value: TcpVerificationStatus;\n onChange: (value: TcpVerificationStatus) => void;\n pending?: boolean;\n /** Icon-only segments (for the narrow reading rail). */\n compact?: boolean;\n className?: string;\n}\n\nfunction VerificationSegments({\n value,\n onChange,\n pending = false,\n compact = false,\n className,\n}: VerificationSegmentsProps) {\n return (\n <div\n role=\"radiogroup\"\n aria-label=\"Verification status\"\n className={cn(\n 'grid grid-cols-2 gap-1 rounded-xl border border-border-seam bg-bg-surface p-1',\n compact ? 'grid-cols-4' : 'sm:inline-grid sm:auto-cols-max sm:grid-flow-col',\n className\n )}\n >\n {VERIFICATIONS.map((v) => {\n const meta = VERIFICATION_META[v];\n const Icon = meta.icon;\n const active = v === value;\n return (\n <button\n key={v}\n type=\"button\"\n role=\"radio\"\n aria-checked={active}\n aria-label={meta.label}\n title={meta.label}\n disabled={pending}\n onClick={() => onChange(v)}\n className={cn(\n 'inline-flex min-h-9 items-center justify-center gap-1.5 rounded-lg px-2.5 py-1.5 text-xs font-medium transition-colors disabled:opacity-60',\n active\n ? SEGMENT_ACTIVE[meta.accent]\n : 'text-text-tertiary hover:bg-bg-sunken hover:text-text-primary'\n )}\n >\n <Icon className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n <span className={compact ? 'sr-only' : 'hidden sm:inline'}>{meta.label}</span>\n </button>\n );\n })}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Citation row\n// ---------------------------------------------------------------------------\n\nfunction CitationRow({ citation, compact }: { citation: TcpCitation; compact: boolean }) {\n const verify = useTcpVerifyCitation();\n\n const changeVerification = async (verification: TcpVerificationStatus) => {\n if (verification === citation.verification) return;\n try {\n await verify.mutateAsync({ id: citation.id, verification });\n notify.success('Verification updated', formatEnumLabel(verification));\n } catch (err) {\n notify.error('Could not update verification', describeError(err));\n }\n };\n\n const meta = [citation.author, citation.publisher, formatDate(citation.publishedOn)]\n .filter((v) => v && v !== '—')\n .join(' · ');\n\n return (\n <li\n className={cn(\n 'rounded-2xl border border-border-seam bg-bg-surface transition-colors hover:border-border-strong',\n compact ? 'p-3.5' : 'p-4 sm:p-5'\n )}\n >\n <div className={cn('flex gap-3', compact ? 'items-start' : 'sm:gap-4')}>\n {/* Source-type motif tile */}\n <span\n className={cn(\n 'grid shrink-0 place-items-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary',\n compact ? 'h-9 w-9' : 'h-12 w-12'\n )}\n aria-hidden=\"true\"\n >\n {createElement(sourceTypeIcon(citation.sourceType), {\n className: compact ? 'h-4 w-4' : 'h-6 w-6',\n })}\n </span>\n\n <div className=\"min-w-0 flex-1\">\n <div className=\"mb-1 flex flex-wrap items-center gap-1.5\">\n <StatusBadge\n status={citation.sourceType}\n icon={createElement(sourceTypeIcon(citation.sourceType), { className: 'h-3 w-3' })}\n />\n {compact && <StatusBadge status={citation.verification} />}\n </div>\n\n <p\n className={cn(\n 'font-display font-semibold leading-snug text-text-primary',\n compact ? 'text-sm' : 'text-base'\n )}\n >\n {citation.title}\n </p>\n {meta && <p className=\"mt-0.5 text-xs text-text-tertiary\">{meta}</p>}\n\n {citation.excerpt && !compact && (\n <blockquote className=\"mt-3 flex gap-2 rounded-lg bg-bg-sunken/60 px-3 py-2 text-sm text-text-secondary\">\n <Quote className=\"h-3.5 w-3.5 shrink-0 text-text-tertiary\" aria-hidden=\"true\" />\n <span>{citation.excerpt}</span>\n </blockquote>\n )}\n\n {citation.provenance && !compact && (\n <p className=\"mt-2 text-xs text-text-tertiary\">\n <span className=\"font-medium text-text-secondary\">Provenance:</span>{' '}\n {citation.provenance}\n </p>\n )}\n\n {(citation.url || citation.sourceFile) && (\n <div className=\"mt-2 flex flex-wrap items-center gap-x-3 gap-y-1\">\n {citation.url && (\n <a\n href={citation.url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className=\"inline-flex max-w-full items-center gap-1 text-xs font-medium text-accent hover:underline\"\n >\n <span className=\"truncate\">{compact ? 'View source' : citation.url}</span>\n <ExternalLink className=\"h-3 w-3 shrink-0\" aria-hidden=\"true\" />\n </a>\n )}\n <SourceScanLink\n fileId={citation.sourceFile?.id}\n label={compact ? 'View scan' : 'View source scan'}\n />\n </div>\n )}\n </div>\n </div>\n\n {/* Verification control */}\n <div\n className={cn(\n 'mt-3 flex flex-wrap items-center gap-x-3 gap-y-2 border-t border-border-seam pt-3',\n compact ? '' : 'sm:justify-between'\n )}\n >\n <span className=\"inline-flex items-center gap-1.5 text-xs font-medium text-text-secondary\">\n <ShieldCheck className=\"h-3.5 w-3.5 text-text-tertiary\" aria-hidden=\"true\" />\n Verification\n {verify.isPending && <Spinner size=\"sm\" />}\n </span>\n <VerificationSegments\n value={citation.verification}\n onChange={changeVerification}\n pending={verify.isPending}\n compact={compact}\n />\n </div>\n </li>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Methodology legend (static — no fabricated data)\n// ---------------------------------------------------------------------------\n\nfunction MethodologyLegend() {\n return (\n <div className=\"mt-4 rounded-2xl border border-border-seam bg-bg-sunken/50 p-4 sm:p-5\">\n <div className=\"flex items-start gap-3\">\n <span\n className=\"grid h-9 w-9 shrink-0 place-items-center rounded-xl bg-[var(--color-accent-soft)] text-text-primary\"\n aria-hidden=\"true\"\n >\n <ShieldCheck className=\"h-5 w-5\" />\n </span>\n <p className=\"text-sm text-text-secondary\">\n Every source is evaluated for accuracy, relevance, and cultural sensitivity through a\n transparent, multi-step review by editors and subject-area contributors.\n </p>\n </div>\n <dl className=\"mt-4 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4\">\n {VERIFICATIONS.map((v) => {\n const m = VERIFICATION_META[v];\n const Icon = m.icon;\n return (\n <div key={v} className=\"flex items-start gap-2\">\n <Icon\n className={cn(\n 'mt-0.5 h-4 w-4 shrink-0',\n m.accent === 'success' && 'text-status-success-text',\n m.accent === 'warning' && 'text-status-warning-text',\n m.accent === 'destructive' && 'text-status-error-text',\n m.accent === 'secondary' && 'text-text-tertiary'\n )}\n aria-hidden=\"true\"\n />\n <div>\n <dt className=\"text-xs font-semibold text-text-primary\">{m.label}</dt>\n <dd className=\"mt-0.5 text-xs text-text-tertiary\">{m.blurb}</dd>\n </div>\n </div>\n );\n })}\n </dl>\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// CitationList\n// ---------------------------------------------------------------------------\n\ninterface CitationListProps {\n storyId: string;\n /** Narrow reading-rail rendering: smaller cards, icon-only segments, no legend. */\n compact?: boolean;\n className?: string;\n}\n\nexport function CitationList({ storyId, compact = false, className }: CitationListProps) {\n const { data: citations, isLoading, isError, refetch } = useTcpCitations(storyId);\n\n if (isLoading) return <LoadingView label=\"Loading citations…\" className={className} />;\n if (isError)\n return (\n <div className={className}>\n <ErrorView message=\"We couldn't load citations.\" onRetry={() => refetch()} />\n </div>\n );\n\n if (!citations || citations.length === 0) {\n return (\n <div className={className}>\n <EmptyState\n icon={<BookMarked className=\"h-9 w-9\" />}\n title=\"No citations yet\"\n description=\"Add verifiable sources to ground this story in the record.\"\n />\n </div>\n );\n }\n\n return (\n <div className={className}>\n <ul className={cn('flex flex-col', compact ? 'gap-3' : 'gap-3 sm:gap-4')}>\n {citations.map((citation) => (\n <CitationRow key={citation.id} citation={citation} compact={compact} />\n ))}\n </ul>\n {!compact && <MethodologyLegend />}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -0,0 +1,25 @@
1
+ /**
2
+ * SourceScanLink — open the scanned primary source attached to a citation.
3
+ * @module theculturepost/components
4
+ *
5
+ * `TcpCitation.sourceFile` is an id-only wspace-files-svc reference — the scan
6
+ * an editor attaches through {@link AddCitationDialog}. Until now that field
7
+ * was write-only: the dialog persisted `sourceFileId`, every citation query
8
+ * selected `sourceFile { id }`, and no surface ever offered a way back to the
9
+ * document. This resolves the id to a presigned URL via {@link useFileUrl}.
10
+ *
11
+ * States are deliberate: NOTHING renders when the citation carries no scan, a
12
+ * non-interactive note renders while the URL resolves, and an honest
13
+ * "unavailable" note renders if resolution fails — never a link that looks live
14
+ * but leads nowhere.
15
+ */
16
+ interface SourceScanLinkProps {
17
+ /** `citation.sourceFile?.id` — nullish when no scan was attached. */
18
+ fileId?: string | null;
19
+ /** Visible link text. */
20
+ label?: string;
21
+ className?: string;
22
+ }
23
+ export declare function SourceScanLink({ fileId, label, className, }: SourceScanLinkProps): import("react/jsx-runtime").JSX.Element | null;
24
+ export {};
25
+ //# sourceMappingURL=SourceScanLink.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SourceScanLink.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/components/SourceScanLink.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAOH,UAAU,mBAAmB;IAC3B,qEAAqE;IACrE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,yBAAyB;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,wBAAgB,cAAc,CAAC,EAC7B,MAAM,EACN,KAA0B,EAC1B,SAAS,GACV,EAAE,mBAAmB,kDAoCrB"}
@@ -0,0 +1,36 @@
1
+ import { cn as e } from "../utils/cn.js";
2
+ import { useFileUrl as t } from "../hooks/useFiles.js";
3
+ import { ScanLine as n } from "lucide-react";
4
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
5
+ import { Spinner as a } from "@burdenoff/fe-libs/ui";
6
+ //#region src/theculturepost/components/SourceScanLink.tsx
7
+ function o({ fileId: o, label: s = "View source scan", className: c }) {
8
+ let { data: l, isLoading: u, isError: d } = t(o);
9
+ return o ? u ? /* @__PURE__ */ i("span", {
10
+ className: e("inline-flex items-center gap-1.5 text-xs text-text-tertiary", c),
11
+ children: [/* @__PURE__ */ r(a, { size: "sm" }), " Opening scan…"]
12
+ }) : d || !l ? /* @__PURE__ */ i("span", {
13
+ className: e("inline-flex items-center gap-1 text-xs text-text-tertiary", c),
14
+ children: [/* @__PURE__ */ r(n, {
15
+ className: "h-3 w-3 shrink-0",
16
+ "aria-hidden": "true"
17
+ }), " Source scan unavailable"]
18
+ }) : /* @__PURE__ */ i("a", {
19
+ href: l,
20
+ target: "_blank",
21
+ rel: "noreferrer noopener",
22
+ className: e("inline-flex items-center gap-1 text-xs font-medium text-accent hover:underline", c),
23
+ children: [
24
+ /* @__PURE__ */ r(n, {
25
+ className: "h-3 w-3 shrink-0",
26
+ "aria-hidden": "true"
27
+ }),
28
+ " ",
29
+ s
30
+ ]
31
+ }) : null;
32
+ }
33
+ //#endregion
34
+ export { o as SourceScanLink };
35
+
36
+ //# sourceMappingURL=SourceScanLink.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SourceScanLink.js","names":[],"sources":["../../../src/theculturepost/components/SourceScanLink.tsx"],"sourcesContent":["/**\n * SourceScanLink — open the scanned primary source attached to a citation.\n * @module theculturepost/components\n *\n * `TcpCitation.sourceFile` is an id-only wspace-files-svc reference — the scan\n * an editor attaches through {@link AddCitationDialog}. Until now that field\n * was write-only: the dialog persisted `sourceFileId`, every citation query\n * selected `sourceFile { id }`, and no surface ever offered a way back to the\n * document. This resolves the id to a presigned URL via {@link useFileUrl}.\n *\n * States are deliberate: NOTHING renders when the citation carries no scan, a\n * non-interactive note renders while the URL resolves, and an honest\n * \"unavailable\" note renders if resolution fails — never a link that looks live\n * but leads nowhere.\n */\n\nimport { ScanLine } from 'lucide-react';\nimport { Spinner } from '@burdenoff/fe-libs/ui';\nimport { cn } from '../utils/cn';\nimport { useFileUrl } from '../hooks/useFiles';\n\ninterface SourceScanLinkProps {\n /** `citation.sourceFile?.id` — nullish when no scan was attached. */\n fileId?: string | null;\n /** Visible link text. */\n label?: string;\n className?: string;\n}\n\nexport function SourceScanLink({\n fileId,\n label = 'View source scan',\n className,\n}: SourceScanLinkProps) {\n const { data: url, isLoading, isError } = useFileUrl(fileId);\n\n if (!fileId) return null;\n\n if (isLoading) {\n return (\n <span\n className={cn('inline-flex items-center gap-1.5 text-xs text-text-tertiary', className)}\n >\n <Spinner size=\"sm\" /> Opening scan&hellip;\n </span>\n );\n }\n\n if (isError || !url) {\n return (\n <span className={cn('inline-flex items-center gap-1 text-xs text-text-tertiary', className)}>\n <ScanLine className=\"h-3 w-3 shrink-0\" aria-hidden=\"true\" /> Source scan unavailable\n </span>\n );\n }\n\n return (\n <a\n href={url}\n target=\"_blank\"\n rel=\"noreferrer noopener\"\n className={cn(\n 'inline-flex items-center gap-1 text-xs font-medium text-accent hover:underline',\n className\n )}\n >\n <ScanLine className=\"h-3 w-3 shrink-0\" aria-hidden=\"true\" /> {label}\n </a>\n );\n}\n"],"mappings":";;;;;;AA6BA,SAAgB,EAAe,EAC7B,WACA,WAAQ,oBACR,gBACsB;CACtB,IAAM,EAAE,MAAM,GAAK,cAAW,eAAY,EAAW,EAAO;AAsB5D,QApBK,IAED,IAEA,kBAAC,QAAD;EACE,WAAW,EAAG,+DAA+D,EAAU;YADzF,CAGE,kBAAC,GAAD,EAAS,MAAK,MAAO,CAAA,EAAA,iBAChB;MAIP,KAAW,CAAC,IAEZ,kBAAC,QAAD;EAAM,WAAW,EAAG,6DAA6D,EAAU;YAA3F,CACE,kBAAC,GAAD;GAAU,WAAU;GAAmB,eAAY;GAAS,CAAA,EAAA,2BACvD;MAKT,kBAAC,KAAD;EACE,MAAM;EACN,QAAO;EACP,KAAI;EACJ,WAAW,EACT,kFACA,EACD;YAPH;GASE,kBAAC,GAAD;IAAU,WAAU;IAAmB,eAAY;IAAS,CAAA;;GAAE;GAC5D;MA/Bc"}
@@ -32,6 +32,7 @@ export { CollectionCard } from './CollectionCard';
32
32
  export { ArchiveItemCard } from './ArchiveItemCard';
33
33
  export { OralHistoryCard } from './OralHistoryCard';
34
34
  export { CitationList } from './CitationList';
35
+ export { SourceScanLink } from './SourceScanLink';
35
36
  export { ReplyThread } from './ReplyThread';
36
37
  export { CreateStoryDialog } from './CreateStoryDialog';
37
38
  export { CreateTopicDialog } from './CreateTopicDialog';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGnG,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAGjE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGpD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/theculturepost/components/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,QAAQ,EAAE,KAAK,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC/D,OAAO,EAAE,UAAU,EAAE,KAAK,YAAY,EAAE,MAAM,cAAc,CAAC;AAC7D,OAAO,EAAE,IAAI,EAAE,KAAK,OAAO,EAAE,MAAM,QAAQ,CAAC;AAC5C,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,eAAe,EAAE,KAAK,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,eAAe,EAAE,YAAY,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAGnG,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,QAAQ,EAAE,KAAK,EAAE,MAAM,OAAO,CAAC;AAGjE,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAGpD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAGtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,2BAA2B,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -35,6 +35,7 @@ import "./viz/Donut.js";
35
35
  import "./viz/index.js";
36
36
  import "./FileUploadField.js";
37
37
  import "./OralHistoryCard.js";
38
+ import "./SourceScanLink.js";
38
39
  import "./CitationList.js";
39
40
  import "./CreateStoryDialog.js";
40
41
  import "./CreateTopicDialog.js";
@@ -13,7 +13,9 @@
13
13
  * - Status StatTiles: Published / In Review / Draft / Idea stories, plus a
14
14
  * Discussions total and an Archive (artifacts) count.
15
15
  * - "Content pipeline" {@link BarChart} across the lifecycle stages.
16
- * - "Discussion health" {@link Donut} = share of discussions that are Approved.
16
+ * - "Discussion health" {@link Donut} = share of discussions that are Approved,
17
+ * beside the real per-status moderation tally (Approved / Pending / Flagged
18
+ * / Removed) read straight off `TcpDiscussion.status`.
17
19
  * - "Recent activity" — the latest stories + discussions, newest first.
18
20
  */
19
21
  export declare function GovernancePage(): import("react/jsx-runtime").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"GovernancePage.d.ts","sourceRoot":"","sources":["../../../../src/theculturepost/pages/governance/GovernancePage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAuDH,wBAAgB,cAAc,4CAgS7B"}
1
+ {"version":3,"file":"GovernancePage.d.ts","sourceRoot":"","sources":["../../../../src/theculturepost/pages/governance/GovernancePage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAiFH,wBAAgB,cAAc,4CAiT7B"}