@burdenoff/microfe-theculturepost 2026.802.1 → 2026.830.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.
- package/dist/theculturepost/components/CitationList.d.ts +2 -1
- package/dist/theculturepost/components/CitationList.d.ts.map +1 -1
- package/dist/theculturepost/components/CitationList.js +114 -95
- package/dist/theculturepost/components/CitationList.js.map +1 -1
- package/dist/theculturepost/components/SourceScanLink.d.ts +25 -0
- package/dist/theculturepost/components/SourceScanLink.d.ts.map +1 -0
- package/dist/theculturepost/components/SourceScanLink.js +36 -0
- package/dist/theculturepost/components/SourceScanLink.js.map +1 -0
- package/dist/theculturepost/components/index.d.ts +1 -0
- package/dist/theculturepost/components/index.d.ts.map +1 -1
- package/dist/theculturepost/components/index.js +1 -0
- package/dist/theculturepost/hooks/useTr.js +2 -2
- package/dist/theculturepost/hooks/useTr.js.map +1 -1
- package/dist/theculturepost/pages/HomePage.d.ts.map +1 -1
- package/dist/theculturepost/pages/HomePage.js +145 -127
- package/dist/theculturepost/pages/HomePage.js.map +1 -1
- package/dist/theculturepost/pages/governance/GovernancePage.d.ts +3 -1
- package/dist/theculturepost/pages/governance/GovernancePage.d.ts.map +1 -1
- package/dist/theculturepost/pages/governance/GovernancePage.js +122 -99
- package/dist/theculturepost/pages/governance/GovernancePage.js.map +1 -1
- package/dist/theculturepost/pages/stories/StoryDetailPage.d.ts +3 -0
- package/dist/theculturepost/pages/stories/StoryDetailPage.d.ts.map +1 -1
- package/dist/theculturepost/pages/stories/StoryDetailPage.js +202 -132
- package/dist/theculturepost/pages/stories/StoryDetailPage.js.map +1 -1
- package/dist/theculturepost/translations/ar.d.ts +43 -0
- package/dist/theculturepost/translations/ar.d.ts.map +1 -1
- package/dist/theculturepost/translations/ar.js +43 -0
- package/dist/theculturepost/translations/ar.js.map +1 -1
- package/dist/theculturepost/translations/en.d.ts +43 -0
- package/dist/theculturepost/translations/en.d.ts.map +1 -1
- package/dist/theculturepost/translations/en.js +43 -0
- package/dist/theculturepost/translations/en.js.map +1 -1
- package/dist/theculturepost/translations/es.d.ts +43 -0
- package/dist/theculturepost/translations/es.d.ts.map +1 -1
- package/dist/theculturepost/translations/es.js +43 -0
- package/dist/theculturepost/translations/es.js.map +1 -1
- package/dist/theculturepost/translations/hi.d.ts +43 -0
- package/dist/theculturepost/translations/hi.d.ts.map +1 -1
- package/dist/theculturepost/translations/hi.js +43 -0
- package/dist/theculturepost/translations/hi.js.map +1 -1
- package/dist/theculturepost/translations/ta.d.ts +43 -0
- package/dist/theculturepost/translations/ta.d.ts.map +1 -1
- package/dist/theculturepost/translations/ta.js +43 -0
- package/dist/theculturepost/translations/ta.js.map +1 -1
- package/dist/theculturepost/translations/zh.d.ts +43 -0
- package/dist/theculturepost/translations/zh.d.ts.map +1 -1
- package/dist/theculturepost/translations/zh.js +43 -0
- package/dist/theculturepost/translations/zh.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,108 +1,144 @@
|
|
|
1
1
|
import { useTheCulturePost as e } from "../../context/TheCulturePostContext.js";
|
|
2
2
|
import "../../context/index.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
3
|
+
import { cn as t } from "../../utils/cn.js";
|
|
4
|
+
import { PageHero as n } from "../../components/PageHero.js";
|
|
5
|
+
import { ErrorView as r, LoadingView as i, Section as a } from "../../components/StateViews.js";
|
|
6
|
+
import { formatRelativeTime as ee } from "../../utils/formatters.js";
|
|
6
7
|
import { StatusBadge as o } from "../../components/StatusBadge.js";
|
|
7
|
-
import { useTcpArchiveItems as
|
|
8
|
+
import { useTcpArchiveItems as te, useTcpCollections as s, useTcpDiscussions as c, useTcpStories as l } from "../../hooks/useTcpApi.js";
|
|
8
9
|
import "../../hooks/index.js";
|
|
9
|
-
import { EmptyState as
|
|
10
|
-
import { StatTile as
|
|
11
|
-
import { BarChart as
|
|
12
|
-
import { Donut as
|
|
10
|
+
import { EmptyState as u } from "../../components/EmptyState.js";
|
|
11
|
+
import { StatTile as d } from "../../components/viz/StatTile.js";
|
|
12
|
+
import { BarChart as f } from "../../components/viz/BarChart.js";
|
|
13
|
+
import { Donut as p } from "../../components/viz/Donut.js";
|
|
13
14
|
import "../../components/index.js";
|
|
14
|
-
import { useMemo as
|
|
15
|
-
import { Activity as
|
|
15
|
+
import { useMemo as m } from "react";
|
|
16
|
+
import { Activity as h, ArrowRight as g, BookOpen as _, CheckCircle2 as v, CircleSlash as y, Clock as b, Flag as x, Library as ne, Lightbulb as re, MessagesSquare as S, PencilLine as C, ShieldCheck as w } from "lucide-react";
|
|
16
17
|
import { Fragment as T, jsx as E, jsxs as D } from "react/jsx-runtime";
|
|
17
18
|
import { GlassCard as O } from "@burdenoff/fe-libs/ui";
|
|
18
19
|
//#region src/theculturepost/pages/governance/GovernancePage.tsx
|
|
19
20
|
var k = 100, A = {
|
|
20
|
-
story:
|
|
21
|
+
story: _,
|
|
21
22
|
discussion: S
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
}, j = {
|
|
24
|
+
APPROVED: {
|
|
25
|
+
label: "Approved",
|
|
26
|
+
icon: v,
|
|
27
|
+
iconClass: "text-accent"
|
|
28
|
+
},
|
|
29
|
+
PENDING: {
|
|
30
|
+
label: "Pending review",
|
|
31
|
+
icon: b,
|
|
32
|
+
iconClass: "text-text-tertiary"
|
|
33
|
+
},
|
|
34
|
+
FLAGGED: {
|
|
35
|
+
label: "Flagged",
|
|
36
|
+
icon: x,
|
|
37
|
+
iconClass: "text-status-warning-text"
|
|
38
|
+
},
|
|
39
|
+
REMOVED: {
|
|
40
|
+
label: "Removed",
|
|
41
|
+
icon: y,
|
|
42
|
+
iconClass: "text-status-error-text"
|
|
43
|
+
}
|
|
44
|
+
}, M = [
|
|
45
|
+
"APPROVED",
|
|
46
|
+
"PENDING",
|
|
47
|
+
"FLAGGED",
|
|
48
|
+
"REMOVED"
|
|
49
|
+
];
|
|
50
|
+
function N() {
|
|
51
|
+
let { navigateTo: v } = e(), y = l({
|
|
25
52
|
status: "IDEA",
|
|
26
53
|
pagination: { limit: k }
|
|
27
|
-
}),
|
|
54
|
+
}), x = l({
|
|
28
55
|
status: "DRAFT",
|
|
29
56
|
pagination: { limit: k }
|
|
30
|
-
}),
|
|
57
|
+
}), N = l({
|
|
31
58
|
status: "IN_REVIEW",
|
|
32
59
|
pagination: { limit: k }
|
|
33
|
-
}),
|
|
60
|
+
}), P = l({
|
|
34
61
|
status: "PUBLISHED",
|
|
35
62
|
pagination: { limit: k }
|
|
36
|
-
}),
|
|
63
|
+
}), F = c({ pagination: { limit: k } }), I = s({ limit: k }), L = te({ pagination: { limit: k } }), R = [
|
|
37
64
|
...[
|
|
38
|
-
|
|
65
|
+
y,
|
|
66
|
+
x,
|
|
39
67
|
N,
|
|
40
|
-
P
|
|
41
|
-
F
|
|
68
|
+
P
|
|
42
69
|
],
|
|
70
|
+
F,
|
|
43
71
|
I,
|
|
44
|
-
L
|
|
45
|
-
|
|
46
|
-
|
|
72
|
+
L
|
|
73
|
+
], z = m(() => y.data?.items ?? [], [y.data]), B = m(() => x.data?.items ?? [], [x.data]), V = m(() => N.data?.items ?? [], [N.data]), H = m(() => P.data?.items ?? [], [P.data]), U = m(() => F.data?.items ?? [], [F.data]), W = z.length, G = B.length, K = V.length, q = H.length, J = U.length, Y = m(() => {
|
|
74
|
+
let e = {
|
|
75
|
+
APPROVED: 0,
|
|
76
|
+
PENDING: 0,
|
|
77
|
+
FLAGGED: 0,
|
|
78
|
+
REMOVED: 0
|
|
79
|
+
};
|
|
80
|
+
for (let t of U) Object.prototype.hasOwnProperty.call(e, t.status) && (e[t.status] += 1);
|
|
81
|
+
return e;
|
|
82
|
+
}, [U]), X = Y.APPROVED, Z = J - X, Q = I.data?.length ?? 0, ie = L.data?.items.length ?? 0, ae = [
|
|
47
83
|
{
|
|
48
84
|
label: "Idea",
|
|
49
|
-
value:
|
|
85
|
+
value: W
|
|
50
86
|
},
|
|
51
87
|
{
|
|
52
88
|
label: "Draft",
|
|
53
|
-
value:
|
|
89
|
+
value: G
|
|
54
90
|
},
|
|
55
91
|
{
|
|
56
92
|
label: "In Review",
|
|
57
|
-
value:
|
|
93
|
+
value: K
|
|
58
94
|
},
|
|
59
95
|
{
|
|
60
96
|
label: "Published",
|
|
61
|
-
value:
|
|
97
|
+
value: q
|
|
62
98
|
}
|
|
63
|
-
], $ =
|
|
99
|
+
], $ = m(() => {
|
|
64
100
|
let e = [
|
|
101
|
+
...z,
|
|
65
102
|
...B,
|
|
66
103
|
...V,
|
|
67
|
-
...H
|
|
68
|
-
...U
|
|
104
|
+
...H
|
|
69
105
|
].map((e) => ({
|
|
70
106
|
id: `story:${e.id}`,
|
|
71
107
|
kind: "story",
|
|
72
108
|
title: e.title,
|
|
73
109
|
status: e.status,
|
|
74
110
|
createdAt: e.createdAt,
|
|
75
|
-
onOpen: () =>
|
|
76
|
-
})), t =
|
|
111
|
+
onOpen: () => v(`stories/${e.id}`)
|
|
112
|
+
})), t = U.map((e) => ({
|
|
77
113
|
id: `discussion:${e.id}`,
|
|
78
114
|
kind: "discussion",
|
|
79
115
|
title: e.title,
|
|
80
116
|
status: e.status,
|
|
81
117
|
createdAt: e.createdAt,
|
|
82
|
-
onOpen: () =>
|
|
118
|
+
onOpen: () => v(`discussions/${e.id}`)
|
|
83
119
|
}));
|
|
84
120
|
return [...e, ...t].sort((e, t) => new Date(t.createdAt).getTime() - new Date(e.createdAt).getTime()).slice(0, 8);
|
|
85
121
|
}, [
|
|
122
|
+
z,
|
|
86
123
|
B,
|
|
87
124
|
V,
|
|
88
125
|
H,
|
|
89
126
|
U,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
]), ne = z.some((e) => e.isLoading), re = z.some((e) => e.isError);
|
|
127
|
+
v
|
|
128
|
+
]), oe = R.some((e) => e.isLoading), se = R.some((e) => e.isError);
|
|
93
129
|
return /* @__PURE__ */ E("div", {
|
|
94
130
|
className: "h-full overflow-auto",
|
|
95
131
|
children: /* @__PURE__ */ D("div", {
|
|
96
132
|
className: "mx-auto flex max-w-6xl flex-col gap-6 p-4 sm:p-6",
|
|
97
|
-
children: [/* @__PURE__ */ E(
|
|
133
|
+
children: [/* @__PURE__ */ E(n, {
|
|
98
134
|
eyebrow: "Editorial",
|
|
99
135
|
title: "Governance & Editorial",
|
|
100
136
|
description: "Oversee content quality, discussions and community participation — at a glance."
|
|
101
|
-
}),
|
|
137
|
+
}), oe ? /* @__PURE__ */ E(i, { label: "Computing editorial metrics…" }) : se ? /* @__PURE__ */ E(r, {
|
|
102
138
|
title: "Couldn't load the overview",
|
|
103
139
|
message: "We couldn't compute the editorial metrics for this workspace.",
|
|
104
140
|
onRetry: () => {
|
|
105
|
-
|
|
141
|
+
R.forEach((e) => {
|
|
106
142
|
e.refetch();
|
|
107
143
|
});
|
|
108
144
|
}
|
|
@@ -114,40 +150,40 @@ function j() {
|
|
|
114
150
|
/* @__PURE__ */ D("div", {
|
|
115
151
|
className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
116
152
|
children: [
|
|
117
|
-
/* @__PURE__ */ E(
|
|
118
|
-
icon: /* @__PURE__ */ E(
|
|
153
|
+
/* @__PURE__ */ E(d, {
|
|
154
|
+
icon: /* @__PURE__ */ E(_, { className: "h-5 w-5" }),
|
|
119
155
|
label: "Published",
|
|
120
|
-
value:
|
|
156
|
+
value: q,
|
|
121
157
|
caption: "stories live"
|
|
122
158
|
}),
|
|
123
|
-
/* @__PURE__ */ E(
|
|
124
|
-
icon: /* @__PURE__ */ E(
|
|
159
|
+
/* @__PURE__ */ E(d, {
|
|
160
|
+
icon: /* @__PURE__ */ E(b, { className: "h-5 w-5" }),
|
|
125
161
|
label: "In Review",
|
|
126
|
-
value:
|
|
162
|
+
value: K,
|
|
127
163
|
caption: "awaiting editorial sign-off"
|
|
128
164
|
}),
|
|
129
|
-
/* @__PURE__ */ E(
|
|
165
|
+
/* @__PURE__ */ E(d, {
|
|
130
166
|
icon: /* @__PURE__ */ E(C, { className: "h-5 w-5" }),
|
|
131
167
|
label: "Draft",
|
|
132
|
-
value:
|
|
168
|
+
value: G,
|
|
133
169
|
caption: "in progress"
|
|
134
170
|
}),
|
|
135
|
-
/* @__PURE__ */ E(
|
|
136
|
-
icon: /* @__PURE__ */ E(
|
|
171
|
+
/* @__PURE__ */ E(d, {
|
|
172
|
+
icon: /* @__PURE__ */ E(re, { className: "h-5 w-5" }),
|
|
137
173
|
label: "Idea",
|
|
138
|
-
value:
|
|
174
|
+
value: W,
|
|
139
175
|
caption: "pitched, not yet drafted"
|
|
140
176
|
}),
|
|
141
|
-
/* @__PURE__ */ E(
|
|
177
|
+
/* @__PURE__ */ E(d, {
|
|
142
178
|
icon: /* @__PURE__ */ E(S, { className: "h-5 w-5" }),
|
|
143
179
|
label: "Discussions",
|
|
144
|
-
value:
|
|
180
|
+
value: J,
|
|
145
181
|
caption: "community conversations"
|
|
146
182
|
}),
|
|
147
|
-
/* @__PURE__ */ E(
|
|
148
|
-
icon: /* @__PURE__ */ E(
|
|
183
|
+
/* @__PURE__ */ E(d, {
|
|
184
|
+
icon: /* @__PURE__ */ E(ne, { className: "h-5 w-5" }),
|
|
149
185
|
label: "Archive",
|
|
150
|
-
value:
|
|
186
|
+
value: ie,
|
|
151
187
|
caption: `artifacts in ${Q} collection${Q === 1 ? "" : "s"}`
|
|
152
188
|
})
|
|
153
189
|
]
|
|
@@ -163,13 +199,13 @@ function j() {
|
|
|
163
199
|
}), /* @__PURE__ */ E("p", {
|
|
164
200
|
className: "mt-0.5 text-sm text-text-secondary",
|
|
165
201
|
children: "Stories across the editorial lifecycle."
|
|
166
|
-
})] }),
|
|
167
|
-
icon: /* @__PURE__ */ E(
|
|
202
|
+
})] }), q + K + G + W === 0 ? /* @__PURE__ */ E(u, {
|
|
203
|
+
icon: /* @__PURE__ */ E(_, { className: "h-9 w-9" }),
|
|
168
204
|
title: "No stories yet",
|
|
169
205
|
description: "Draft the first story to populate the pipeline."
|
|
170
|
-
}) : /* @__PURE__ */ E(
|
|
206
|
+
}) : /* @__PURE__ */ E(f, {
|
|
171
207
|
ariaLabel: "Stories by lifecycle stage",
|
|
172
|
-
data:
|
|
208
|
+
data: ae,
|
|
173
209
|
height: 180
|
|
174
210
|
})]
|
|
175
211
|
}), /* @__PURE__ */ D(O, {
|
|
@@ -181,64 +217,51 @@ function j() {
|
|
|
181
217
|
}), /* @__PURE__ */ E("p", {
|
|
182
218
|
className: "mt-0.5 text-sm text-text-secondary",
|
|
183
219
|
children: "Share of community discussions that are approved."
|
|
184
|
-
})] }),
|
|
220
|
+
})] }), J === 0 ? /* @__PURE__ */ E(u, {
|
|
185
221
|
icon: /* @__PURE__ */ E(S, { className: "h-9 w-9" }),
|
|
186
222
|
title: "No discussions yet",
|
|
187
223
|
description: "Open a discussion to start tracking conversation quality."
|
|
188
224
|
}) : /* @__PURE__ */ D("div", {
|
|
189
225
|
className: "flex flex-col items-center gap-5 sm:flex-row sm:items-center sm:gap-6",
|
|
190
|
-
children: [/* @__PURE__ */ E(
|
|
226
|
+
children: [/* @__PURE__ */ E(p, {
|
|
191
227
|
value: X,
|
|
192
|
-
total:
|
|
228
|
+
total: J,
|
|
193
229
|
caption: "approved",
|
|
194
230
|
size: 140,
|
|
195
|
-
ariaLabel: `${X} of ${
|
|
231
|
+
ariaLabel: `${X} of ${J} discussions approved`
|
|
196
232
|
}), /* @__PURE__ */ D("dl", {
|
|
197
|
-
className: "flex w-full flex-col gap-
|
|
198
|
-
children: [
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
children: [/* @__PURE__ */ D("dt", {
|
|
202
|
-
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
203
|
-
children: [/* @__PURE__ */ E("span", {
|
|
204
|
-
className: "h-2.5 w-2.5 rounded-full bg-accent",
|
|
205
|
-
"aria-hidden": "true"
|
|
206
|
-
}), "Approved"]
|
|
207
|
-
}), /* @__PURE__ */ E("dd", {
|
|
208
|
-
className: "font-display text-sm font-semibold tabular-nums text-text-primary",
|
|
209
|
-
children: X
|
|
210
|
-
})]
|
|
211
|
-
}),
|
|
212
|
-
/* @__PURE__ */ D("div", {
|
|
233
|
+
className: "flex w-full flex-col gap-2.5",
|
|
234
|
+
children: [M.map((e) => {
|
|
235
|
+
let n = j[e], r = n.icon;
|
|
236
|
+
return /* @__PURE__ */ D("div", {
|
|
213
237
|
className: "flex items-center justify-between gap-3",
|
|
214
238
|
children: [/* @__PURE__ */ D("dt", {
|
|
215
239
|
className: "flex items-center gap-2 text-sm text-text-secondary",
|
|
216
|
-
children: [/* @__PURE__ */ E(
|
|
217
|
-
className: "h-
|
|
240
|
+
children: [/* @__PURE__ */ E(r, {
|
|
241
|
+
className: t("h-4 w-4 shrink-0", n.iconClass),
|
|
218
242
|
"aria-hidden": "true"
|
|
219
|
-
}),
|
|
243
|
+
}), n.label]
|
|
220
244
|
}), /* @__PURE__ */ E("dd", {
|
|
221
245
|
className: "font-display text-sm font-semibold tabular-nums text-text-primary",
|
|
222
|
-
children:
|
|
246
|
+
children: Y[e]
|
|
223
247
|
})]
|
|
224
|
-
})
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
]
|
|
248
|
+
}, e);
|
|
249
|
+
}), /* @__PURE__ */ D("div", {
|
|
250
|
+
className: "mt-1 flex items-start gap-2 rounded-lg bg-bg-sunken/60 p-3 text-xs text-text-secondary",
|
|
251
|
+
children: [/* @__PURE__ */ E(w, {
|
|
252
|
+
className: "mt-0.5 h-4 w-4 shrink-0 text-accent",
|
|
253
|
+
"aria-hidden": "true"
|
|
254
|
+
}), /* @__PURE__ */ E("span", { children: Z === 0 ? "Every discussion in this workspace has been approved — nothing is waiting on a moderator." : Z === 1 ? "Approved discussions are published; one other needs a moderator’s attention." : `Approved discussions are published; ${Z} others need a moderator’s attention.` })]
|
|
255
|
+
})]
|
|
233
256
|
})]
|
|
234
257
|
})]
|
|
235
258
|
})]
|
|
236
259
|
}),
|
|
237
|
-
/* @__PURE__ */ E(
|
|
260
|
+
/* @__PURE__ */ E(a, {
|
|
238
261
|
title: "Recent activity",
|
|
239
262
|
description: "The latest stories and discussions across the workspace.",
|
|
240
|
-
children: $.length === 0 ? /* @__PURE__ */ E(
|
|
241
|
-
icon: /* @__PURE__ */ E(
|
|
263
|
+
children: $.length === 0 ? /* @__PURE__ */ E(u, {
|
|
264
|
+
icon: /* @__PURE__ */ E(h, { className: "h-9 w-9" }),
|
|
242
265
|
title: "Nothing yet",
|
|
243
266
|
description: "Draft a story or open a discussion — it will show up here."
|
|
244
267
|
}) : /* @__PURE__ */ E(O, {
|
|
@@ -266,7 +289,7 @@ function j() {
|
|
|
266
289
|
children: [
|
|
267
290
|
e.kind,
|
|
268
291
|
" · ",
|
|
269
|
-
|
|
292
|
+
ee(e.createdAt)
|
|
270
293
|
]
|
|
271
294
|
})]
|
|
272
295
|
}),
|
|
@@ -274,7 +297,7 @@ function j() {
|
|
|
274
297
|
status: e.status,
|
|
275
298
|
className: "hidden shrink-0 sm:inline-flex"
|
|
276
299
|
}),
|
|
277
|
-
/* @__PURE__ */ E(
|
|
300
|
+
/* @__PURE__ */ E(g, {
|
|
278
301
|
className: "h-4 w-4 shrink-0 text-text-tertiary",
|
|
279
302
|
"aria-hidden": "true"
|
|
280
303
|
})
|
|
@@ -288,6 +311,6 @@ function j() {
|
|
|
288
311
|
});
|
|
289
312
|
}
|
|
290
313
|
//#endregion
|
|
291
|
-
export {
|
|
314
|
+
export { N as GovernancePage };
|
|
292
315
|
|
|
293
316
|
//# sourceMappingURL=GovernancePage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GovernancePage.js","names":[],"sources":["../../../../src/theculturepost/pages/governance/GovernancePage.tsx"],"sourcesContent":["/**\n * GovernancePage — the editorial / governance overview for TheCulturePost.\n * @module theculturepost/pages/governance\n *\n * A data-honest dashboard computed entirely from real counts in the current\n * workspace. There is NO historical series in the contract, so this surface\n * carries NO trend deltas and NO time-series line chart (unlike the concept\n * mockup). It also DROPS the \"Contributors & Roles\" table — actors are id-only\n * federation stubs and roles are platform RBAC, neither of which this subgraph\n * exposes.\n *\n * What it renders, all from live `tcp*` reads:\n * - Status StatTiles: Published / In Review / Draft / Idea stories, plus a\n * Discussions total and an Archive (artifacts) count.\n * - \"Content pipeline\" {@link BarChart} across the lifecycle stages.\n * - \"Discussion health\" {@link Donut} = share of discussions that are Approved.\n * - \"Recent activity\" — the latest stories + discussions, newest first.\n */\n\nimport { useMemo } from 'react';\nimport {\n BookOpen,\n Clock,\n PencilLine,\n Lightbulb,\n MessagesSquare,\n Library,\n Activity,\n ShieldCheck,\n ArrowRight,\n} from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageHero,\n Section,\n LoadingView,\n ErrorView,\n EmptyState,\n StatTile,\n BarChart,\n Donut,\n StatusBadge,\n type BarDatum,\n} from '../../components';\nimport {\n useTcpStories,\n useTcpDiscussions,\n useTcpCollections,\n useTcpArchiveItems,\n} from '../../hooks';\nimport { useTheCulturePost } from '../../context';\nimport { formatRelativeTime } from '../../utils/formatters';\n\nconst COUNT_LIMIT = 100;\n\ntype ActivityKind = 'story' | 'discussion';\n\ninterface ActivityRow {\n id: string;\n kind: ActivityKind;\n title: string;\n status: string;\n createdAt: string;\n onOpen: () => void;\n}\n\nconst ACTIVITY_ICON: Record<ActivityKind, LucideIcon> = {\n story: BookOpen,\n discussion: MessagesSquare,\n};\n\nexport function GovernancePage() {\n const { navigateTo } = useTheCulturePost();\n\n // One query per lifecycle stage — each returns that stage's stories, so the\n // count is `items.length` (up to COUNT_LIMIT). No cross-stage query needed.\n const ideaQuery = useTcpStories({ status: 'IDEA', pagination: { limit: COUNT_LIMIT } });\n const draftQuery = useTcpStories({ status: 'DRAFT', pagination: { limit: COUNT_LIMIT } });\n const reviewQuery = useTcpStories({ status: 'IN_REVIEW', pagination: { limit: COUNT_LIMIT } });\n const publishedQuery = useTcpStories({\n status: 'PUBLISHED',\n pagination: { limit: COUNT_LIMIT },\n });\n const discussionsQuery = useTcpDiscussions({ pagination: { limit: COUNT_LIMIT } });\n const collectionsQuery = useTcpCollections({ limit: COUNT_LIMIT });\n const artifactsQuery = useTcpArchiveItems({ pagination: { limit: COUNT_LIMIT } });\n\n const storyQueries = [ideaQuery, draftQuery, reviewQuery, publishedQuery];\n const allQueries = [...storyQueries, discussionsQuery, collectionsQuery, artifactsQuery];\n\n const ideaStories = useMemo(() => ideaQuery.data?.items ?? [], [ideaQuery.data]);\n const draftStories = useMemo(() => draftQuery.data?.items ?? [], [draftQuery.data]);\n const reviewStories = useMemo(() => reviewQuery.data?.items ?? [], [reviewQuery.data]);\n const publishedStories = useMemo(() => publishedQuery.data?.items ?? [], [publishedQuery.data]);\n const discussions = useMemo(() => discussionsQuery.data?.items ?? [], [discussionsQuery.data]);\n\n const ideaCount = ideaStories.length;\n const draftCount = draftStories.length;\n const reviewCount = reviewStories.length;\n const publishedCount = publishedStories.length;\n const discussionTotal = discussions.length;\n const approvedCount = discussions.filter((d) => d.status === 'APPROVED').length;\n const needsAttentionCount = discussionTotal - approvedCount;\n const collectionsCount = collectionsQuery.data?.length ?? 0;\n const artifactsCount = artifactsQuery.data?.items.length ?? 0;\n\n const pipelineData: BarDatum[] = [\n { label: 'Idea', value: ideaCount },\n { label: 'Draft', value: draftCount },\n { label: 'In Review', value: reviewCount },\n { label: 'Published', value: publishedCount },\n ];\n\n const recentActivity = useMemo<ActivityRow[]>(() => {\n const storyRows: ActivityRow[] = [\n ...ideaStories,\n ...draftStories,\n ...reviewStories,\n ...publishedStories,\n ].map((story) => ({\n id: `story:${story.id}`,\n kind: 'story' as const,\n title: story.title,\n status: story.status,\n createdAt: story.createdAt,\n onOpen: () => navigateTo(`stories/${story.id}`),\n }));\n\n const discussionRows: ActivityRow[] = discussions.map((discussion) => ({\n id: `discussion:${discussion.id}`,\n kind: 'discussion' as const,\n title: discussion.title,\n status: discussion.status,\n createdAt: discussion.createdAt,\n onOpen: () => navigateTo(`discussions/${discussion.id}`),\n }));\n\n return [...storyRows, ...discussionRows]\n .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())\n .slice(0, 8);\n }, [ideaStories, draftStories, reviewStories, publishedStories, discussions, navigateTo]);\n\n const isLoading = allQueries.some((q) => q.isLoading);\n const isError = allQueries.some((q) => q.isError);\n\n const retryAll = () => {\n allQueries.forEach((q) => {\n void q.refetch();\n });\n };\n\n return (\n <div className=\"h-full overflow-auto\">\n <div className=\"mx-auto flex max-w-6xl flex-col gap-6 p-4 sm:p-6\">\n <PageHero\n eyebrow=\"Editorial\"\n title=\"Governance & Editorial\"\n description=\"Oversee content quality, discussions and community participation — at a glance.\"\n />\n\n {isLoading ? (\n <LoadingView label=\"Computing editorial metrics…\" />\n ) : isError ? (\n <ErrorView\n title=\"Couldn't load the overview\"\n message=\"We couldn't compute the editorial metrics for this workspace.\"\n onRetry={retryAll}\n />\n ) : (\n <>\n <p className=\"-mt-2 text-xs text-text-tertiary\">\n Counts are computed live from the current workspace.\n </p>\n\n {/* Status metrics */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <StatTile\n icon={<BookOpen className=\"h-5 w-5\" />}\n label=\"Published\"\n value={publishedCount}\n caption=\"stories live\"\n />\n <StatTile\n icon={<Clock className=\"h-5 w-5\" />}\n label=\"In Review\"\n value={reviewCount}\n caption=\"awaiting editorial sign-off\"\n />\n <StatTile\n icon={<PencilLine className=\"h-5 w-5\" />}\n label=\"Draft\"\n value={draftCount}\n caption=\"in progress\"\n />\n <StatTile\n icon={<Lightbulb className=\"h-5 w-5\" />}\n label=\"Idea\"\n value={ideaCount}\n caption=\"pitched, not yet drafted\"\n />\n <StatTile\n icon={<MessagesSquare className=\"h-5 w-5\" />}\n label=\"Discussions\"\n value={discussionTotal}\n caption=\"community conversations\"\n />\n <StatTile\n icon={<Library className=\"h-5 w-5\" />}\n label=\"Archive\"\n value={artifactsCount}\n caption={`artifacts in ${collectionsCount} collection${\n collectionsCount === 1 ? '' : 's'\n }`}\n />\n </div>\n\n {/* Pipeline + discussion health */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <GlassCard treatment=\"plain\" className=\"flex flex-col gap-4 p-5 sm:p-6\">\n <div>\n <h2 className=\"font-display text-lg font-semibold text-text-primary\">\n Content pipeline\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Stories across the editorial lifecycle.\n </p>\n </div>\n {publishedCount + reviewCount + draftCount + ideaCount === 0 ? (\n <EmptyState\n icon={<BookOpen className=\"h-9 w-9\" />}\n title=\"No stories yet\"\n description=\"Draft the first story to populate the pipeline.\"\n />\n ) : (\n <BarChart\n ariaLabel=\"Stories by lifecycle stage\"\n data={pipelineData}\n height={180}\n />\n )}\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"flex flex-col gap-4 p-5 sm:p-6\">\n <div>\n <h2 className=\"font-display text-lg font-semibold text-text-primary\">\n Discussion health\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Share of community discussions that are approved.\n </p>\n </div>\n {discussionTotal === 0 ? (\n <EmptyState\n icon={<MessagesSquare className=\"h-9 w-9\" />}\n title=\"No discussions yet\"\n description=\"Open a discussion to start tracking conversation quality.\"\n />\n ) : (\n <div className=\"flex flex-col items-center gap-5 sm:flex-row sm:items-center sm:gap-6\">\n <Donut\n value={approvedCount}\n total={discussionTotal}\n caption=\"approved\"\n size={140}\n ariaLabel={`${approvedCount} of ${discussionTotal} discussions approved`}\n />\n <dl className=\"flex w-full flex-col gap-3\">\n <div className=\"flex items-center justify-between gap-3\">\n <dt className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span className=\"h-2.5 w-2.5 rounded-full bg-accent\" aria-hidden=\"true\" />\n Approved\n </dt>\n <dd className=\"font-display text-sm font-semibold tabular-nums text-text-primary\">\n {approvedCount}\n </dd>\n </div>\n <div className=\"flex items-center justify-between gap-3\">\n <dt className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <span\n className=\"h-2.5 w-2.5 rounded-full bg-bg-sunken\"\n aria-hidden=\"true\"\n />\n Needs attention\n </dt>\n <dd className=\"font-display text-sm font-semibold tabular-nums text-text-primary\">\n {needsAttentionCount}\n </dd>\n </div>\n <div className=\"mt-1 flex items-start gap-2 rounded-lg bg-bg-sunken/60 p-3 text-xs text-text-secondary\">\n <ShieldCheck\n className=\"mt-0.5 h-4 w-4 shrink-0 text-accent\"\n aria-hidden=\"true\"\n />\n <span>\n Approved replies are published; pending, flagged and removed discussions\n need a moderator’s attention.\n </span>\n </div>\n </dl>\n </div>\n )}\n </GlassCard>\n </div>\n\n {/* Recent activity */}\n <Section\n title=\"Recent activity\"\n description=\"The latest stories and discussions across the workspace.\"\n >\n {recentActivity.length === 0 ? (\n <EmptyState\n icon={<Activity className=\"h-9 w-9\" />}\n title=\"Nothing yet\"\n description=\"Draft a story or open a discussion — it will show up here.\"\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"divide-y divide-border-seam\">\n {recentActivity.map((row) => {\n const Icon = ACTIVITY_ICON[row.kind];\n return (\n <button\n key={row.id}\n type=\"button\"\n onClick={row.onOpen}\n className=\"flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-bg-sunken/60 focus-visible:bg-bg-sunken/60 focus-visible:outline-none\"\n >\n <span\n className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\"\n aria-hidden=\"true\"\n >\n <Icon className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {row.title}\n </p>\n <p className=\"mt-0.5 text-xs capitalize text-text-tertiary\">\n {row.kind} · {formatRelativeTime(row.createdAt)}\n </p>\n </div>\n <StatusBadge\n status={row.status}\n className=\"hidden shrink-0 sm:inline-flex\"\n />\n <ArrowRight\n className=\"h-4 w-4 shrink-0 text-text-tertiary\"\n aria-hidden=\"true\"\n />\n </button>\n );\n })}\n </GlassCard>\n )}\n </Section>\n </>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAsDA,IAAM,IAAc,KAad,IAAkD;CACtD,OAAO;CACP,YAAY;CACb;AAED,SAAgB,IAAiB;CAC/B,IAAM,EAAE,kBAAe,GAAmB,EAIpC,IAAY,EAAc;EAAE,QAAQ;EAAQ,YAAY,EAAE,OAAO,GAAa;EAAE,CAAC,EACjF,IAAa,EAAc;EAAE,QAAQ;EAAS,YAAY,EAAE,OAAO,GAAa;EAAE,CAAC,EACnF,IAAc,EAAc;EAAE,QAAQ;EAAa,YAAY,EAAE,OAAO,GAAa;EAAE,CAAC,EACxF,IAAiB,EAAc;EACnC,QAAQ;EACR,YAAY,EAAE,OAAO,GAAa;EACnC,CAAC,EACI,IAAmB,EAAkB,EAAE,YAAY,EAAE,OAAO,GAAa,EAAE,CAAC,EAC5E,IAAmB,EAAkB,EAAE,OAAO,GAAa,CAAC,EAC5D,IAAiB,EAAmB,EAAE,YAAY,EAAE,OAAO,GAAa,EAAE,CAAC,EAG3E,IAAa;EAAC,GADC;GAAC;GAAW;GAAY;GAAa;GAAe;EACpC;EAAkB;EAAkB;EAAe,EAElF,IAAc,QAAc,EAAU,MAAM,SAAS,EAAE,EAAE,CAAC,EAAU,KAAK,CAAC,EAC1E,IAAe,QAAc,EAAW,MAAM,SAAS,EAAE,EAAE,CAAC,EAAW,KAAK,CAAC,EAC7E,IAAgB,QAAc,EAAY,MAAM,SAAS,EAAE,EAAE,CAAC,EAAY,KAAK,CAAC,EAChF,IAAmB,QAAc,EAAe,MAAM,SAAS,EAAE,EAAE,CAAC,EAAe,KAAK,CAAC,EACzF,IAAc,QAAc,EAAiB,MAAM,SAAS,EAAE,EAAE,CAAC,EAAiB,KAAK,CAAC,EAExF,IAAY,EAAY,QACxB,IAAa,EAAa,QAC1B,IAAc,EAAc,QAC5B,IAAiB,EAAiB,QAClC,IAAkB,EAAY,QAC9B,IAAgB,EAAY,QAAQ,MAAM,EAAE,WAAW,WAAW,CAAC,QACnE,IAAsB,IAAkB,GACxC,IAAmB,EAAiB,MAAM,UAAU,GACpD,KAAiB,EAAe,MAAM,MAAM,UAAU,GAEtD,KAA2B;EAC/B;GAAE,OAAO;GAAQ,OAAO;GAAW;EACnC;GAAE,OAAO;GAAS,OAAO;GAAY;EACrC;GAAE,OAAO;GAAa,OAAO;GAAa;EAC1C;GAAE,OAAO;GAAa,OAAO;GAAgB;EAC9C,EAEK,IAAiB,QAA6B;EAClD,IAAM,IAA2B;GAC/B,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;GACJ,CAAC,KAAK,OAAW;GAChB,IAAI,SAAS,EAAM;GACnB,MAAM;GACN,OAAO,EAAM;GACb,QAAQ,EAAM;GACd,WAAW,EAAM;GACjB,cAAc,EAAW,WAAW,EAAM,KAAK;GAChD,EAAE,EAEG,IAAgC,EAAY,KAAK,OAAgB;GACrE,IAAI,cAAc,EAAW;GAC7B,MAAM;GACN,OAAO,EAAW;GAClB,QAAQ,EAAW;GACnB,WAAW,EAAW;GACtB,cAAc,EAAW,eAAe,EAAW,KAAK;GACzD,EAAE;AAEH,SAAO,CAAC,GAAG,GAAW,GAAG,EAAe,CACrC,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,CACjF,MAAM,GAAG,EAAE;IACb;EAAC;EAAa;EAAc;EAAe;EAAkB;EAAa;EAAW,CAAC,EAEnF,KAAY,EAAW,MAAM,MAAM,EAAE,UAAU,EAC/C,KAAU,EAAW,MAAM,MAAM,EAAE,QAAQ;AAQjD,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IACE,SAAQ;IACR,OAAM;IACN,aAAY;IACZ,CAAA,EAED,KACC,kBAAC,GAAD,EAAa,OAAM,gCAAiC,CAAA,GAClD,KACF,kBAAC,GAAD;IACE,OAAM;IACN,SAAQ;IACR,eArBa;AACrB,OAAW,SAAS,MAAM;AACnB,QAAE,SAAS;OAChB;;IAmBM,CAAA,GAEF,kBAAA,GAAA,EAAA,UAAA;IACE,kBAAC,KAAD;KAAG,WAAU;eAAmC;KAE5C,CAAA;IAGJ,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;OACtC,OAAM;OACN,OAAO;OACP,SAAQ;OACR,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;OACnC,OAAM;OACN,OAAO;OACP,SAAQ;OACR,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;OACxC,OAAM;OACN,OAAO;OACP,SAAQ;OACR,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;OACvC,OAAM;OACN,OAAO;OACP,SAAQ;OACR,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAgB,WAAU,WAAY,CAAA;OAC5C,OAAM;OACN,OAAO;OACP,SAAQ;OACR,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAS,WAAU,WAAY,CAAA;OACrC,OAAM;OACN,OAAO;OACP,SAAS,gBAAgB,EAAiB,aACxC,MAAqB,IAAI,KAAK;OAEhC,CAAA;MACE;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAuD;OAEhE,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBAAqC;OAE9C,CAAA,CACA,EAAA,CAAA,EACL,IAAiB,IAAc,IAAa,MAAc,IACzD,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;OACtC,OAAM;OACN,aAAY;OACZ,CAAA,GAEF,kBAAC,GAAD;OACE,WAAU;OACV,MAAM;OACN,QAAQ;OACR,CAAA,CAEM;SAEZ,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAuD;OAEhE,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBAAqC;OAE9C,CAAA,CACA,EAAA,CAAA,EACL,MAAoB,IACnB,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAgB,WAAU,WAAY,CAAA;OAC5C,OAAM;OACN,aAAY;OACZ,CAAA,GAEF,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,OAAO;QACP,OAAO;QACP,SAAQ;QACR,MAAM;QACN,WAAW,GAAG,EAAc,MAAM,EAAgB;QAClD,CAAA,EACF,kBAAC,MAAD;QAAI,WAAU;kBAAd;SACE,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBAAd,CACE,kBAAC,QAAD;YAAM,WAAU;YAAqC,eAAY;YAAS,CAAA,EAAA,WAEvE;cACL,kBAAC,MAAD;WAAI,WAAU;qBACX;WACE,CAAA,CACD;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,MAAD;WAAI,WAAU;qBAAd,CACE,kBAAC,QAAD;YACE,WAAU;YACV,eAAY;YACZ,CAAA,EAAA,kBAEC;cACL,kBAAC,MAAD;WAAI,WAAU;qBACX;WACE,CAAA,CACD;;SACN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,GAAD;WACE,WAAU;WACV,eAAY;WACZ,CAAA,EACF,kBAAC,QAAD,EAAA,UAAM,0GAGC,CAAA,CACH;;SACH;UACD;SAEE;QACR;;IAGN,kBAAC,GAAD;KACE,OAAM;KACN,aAAY;eAEX,EAAe,WAAW,IACzB,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;MACtC,OAAM;MACN,aAAY;MACZ,CAAA,GAEF,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBACpC,EAAe,KAAK,MAAQ;OAC3B,IAAM,IAAO,EAAc,EAAI;AAC/B,cACE,kBAAC,UAAD;QAEE,MAAK;QACL,SAAS,EAAI;QACb,WAAU;kBAJZ;SAME,kBAAC,QAAD;UACE,WAAU;UACV,eAAY;oBAEZ,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;UACvB,CAAA;SACP,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAI;WACH,CAAA,EACJ,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAI;YAAK;YAAI,EAAmB,EAAI,UAAU;YAC7C;aACA;;SACN,kBAAC,GAAD;UACE,QAAQ,EAAI;UACZ,WAAU;UACV,CAAA;SACF,kBAAC,GAAD;UACE,WAAU;UACV,eAAY;UACZ,CAAA;SACK;UA3BF,EAAI,GA2BF;QAEX;MACQ,CAAA;KAEN,CAAA;IACT,EAAA,CAAA,CAED;;EACF,CAAA"}
|
|
1
|
+
{"version":3,"file":"GovernancePage.js","names":[],"sources":["../../../../src/theculturepost/pages/governance/GovernancePage.tsx"],"sourcesContent":["/**\n * GovernancePage — the editorial / governance overview for TheCulturePost.\n * @module theculturepost/pages/governance\n *\n * A data-honest dashboard computed entirely from real counts in the current\n * workspace. There is NO historical series in the contract, so this surface\n * carries NO trend deltas and NO time-series line chart (unlike the concept\n * mockup). It also DROPS the \"Contributors & Roles\" table — actors are id-only\n * federation stubs and roles are platform RBAC, neither of which this subgraph\n * exposes.\n *\n * What it renders, all from live `tcp*` reads:\n * - Status StatTiles: Published / In Review / Draft / Idea stories, plus a\n * Discussions total and an Archive (artifacts) count.\n * - \"Content pipeline\" {@link BarChart} across the lifecycle stages.\n * - \"Discussion health\" {@link Donut} = share of discussions that are Approved,\n * beside the real per-status moderation tally (Approved / Pending / Flagged\n * / Removed) read straight off `TcpDiscussion.status`.\n * - \"Recent activity\" — the latest stories + discussions, newest first.\n */\n\nimport { useMemo } from 'react';\nimport {\n BookOpen,\n Clock,\n PencilLine,\n Lightbulb,\n MessagesSquare,\n Library,\n Activity,\n ShieldCheck,\n ArrowRight,\n CheckCircle2,\n Flag,\n CircleSlash,\n} from 'lucide-react';\nimport type { LucideIcon } from 'lucide-react';\nimport { GlassCard } from '@burdenoff/fe-libs/ui';\nimport {\n PageHero,\n Section,\n LoadingView,\n ErrorView,\n EmptyState,\n StatTile,\n BarChart,\n Donut,\n StatusBadge,\n type BarDatum,\n} from '../../components';\nimport {\n useTcpStories,\n useTcpDiscussions,\n useTcpCollections,\n useTcpArchiveItems,\n} from '../../hooks';\nimport { useTheCulturePost } from '../../context';\nimport { formatRelativeTime } from '../../utils/formatters';\nimport { cn } from '../../utils/cn';\nimport type { TcpModerationStatus } from '../../types';\n\nconst COUNT_LIMIT = 100;\n\ntype ActivityKind = 'story' | 'discussion';\n\ninterface ActivityRow {\n id: string;\n kind: ActivityKind;\n title: string;\n status: string;\n createdAt: string;\n onOpen: () => void;\n}\n\nconst ACTIVITY_ICON: Record<ActivityKind, LucideIcon> = {\n story: BookOpen,\n discussion: MessagesSquare,\n};\n\ninterface ModerationMetaEntry {\n label: string;\n icon: LucideIcon;\n /** Semantic-token colour for the leading icon. */\n iconClass: string;\n}\n\n/**\n * The four real `TcpModerationStatus` values, in reading order. Everything that\n * is not APPROVED needs a moderator, but they need different things — so the\n * panel names each one instead of collapsing them into one opaque number.\n */\nconst MODERATION_META: Record<TcpModerationStatus, ModerationMetaEntry> = {\n APPROVED: { label: 'Approved', icon: CheckCircle2, iconClass: 'text-accent' },\n PENDING: { label: 'Pending review', icon: Clock, iconClass: 'text-text-tertiary' },\n FLAGGED: { label: 'Flagged', icon: Flag, iconClass: 'text-status-warning-text' },\n REMOVED: { label: 'Removed', icon: CircleSlash, iconClass: 'text-status-error-text' },\n};\n\nconst MODERATION_ORDER: TcpModerationStatus[] = ['APPROVED', 'PENDING', 'FLAGGED', 'REMOVED'];\n\nexport function GovernancePage() {\n const { navigateTo } = useTheCulturePost();\n\n // One query per lifecycle stage — each returns that stage's stories, so the\n // count is `items.length` (up to COUNT_LIMIT). No cross-stage query needed.\n const ideaQuery = useTcpStories({ status: 'IDEA', pagination: { limit: COUNT_LIMIT } });\n const draftQuery = useTcpStories({ status: 'DRAFT', pagination: { limit: COUNT_LIMIT } });\n const reviewQuery = useTcpStories({ status: 'IN_REVIEW', pagination: { limit: COUNT_LIMIT } });\n const publishedQuery = useTcpStories({\n status: 'PUBLISHED',\n pagination: { limit: COUNT_LIMIT },\n });\n const discussionsQuery = useTcpDiscussions({ pagination: { limit: COUNT_LIMIT } });\n const collectionsQuery = useTcpCollections({ limit: COUNT_LIMIT });\n const artifactsQuery = useTcpArchiveItems({ pagination: { limit: COUNT_LIMIT } });\n\n const storyQueries = [ideaQuery, draftQuery, reviewQuery, publishedQuery];\n const allQueries = [...storyQueries, discussionsQuery, collectionsQuery, artifactsQuery];\n\n const ideaStories = useMemo(() => ideaQuery.data?.items ?? [], [ideaQuery.data]);\n const draftStories = useMemo(() => draftQuery.data?.items ?? [], [draftQuery.data]);\n const reviewStories = useMemo(() => reviewQuery.data?.items ?? [], [reviewQuery.data]);\n const publishedStories = useMemo(() => publishedQuery.data?.items ?? [], [publishedQuery.data]);\n const discussions = useMemo(() => discussionsQuery.data?.items ?? [], [discussionsQuery.data]);\n\n const ideaCount = ideaStories.length;\n const draftCount = draftStories.length;\n const reviewCount = reviewStories.length;\n const publishedCount = publishedStories.length;\n const discussionTotal = discussions.length;\n\n // One pass over the discussions already in hand — no extra query.\n const moderationCounts = useMemo(() => {\n const counts: Record<TcpModerationStatus, number> = {\n APPROVED: 0,\n PENDING: 0,\n FLAGGED: 0,\n REMOVED: 0,\n };\n for (const discussion of discussions) {\n if (Object.prototype.hasOwnProperty.call(counts, discussion.status)) {\n counts[discussion.status] += 1;\n }\n }\n return counts;\n }, [discussions]);\n\n const approvedCount = moderationCounts.APPROVED;\n const needsAttentionCount = discussionTotal - approvedCount;\n const collectionsCount = collectionsQuery.data?.length ?? 0;\n const artifactsCount = artifactsQuery.data?.items.length ?? 0;\n\n const pipelineData: BarDatum[] = [\n { label: 'Idea', value: ideaCount },\n { label: 'Draft', value: draftCount },\n { label: 'In Review', value: reviewCount },\n { label: 'Published', value: publishedCount },\n ];\n\n const recentActivity = useMemo<ActivityRow[]>(() => {\n const storyRows: ActivityRow[] = [\n ...ideaStories,\n ...draftStories,\n ...reviewStories,\n ...publishedStories,\n ].map((story) => ({\n id: `story:${story.id}`,\n kind: 'story' as const,\n title: story.title,\n status: story.status,\n createdAt: story.createdAt,\n onOpen: () => navigateTo(`stories/${story.id}`),\n }));\n\n const discussionRows: ActivityRow[] = discussions.map((discussion) => ({\n id: `discussion:${discussion.id}`,\n kind: 'discussion' as const,\n title: discussion.title,\n status: discussion.status,\n createdAt: discussion.createdAt,\n onOpen: () => navigateTo(`discussions/${discussion.id}`),\n }));\n\n return [...storyRows, ...discussionRows]\n .sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime())\n .slice(0, 8);\n }, [ideaStories, draftStories, reviewStories, publishedStories, discussions, navigateTo]);\n\n const isLoading = allQueries.some((q) => q.isLoading);\n const isError = allQueries.some((q) => q.isError);\n\n const retryAll = () => {\n allQueries.forEach((q) => {\n void q.refetch();\n });\n };\n\n return (\n <div className=\"h-full overflow-auto\">\n <div className=\"mx-auto flex max-w-6xl flex-col gap-6 p-4 sm:p-6\">\n <PageHero\n eyebrow=\"Editorial\"\n title=\"Governance & Editorial\"\n description=\"Oversee content quality, discussions and community participation — at a glance.\"\n />\n\n {isLoading ? (\n <LoadingView label=\"Computing editorial metrics…\" />\n ) : isError ? (\n <ErrorView\n title=\"Couldn't load the overview\"\n message=\"We couldn't compute the editorial metrics for this workspace.\"\n onRetry={retryAll}\n />\n ) : (\n <>\n <p className=\"-mt-2 text-xs text-text-tertiary\">\n Counts are computed live from the current workspace.\n </p>\n\n {/* Status metrics */}\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n <StatTile\n icon={<BookOpen className=\"h-5 w-5\" />}\n label=\"Published\"\n value={publishedCount}\n caption=\"stories live\"\n />\n <StatTile\n icon={<Clock className=\"h-5 w-5\" />}\n label=\"In Review\"\n value={reviewCount}\n caption=\"awaiting editorial sign-off\"\n />\n <StatTile\n icon={<PencilLine className=\"h-5 w-5\" />}\n label=\"Draft\"\n value={draftCount}\n caption=\"in progress\"\n />\n <StatTile\n icon={<Lightbulb className=\"h-5 w-5\" />}\n label=\"Idea\"\n value={ideaCount}\n caption=\"pitched, not yet drafted\"\n />\n <StatTile\n icon={<MessagesSquare className=\"h-5 w-5\" />}\n label=\"Discussions\"\n value={discussionTotal}\n caption=\"community conversations\"\n />\n <StatTile\n icon={<Library className=\"h-5 w-5\" />}\n label=\"Archive\"\n value={artifactsCount}\n caption={`artifacts in ${collectionsCount} collection${\n collectionsCount === 1 ? '' : 's'\n }`}\n />\n </div>\n\n {/* Pipeline + discussion health */}\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n <GlassCard treatment=\"plain\" className=\"flex flex-col gap-4 p-5 sm:p-6\">\n <div>\n <h2 className=\"font-display text-lg font-semibold text-text-primary\">\n Content pipeline\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Stories across the editorial lifecycle.\n </p>\n </div>\n {publishedCount + reviewCount + draftCount + ideaCount === 0 ? (\n <EmptyState\n icon={<BookOpen className=\"h-9 w-9\" />}\n title=\"No stories yet\"\n description=\"Draft the first story to populate the pipeline.\"\n />\n ) : (\n <BarChart\n ariaLabel=\"Stories by lifecycle stage\"\n data={pipelineData}\n height={180}\n />\n )}\n </GlassCard>\n\n <GlassCard treatment=\"plain\" className=\"flex flex-col gap-4 p-5 sm:p-6\">\n <div>\n <h2 className=\"font-display text-lg font-semibold text-text-primary\">\n Discussion health\n </h2>\n <p className=\"mt-0.5 text-sm text-text-secondary\">\n Share of community discussions that are approved.\n </p>\n </div>\n {discussionTotal === 0 ? (\n <EmptyState\n icon={<MessagesSquare className=\"h-9 w-9\" />}\n title=\"No discussions yet\"\n description=\"Open a discussion to start tracking conversation quality.\"\n />\n ) : (\n <div className=\"flex flex-col items-center gap-5 sm:flex-row sm:items-center sm:gap-6\">\n <Donut\n value={approvedCount}\n total={discussionTotal}\n caption=\"approved\"\n size={140}\n ariaLabel={`${approvedCount} of ${discussionTotal} discussions approved`}\n />\n <dl className=\"flex w-full flex-col gap-2.5\">\n {MODERATION_ORDER.map((status) => {\n const meta = MODERATION_META[status];\n const Icon = meta.icon;\n return (\n <div key={status} className=\"flex items-center justify-between gap-3\">\n <dt className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Icon\n className={cn('h-4 w-4 shrink-0', meta.iconClass)}\n aria-hidden=\"true\"\n />\n {meta.label}\n </dt>\n <dd className=\"font-display text-sm font-semibold tabular-nums text-text-primary\">\n {moderationCounts[status]}\n </dd>\n </div>\n );\n })}\n <div className=\"mt-1 flex items-start gap-2 rounded-lg bg-bg-sunken/60 p-3 text-xs text-text-secondary\">\n <ShieldCheck\n className=\"mt-0.5 h-4 w-4 shrink-0 text-accent\"\n aria-hidden=\"true\"\n />\n <span>\n {needsAttentionCount === 0\n ? 'Every discussion in this workspace has been approved — nothing is waiting on a moderator.'\n : needsAttentionCount === 1\n ? 'Approved discussions are published; one other needs a moderator’s attention.'\n : `Approved discussions are published; ${needsAttentionCount} others need a moderator’s attention.`}\n </span>\n </div>\n </dl>\n </div>\n )}\n </GlassCard>\n </div>\n\n {/* Recent activity */}\n <Section\n title=\"Recent activity\"\n description=\"The latest stories and discussions across the workspace.\"\n >\n {recentActivity.length === 0 ? (\n <EmptyState\n icon={<Activity className=\"h-9 w-9\" />}\n title=\"Nothing yet\"\n description=\"Draft a story or open a discussion — it will show up here.\"\n />\n ) : (\n <GlassCard treatment=\"plain\" className=\"divide-y divide-border-seam\">\n {recentActivity.map((row) => {\n const Icon = ACTIVITY_ICON[row.kind];\n return (\n <button\n key={row.id}\n type=\"button\"\n onClick={row.onOpen}\n className=\"flex w-full items-center gap-3 px-4 py-3 text-left transition-colors hover:bg-bg-sunken/60 focus-visible:bg-bg-sunken/60 focus-visible:outline-none\"\n >\n <span\n className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-lg bg-[var(--color-accent-soft)] text-text-primary\"\n aria-hidden=\"true\"\n >\n <Icon className=\"h-4 w-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <p className=\"truncate text-sm font-medium text-text-primary\">\n {row.title}\n </p>\n <p className=\"mt-0.5 text-xs capitalize text-text-tertiary\">\n {row.kind} · {formatRelativeTime(row.createdAt)}\n </p>\n </div>\n <StatusBadge\n status={row.status}\n className=\"hidden shrink-0 sm:inline-flex\"\n />\n <ArrowRight\n className=\"h-4 w-4 shrink-0 text-text-tertiary\"\n aria-hidden=\"true\"\n />\n </button>\n );\n })}\n </GlassCard>\n )}\n </Section>\n </>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AA6DA,IAAM,IAAc,KAad,IAAkD;CACtD,OAAO;CACP,YAAY;CACb,EAcK,IAAoE;CACxE,UAAU;EAAE,OAAO;EAAY,MAAM;EAAc,WAAW;EAAe;CAC7E,SAAS;EAAE,OAAO;EAAkB,MAAM;EAAO,WAAW;EAAsB;CAClF,SAAS;EAAE,OAAO;EAAW,MAAM;EAAM,WAAW;EAA4B;CAChF,SAAS;EAAE,OAAO;EAAW,MAAM;EAAa,WAAW;EAA0B;CACtF,EAEK,IAA0C;CAAC;CAAY;CAAW;CAAW;CAAU;AAE7F,SAAgB,IAAiB;CAC/B,IAAM,EAAE,kBAAe,GAAmB,EAIpC,IAAY,EAAc;EAAE,QAAQ;EAAQ,YAAY,EAAE,OAAO,GAAa;EAAE,CAAC,EACjF,IAAa,EAAc;EAAE,QAAQ;EAAS,YAAY,EAAE,OAAO,GAAa;EAAE,CAAC,EACnF,IAAc,EAAc;EAAE,QAAQ;EAAa,YAAY,EAAE,OAAO,GAAa;EAAE,CAAC,EACxF,IAAiB,EAAc;EACnC,QAAQ;EACR,YAAY,EAAE,OAAO,GAAa;EACnC,CAAC,EACI,IAAmB,EAAkB,EAAE,YAAY,EAAE,OAAO,GAAa,EAAE,CAAC,EAC5E,IAAmB,EAAkB,EAAE,OAAO,GAAa,CAAC,EAC5D,IAAiB,GAAmB,EAAE,YAAY,EAAE,OAAO,GAAa,EAAE,CAAC,EAG3E,IAAa;EAAC,GADC;GAAC;GAAW;GAAY;GAAa;GAAe;EACpC;EAAkB;EAAkB;EAAe,EAElF,IAAc,QAAc,EAAU,MAAM,SAAS,EAAE,EAAE,CAAC,EAAU,KAAK,CAAC,EAC1E,IAAe,QAAc,EAAW,MAAM,SAAS,EAAE,EAAE,CAAC,EAAW,KAAK,CAAC,EAC7E,IAAgB,QAAc,EAAY,MAAM,SAAS,EAAE,EAAE,CAAC,EAAY,KAAK,CAAC,EAChF,IAAmB,QAAc,EAAe,MAAM,SAAS,EAAE,EAAE,CAAC,EAAe,KAAK,CAAC,EACzF,IAAc,QAAc,EAAiB,MAAM,SAAS,EAAE,EAAE,CAAC,EAAiB,KAAK,CAAC,EAExF,IAAY,EAAY,QACxB,IAAa,EAAa,QAC1B,IAAc,EAAc,QAC5B,IAAiB,EAAiB,QAClC,IAAkB,EAAY,QAG9B,IAAmB,QAAc;EACrC,IAAM,IAA8C;GAClD,UAAU;GACV,SAAS;GACT,SAAS;GACT,SAAS;GACV;AACD,OAAK,IAAM,KAAc,EACvB,CAAI,OAAO,UAAU,eAAe,KAAK,GAAQ,EAAW,OAAO,KACjE,EAAO,EAAW,WAAW;AAGjC,SAAO;IACN,CAAC,EAAY,CAAC,EAEX,IAAgB,EAAiB,UACjC,IAAsB,IAAkB,GACxC,IAAmB,EAAiB,MAAM,UAAU,GACpD,KAAiB,EAAe,MAAM,MAAM,UAAU,GAEtD,KAA2B;EAC/B;GAAE,OAAO;GAAQ,OAAO;GAAW;EACnC;GAAE,OAAO;GAAS,OAAO;GAAY;EACrC;GAAE,OAAO;GAAa,OAAO;GAAa;EAC1C;GAAE,OAAO;GAAa,OAAO;GAAgB;EAC9C,EAEK,IAAiB,QAA6B;EAClD,IAAM,IAA2B;GAC/B,GAAG;GACH,GAAG;GACH,GAAG;GACH,GAAG;GACJ,CAAC,KAAK,OAAW;GAChB,IAAI,SAAS,EAAM;GACnB,MAAM;GACN,OAAO,EAAM;GACb,QAAQ,EAAM;GACd,WAAW,EAAM;GACjB,cAAc,EAAW,WAAW,EAAM,KAAK;GAChD,EAAE,EAEG,IAAgC,EAAY,KAAK,OAAgB;GACrE,IAAI,cAAc,EAAW;GAC7B,MAAM;GACN,OAAO,EAAW;GAClB,QAAQ,EAAW;GACnB,WAAW,EAAW;GACtB,cAAc,EAAW,eAAe,EAAW,KAAK;GACzD,EAAE;AAEH,SAAO,CAAC,GAAG,GAAW,GAAG,EAAe,CACrC,MAAM,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,GAAG,IAAI,KAAK,EAAE,UAAU,CAAC,SAAS,CAAC,CACjF,MAAM,GAAG,EAAE;IACb;EAAC;EAAa;EAAc;EAAe;EAAkB;EAAa;EAAW,CAAC,EAEnF,KAAY,EAAW,MAAM,MAAM,EAAE,UAAU,EAC/C,KAAU,EAAW,MAAM,MAAM,EAAE,QAAQ;AAQjD,QACE,kBAAC,OAAD;EAAK,WAAU;YACb,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD;IACE,SAAQ;IACR,OAAM;IACN,aAAY;IACZ,CAAA,EAED,KACC,kBAAC,GAAD,EAAa,OAAM,gCAAiC,CAAA,GAClD,KACF,kBAAC,GAAD;IACE,OAAM;IACN,SAAQ;IACR,eArBa;AACrB,OAAW,SAAS,MAAM;AACnB,QAAE,SAAS;OAChB;;IAmBM,CAAA,GAEF,kBAAA,GAAA,EAAA,UAAA;IACE,kBAAC,KAAD;KAAG,WAAU;eAAmC;KAE5C,CAAA;IAGJ,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;OACtC,OAAM;OACN,OAAO;OACP,SAAQ;OACR,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAO,WAAU,WAAY,CAAA;OACnC,OAAM;OACN,OAAO;OACP,SAAQ;OACR,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAY,WAAU,WAAY,CAAA;OACxC,OAAM;OACN,OAAO;OACP,SAAQ;OACR,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,IAAD,EAAW,WAAU,WAAY,CAAA;OACvC,OAAM;OACN,OAAO;OACP,SAAQ;OACR,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAgB,WAAU,WAAY,CAAA;OAC5C,OAAM;OACN,OAAO;OACP,SAAQ;OACR,CAAA;MACF,kBAAC,GAAD;OACE,MAAM,kBAAC,IAAD,EAAS,WAAU,WAAY,CAAA;OACrC,OAAM;OACN,OAAO;OACP,SAAS,gBAAgB,EAAiB,aACxC,MAAqB,IAAI,KAAK;OAEhC,CAAA;MACE;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAuD;OAEhE,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBAAqC;OAE9C,CAAA,CACA,EAAA,CAAA,EACL,IAAiB,IAAc,IAAa,MAAc,IACzD,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;OACtC,OAAM;OACN,aAAY;OACZ,CAAA,GAEF,kBAAC,GAAD;OACE,WAAU;OACV,MAAM;OACN,QAAQ;OACR,CAAA,CAEM;SAEZ,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBAAvC,CACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,WAAU;iBAAuD;OAEhE,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBAAqC;OAE9C,CAAA,CACA,EAAA,CAAA,EACL,MAAoB,IACnB,kBAAC,GAAD;OACE,MAAM,kBAAC,GAAD,EAAgB,WAAU,WAAY,CAAA;OAC5C,OAAM;OACN,aAAY;OACZ,CAAA,GAEF,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD;QACE,OAAO;QACP,OAAO;QACP,SAAQ;QACR,MAAM;QACN,WAAW,GAAG,EAAc,MAAM,EAAgB;QAClD,CAAA,EACF,kBAAC,MAAD;QAAI,WAAU;kBAAd,CACG,EAAiB,KAAK,MAAW;SAChC,IAAM,IAAO,EAAgB,IACvB,IAAO,EAAK;AAClB,gBACE,kBAAC,OAAD;UAAkB,WAAU;oBAA5B,CACE,kBAAC,MAAD;WAAI,WAAU;qBAAd,CACE,kBAAC,GAAD;YACE,WAAW,EAAG,oBAAoB,EAAK,UAAU;YACjD,eAAY;YACZ,CAAA,EACD,EAAK,MACH;cACL,kBAAC,MAAD;WAAI,WAAU;qBACX,EAAiB;WACf,CAAA,CACD;YAXI,EAWJ;UAER,EACF,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,GAAD;UACE,WAAU;UACV,eAAY;UACZ,CAAA,EACF,kBAAC,QAAD,EAAA,UACG,MAAwB,IACrB,8FACA,MAAwB,IACtB,iFACA,uCAAuC,EAAoB,wCAC5D,CAAA,CACH;WACH;UACD;SAEE;QACR;;IAGN,kBAAC,GAAD;KACE,OAAM;KACN,aAAY;eAEX,EAAe,WAAW,IACzB,kBAAC,GAAD;MACE,MAAM,kBAAC,GAAD,EAAU,WAAU,WAAY,CAAA;MACtC,OAAM;MACN,aAAY;MACZ,CAAA,GAEF,kBAAC,GAAD;MAAW,WAAU;MAAQ,WAAU;gBACpC,EAAe,KAAK,MAAQ;OAC3B,IAAM,IAAO,EAAc,EAAI;AAC/B,cACE,kBAAC,UAAD;QAEE,MAAK;QACL,SAAS,EAAI;QACb,WAAU;kBAJZ;SAME,kBAAC,QAAD;UACE,WAAU;UACV,eAAY;oBAEZ,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA;UACvB,CAAA;SACP,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBACV,EAAI;WACH,CAAA,EACJ,kBAAC,KAAD;WAAG,WAAU;qBAAb;YACG,EAAI;YAAK;YAAI,GAAmB,EAAI,UAAU;YAC7C;aACA;;SACN,kBAAC,GAAD;UACE,QAAQ,EAAI;UACZ,WAAU;UACV,CAAA;SACF,kBAAC,GAAD;UACE,WAAU;UACV,eAAY;UACZ,CAAA;SACK;UA3BF,EAAI,GA2BF;QAEX;MACQ,CAAA;KAEN,CAAA;IACT,EAAA,CAAA,CAED;;EACF,CAAA"}
|
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
* - READING (tcp-03): a cover-banner hero, serif display title, a drop-cap
|
|
7
7
|
* article body with ornamental section breaks, and a right rail of citations
|
|
8
8
|
* (compact) + linked discussions.
|
|
9
|
+
* - CITATIONS (tcp-05): the story's full evidence base — roomy source cards
|
|
10
|
+
* carrying excerpt, provenance, source link and scan, a labelled
|
|
11
|
+
* verification control per source, and the methodology legend.
|
|
9
12
|
* - EDITORIAL (tcp-04): a visual lifecycle stepper with validated transitions
|
|
10
13
|
* (Publish / Send back), plus citations as a verifiable-source table with an
|
|
11
14
|
* inline verify control.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StoryDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/theculturepost/pages/stories/StoryDetailPage.tsx"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"StoryDetailPage.d.ts","sourceRoot":"","sources":["../../../../src/theculturepost/pages/stories/StoryDetailPage.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAkJH,wBAAgB,eAAe,4CA2L9B"}
|