@aeptus/aexeo-emdash 0.6.0 → 0.8.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/admin.js CHANGED
@@ -1,16 +1,513 @@
1
- // src/admin/Facts.tsx
1
+ // src/admin/PillarView.tsx
2
2
  import { useCallback, useEffect, useState } from "react";
3
- import { jsx, jsxs } from "react/jsx-runtime";
3
+
4
+ // src/types.ts
5
+ function layerHumanLabel(layer) {
6
+ switch (layer) {
7
+ case "retrievability":
8
+ return "Retrievability";
9
+ case "citability":
10
+ return "Citability";
11
+ case "absorbability":
12
+ return "Absorbability";
13
+ case "entity_legitimacy":
14
+ return "Entity legitimacy";
15
+ }
16
+ }
17
+ function layerOneLineDescription(layer) {
18
+ switch (layer) {
19
+ case "retrievability":
20
+ return "Can the engine find the page at all? Robots, sitemap, internal links, machine-readable surfaces.";
21
+ case "citability":
22
+ return "Once retrieved, does it look worth citing? Structure, schema, evidence density, scannability.";
23
+ case "absorbability":
24
+ return "Does the answer actually use this content? Cite-ready evidence, mirrors, llms.txt.";
25
+ case "entity_legitimacy":
26
+ return "Does the entity exist strongly enough to be selected at all? Aexeo surfaces this layer; it does not fix it.";
27
+ }
28
+ }
29
+
30
+ // src/admin/PillarView.tsx
31
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
32
  var PLUGIN_ID = "aexeo-emdash";
5
33
  var API_BASE = "/_emdash/api";
6
- function Facts() {
7
- const [data, setData] = useState(null);
8
- const [loadError, setLoadError] = useState(null);
9
- const [draft, setDraft] = useState("");
10
- const [validation, setValidation] = useState(null);
11
- const [busy, setBusy] = useState(null);
34
+ function PillarView({
35
+ layer,
36
+ extraSlot
37
+ }) {
38
+ const [state, setState] = useState({
39
+ loading: true,
40
+ error: null,
41
+ payload: null
42
+ });
43
+ const [refreshing, setRefreshing] = useState(false);
12
44
  const [toast, setToast] = useState(null);
13
- const loadCurrent = useCallback(async () => {
45
+ const load = useCallback(async () => {
46
+ setState((s) => ({ ...s, loading: true, error: null }));
47
+ try {
48
+ const res = await fetch(`${API_BASE}/plugins/${PLUGIN_ID}/data`, {
49
+ method: "POST",
50
+ headers: csrfHeaders(),
51
+ body: "{}"
52
+ });
53
+ const body = await res.json();
54
+ const payload2 = unwrap(body, res);
55
+ setState({ loading: false, error: null, payload: payload2 });
56
+ } catch (err) {
57
+ setState({
58
+ loading: false,
59
+ error: err instanceof Error ? err.message : String(err),
60
+ payload: null
61
+ });
62
+ }
63
+ }, []);
64
+ const refresh = useCallback(async () => {
65
+ setRefreshing(true);
66
+ setToast(null);
67
+ try {
68
+ const res = await fetch(`${API_BASE}/plugins/${PLUGIN_ID}/refresh`, {
69
+ method: "POST",
70
+ headers: csrfHeaders(),
71
+ body: "{}"
72
+ });
73
+ const body = await res.json();
74
+ const data = unwrap(body, res);
75
+ setState({ loading: false, error: null, payload: data.payload });
76
+ setToast(`Refreshed ${data.payload.totals.routes} routes`);
77
+ } catch (err) {
78
+ setToast(
79
+ `Refresh failed: ${err instanceof Error ? err.message : String(err)}`
80
+ );
81
+ } finally {
82
+ setRefreshing(false);
83
+ }
84
+ }, []);
85
+ useEffect(() => {
86
+ void load();
87
+ }, [load]);
88
+ if (state.loading && state.payload === null) {
89
+ return /* @__PURE__ */ jsxs("div", { className: "p-4 text-sm text-kumo-subtle", children: [
90
+ "Loading ",
91
+ layerHumanLabel(layer),
92
+ " findings\u2026"
93
+ ] });
94
+ }
95
+ if (state.error !== null) {
96
+ return /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
97
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: layerHumanLabel(layer) }),
98
+ /* @__PURE__ */ jsxs("p", { className: "mt-2 rounded border border-red-500/40 bg-red-500/10 p-3 text-sm text-red-700", children: [
99
+ "Failed to load: ",
100
+ state.error
101
+ ] }),
102
+ /* @__PURE__ */ jsx(
103
+ "button",
104
+ {
105
+ type: "button",
106
+ onClick: () => void load(),
107
+ className: "mt-3 rounded border border-kumo-line px-3 py-1.5 text-sm hover:bg-kumo-tint",
108
+ children: "Retry"
109
+ }
110
+ )
111
+ ] });
112
+ }
113
+ const payload = state.payload;
114
+ if (payload === null) {
115
+ return /* @__PURE__ */ jsx("div", { className: "p-4 text-sm text-kumo-subtle", children: "No data." });
116
+ }
117
+ const breakdown = payload.layerBreakdown.find((b) => b.layer === layer);
118
+ const findings = payload.findings.filter(
119
+ (f) => (f.layers?.primary ?? "citability") === layer
120
+ );
121
+ const sitewide = payload.sitewideFindings.filter(
122
+ (f) => (f.layers?.primary ?? "citability") === layer
123
+ );
124
+ const routesByPath = /* @__PURE__ */ new Map();
125
+ for (const route of payload.routes) {
126
+ routesByPath.set(route.route, route);
127
+ }
128
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-4", children: [
129
+ /* @__PURE__ */ jsxs("header", { className: "flex items-start justify-between gap-4", children: [
130
+ /* @__PURE__ */ jsxs("div", { children: [
131
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: layerHumanLabel(layer) }),
132
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-kumo-subtle", children: layerOneLineDescription(layer) }),
133
+ /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm", children: breakdown === void 0 || breakdown.total === 0 ? /* @__PURE__ */ jsx("span", { className: "text-kumo-subtle", children: "No findings on this layer." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
134
+ /* @__PURE__ */ jsx("strong", { children: breakdown.total }),
135
+ " findings \u2014",
136
+ " ",
137
+ breakdown.errors,
138
+ " errors, ",
139
+ breakdown.warnings,
140
+ " warnings."
141
+ ] }) })
142
+ ] }),
143
+ /* @__PURE__ */ jsx(
144
+ "button",
145
+ {
146
+ type: "button",
147
+ onClick: () => void refresh(),
148
+ disabled: refreshing,
149
+ className: "rounded bg-kumo-brand px-3 py-1.5 text-sm font-medium text-white hover:bg-kumo-brand/90 disabled:opacity-50",
150
+ children: refreshing ? "Refreshing\u2026" : "Refresh"
151
+ }
152
+ )
153
+ ] }),
154
+ toast !== null && /* @__PURE__ */ jsx("div", { className: "rounded border border-kumo-line bg-kumo-tint p-3 text-sm", children: toast }),
155
+ extraSlot,
156
+ sitewide.length > 0 && /* @__PURE__ */ jsxs("section", { className: "rounded border border-kumo-line p-3", children: [
157
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-medium", children: "Sitewide findings" }),
158
+ /* @__PURE__ */ jsx("ul", { className: "mt-2 space-y-1 text-sm", children: sitewide.map((finding, i) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
159
+ /* @__PURE__ */ jsx(SeverityBadge, { severity: finding.severity }),
160
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs", children: finding.rule_id }),
161
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: finding.message }),
162
+ finding.layers?.secondaries.length ? /* @__PURE__ */ jsx(SecondariesChips, { layers: finding.layers.secondaries }) : null
163
+ ] }, i)) })
164
+ ] }),
165
+ findings.length === 0 ? sitewide.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-kumo-subtle", children: "All clean on this layer." }) : null : /* @__PURE__ */ jsx("div", { className: "overflow-x-auto rounded border border-kumo-line", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-left text-sm", children: [
166
+ /* @__PURE__ */ jsx("thead", { className: "bg-kumo-tint", children: /* @__PURE__ */ jsxs("tr", { children: [
167
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Route" }),
168
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Rule" }),
169
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Severity" }),
170
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Message" }),
171
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Also affects" })
172
+ ] }) }),
173
+ /* @__PURE__ */ jsx("tbody", { children: findings.map((finding, i) => {
174
+ const routeInfo = routesByPath.get(finding.route);
175
+ return /* @__PURE__ */ jsxs("tr", { className: "border-t border-kumo-line", children: [
176
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx(RouteLinks, { finding, info: routeInfo }) }),
177
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx("code", { className: "rounded bg-kumo-tint px-1.5 py-0.5 font-mono text-xs", children: finding.rule_id }) }),
178
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx(SeverityBadge, { severity: finding.severity }) }),
179
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: finding.message }),
180
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: finding.layers?.secondaries.length ? /* @__PURE__ */ jsx(SecondariesChips, { layers: finding.layers.secondaries }) : /* @__PURE__ */ jsx("span", { className: "text-xs text-kumo-subtle", children: "\u2014" }) })
181
+ ] }, i);
182
+ }) })
183
+ ] }) })
184
+ ] });
185
+ }
186
+ function RouteLinks({
187
+ finding,
188
+ info
189
+ }) {
190
+ if (info === void 0) {
191
+ return /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: finding.route });
192
+ }
193
+ return /* @__PURE__ */ jsxs("span", { className: "space-x-2", children: [
194
+ info.editUrl !== "" ? /* @__PURE__ */ jsx(
195
+ "a",
196
+ {
197
+ href: info.editUrl,
198
+ className: "font-mono text-xs text-kumo-link hover:underline",
199
+ children: finding.route
200
+ }
201
+ ) : /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: finding.route }),
202
+ info.publishedUrl !== "" && /* @__PURE__ */ jsx(
203
+ "a",
204
+ {
205
+ href: info.publishedUrl,
206
+ target: "_blank",
207
+ rel: "noopener noreferrer",
208
+ className: "text-xs text-kumo-link hover:underline",
209
+ children: "live \u2197"
210
+ }
211
+ ),
212
+ info.status !== "" && info.status !== "published" && /* @__PURE__ */ jsx("span", { className: "text-xs text-kumo-subtle", children: info.status })
213
+ ] });
214
+ }
215
+ function SecondariesChips({ layers }) {
216
+ return /* @__PURE__ */ jsx("span", { className: "space-x-1", children: layers.map((layer) => /* @__PURE__ */ jsx(
217
+ "span",
218
+ {
219
+ className: "inline-block rounded border border-kumo-line bg-kumo-tint px-1.5 py-0.5 text-xs text-kumo-subtle",
220
+ title: layerOneLineDescription(layer),
221
+ children: layerHumanLabel(layer).toLowerCase()
222
+ },
223
+ layer
224
+ )) });
225
+ }
226
+ function SeverityBadge({ severity }) {
227
+ const classes = severity === "error" ? "bg-red-500/10 text-red-700 border-red-500/40" : severity === "warning" ? "bg-yellow-500/10 text-yellow-800 border-yellow-500/40" : "bg-kumo-tint text-kumo-subtle border-kumo-line";
228
+ return /* @__PURE__ */ jsx(
229
+ "span",
230
+ {
231
+ className: `inline-block rounded border px-1.5 py-0.5 text-xs font-medium ${classes}`,
232
+ children: severity
233
+ }
234
+ );
235
+ }
236
+ function csrfHeaders() {
237
+ return {
238
+ "Content-Type": "application/json",
239
+ "X-EmDash-Request": "1"
240
+ };
241
+ }
242
+ function unwrap(body, res) {
243
+ if (body.error !== void 0) {
244
+ throw new Error(`${body.error.code}: ${body.error.message}`);
245
+ }
246
+ if (body.data === void 0) {
247
+ throw new Error(`Empty response (HTTP ${res.status})`);
248
+ }
249
+ return body.data;
250
+ }
251
+
252
+ // src/admin/Absorbability.tsx
253
+ import { jsx as jsx2 } from "react/jsx-runtime";
254
+ function Absorbability() {
255
+ return /* @__PURE__ */ jsx2(PillarView, { layer: "absorbability" });
256
+ }
257
+
258
+ // src/admin/Citability.tsx
259
+ import { jsx as jsx3 } from "react/jsx-runtime";
260
+ function Citability() {
261
+ return /* @__PURE__ */ jsx3(PillarView, { layer: "citability" });
262
+ }
263
+
264
+ // src/admin/EntityPresence.tsx
265
+ import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
266
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
267
+ var PLUGIN_ID2 = "aexeo-emdash";
268
+ var API_BASE2 = "/_emdash/api";
269
+ var SOURCE_LABELS = {
270
+ wikipedia: "Wikipedia",
271
+ wikidata: "Wikidata",
272
+ github: "GitHub",
273
+ rdap: "Domain registration",
274
+ common_crawl: "Common Crawl"
275
+ };
276
+ var SOURCE_ORDER = [
277
+ "wikipedia",
278
+ "wikidata",
279
+ "github",
280
+ "rdap",
281
+ "common_crawl"
282
+ ];
283
+ function EntityPresence() {
284
+ const [data, setData] = useState2(null);
285
+ const [loadError, setLoadError] = useState2(null);
286
+ const [busy, setBusy] = useState2(null);
287
+ const load = useCallback2(async () => {
288
+ setLoadError(null);
289
+ try {
290
+ const res = await callPresenceRoute("data");
291
+ setData(res);
292
+ } catch (error) {
293
+ setLoadError(error instanceof Error ? error.message : String(error));
294
+ }
295
+ }, []);
296
+ const refresh = useCallback2(async () => {
297
+ setBusy("refresh");
298
+ setLoadError(null);
299
+ try {
300
+ const res = await callPresenceRoute("refresh");
301
+ setData(res);
302
+ } catch (error) {
303
+ setLoadError(error instanceof Error ? error.message : String(error));
304
+ } finally {
305
+ setBusy(null);
306
+ }
307
+ }, []);
308
+ useEffect2(() => {
309
+ void load();
310
+ }, [load]);
311
+ if (loadError !== null) {
312
+ return /* @__PURE__ */ jsxs2("div", { className: "rounded border border-red-300 bg-red-50 p-3 text-sm text-red-900", children: [
313
+ "Failed to load presence data: ",
314
+ loadError
315
+ ] });
316
+ }
317
+ if (data === null) {
318
+ return /* @__PURE__ */ jsx4("div", { className: "text-sm text-kumo-subtle", children: "Loading presence data\u2026" });
319
+ }
320
+ if (data.state === "no_manifest") {
321
+ return /* @__PURE__ */ jsx4("div", { className: "rounded border border-kumo-line bg-kumo-canvas p-3 text-sm", children: "Author the truth manifest first (above). The presence checks need at least an organization name and website." });
322
+ }
323
+ if (data.state === "no_organization") {
324
+ return /* @__PURE__ */ jsxs2("div", { className: "rounded border border-kumo-line bg-kumo-canvas p-3 text-sm", children: [
325
+ "The manifest exists but has no organization. Add an",
326
+ " ",
327
+ /* @__PURE__ */ jsx4("code", { children: "organization" }),
328
+ " entry with at least a ",
329
+ /* @__PURE__ */ jsx4("code", { children: "name" }),
330
+ " ",
331
+ "and ideally a ",
332
+ /* @__PURE__ */ jsx4("code", { children: "website" }),
333
+ ", then refresh."
334
+ ] });
335
+ }
336
+ return /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
337
+ /* @__PURE__ */ jsxs2("header", { className: "flex items-baseline justify-between gap-3", children: [
338
+ /* @__PURE__ */ jsxs2("div", { children: [
339
+ /* @__PURE__ */ jsx4("h3", { className: "text-sm font-medium", children: "Public web presence" }),
340
+ /* @__PURE__ */ jsxs2("p", { className: "mt-1 text-xs text-kumo-subtle", children: [
341
+ "What the open web shows about",
342
+ " ",
343
+ /* @__PURE__ */ jsx4("strong", { children: data.entityName ?? "this entity" }),
344
+ ". Aexeo does not score this layer \u2014 it surfaces it. Click through each row to verify."
345
+ ] })
346
+ ] }),
347
+ /* @__PURE__ */ jsx4(
348
+ "button",
349
+ {
350
+ type: "button",
351
+ onClick: () => void refresh(),
352
+ disabled: busy !== null,
353
+ className: "rounded border border-kumo-line px-3 py-1 text-xs hover:bg-kumo-canvas disabled:opacity-50",
354
+ children: busy === "refresh" ? "Checking\u2026" : "Refresh"
355
+ }
356
+ )
357
+ ] }),
358
+ data.state === "empty" && /* @__PURE__ */ jsxs2("div", { className: "rounded border border-kumo-line bg-kumo-canvas p-3 text-sm", children: [
359
+ "No checks have been run yet for",
360
+ " ",
361
+ /* @__PURE__ */ jsx4("strong", { children: data.entityName }),
362
+ ". Hit Refresh to query the five sources."
363
+ ] }),
364
+ data.state === "stale" && data.ageMinutes !== null && /* @__PURE__ */ jsxs2("div", { className: "rounded border border-amber-200 bg-amber-50 p-2 text-xs text-amber-900", children: [
365
+ "Cached results are ",
366
+ formatAge(data.ageMinutes),
367
+ " old. Hit Refresh to re-check."
368
+ ] }),
369
+ data.state === "fresh" && data.ageMinutes !== null && /* @__PURE__ */ jsxs2("div", { className: "text-xs text-kumo-subtle", children: [
370
+ "Last checked ",
371
+ formatAge(data.ageMinutes),
372
+ " ago."
373
+ ] }),
374
+ (data.state === "fresh" || data.state === "stale") && /* @__PURE__ */ jsx4("ul", { className: "space-y-2", children: orderResults(data.results).map((result) => /* @__PURE__ */ jsx4(SourceCard, { result }, result.source)) })
375
+ ] });
376
+ }
377
+ function SourceCard({ result }) {
378
+ const label = SOURCE_LABELS[result.source] ?? result.source;
379
+ const tone = toneFor(result.status);
380
+ return /* @__PURE__ */ jsx4(
381
+ "li",
382
+ {
383
+ className: `rounded border p-3 ${tone.border} ${tone.bg} text-sm`,
384
+ children: /* @__PURE__ */ jsxs2("div", { className: "flex items-start justify-between gap-3", children: [
385
+ /* @__PURE__ */ jsxs2("div", { className: "min-w-0 flex-1", children: [
386
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
387
+ /* @__PURE__ */ jsx4("span", { className: `text-xs font-medium ${tone.icon}`, children: tone.symbol }),
388
+ /* @__PURE__ */ jsx4("span", { className: "font-medium", children: label }),
389
+ /* @__PURE__ */ jsx4("span", { className: `text-xs ${tone.statusText}`, children: statusLabel(result.status) })
390
+ ] }),
391
+ result.label !== void 0 && /* @__PURE__ */ jsx4("div", { className: "mt-1 truncate", children: result.label }),
392
+ result.extra !== void 0 && /* @__PURE__ */ jsx4("div", { className: "mt-0.5 text-xs text-kumo-subtle", children: result.extra }),
393
+ result.error !== void 0 && /* @__PURE__ */ jsx4("div", { className: "mt-0.5 text-xs italic text-kumo-subtle", children: result.error })
394
+ ] }),
395
+ result.url !== void 0 && /* @__PURE__ */ jsx4(
396
+ "a",
397
+ {
398
+ href: result.url,
399
+ target: "_blank",
400
+ rel: "noreferrer noopener",
401
+ className: "shrink-0 self-center text-xs underline hover:no-underline",
402
+ children: "Open \u2197"
403
+ }
404
+ )
405
+ ] })
406
+ }
407
+ );
408
+ }
409
+ function toneFor(status) {
410
+ switch (status) {
411
+ case "found":
412
+ return {
413
+ border: "border-emerald-200",
414
+ bg: "bg-emerald-50",
415
+ icon: "text-emerald-700",
416
+ statusText: "text-emerald-700",
417
+ symbol: "\u2713"
418
+ };
419
+ case "not_found":
420
+ return {
421
+ border: "border-kumo-line",
422
+ bg: "bg-kumo-canvas",
423
+ icon: "text-kumo-subtle",
424
+ statusText: "text-kumo-subtle",
425
+ symbol: "\xB7"
426
+ };
427
+ case "unreachable":
428
+ return {
429
+ border: "border-amber-200",
430
+ bg: "bg-amber-50",
431
+ icon: "text-amber-800",
432
+ statusText: "text-amber-800",
433
+ symbol: "!"
434
+ };
435
+ case "skipped":
436
+ return {
437
+ border: "border-kumo-line",
438
+ bg: "bg-kumo-canvas",
439
+ icon: "text-kumo-subtle",
440
+ statusText: "text-kumo-subtle",
441
+ symbol: "\u2014"
442
+ };
443
+ }
444
+ }
445
+ function statusLabel(status) {
446
+ switch (status) {
447
+ case "found":
448
+ return "found";
449
+ case "not_found":
450
+ return "no record";
451
+ case "unreachable":
452
+ return "couldn't reach";
453
+ case "skipped":
454
+ return "skipped";
455
+ }
456
+ }
457
+ function orderResults(results) {
458
+ const known = SOURCE_ORDER.map(
459
+ (name) => results.find((r) => r.source === name) ?? null
460
+ ).filter((r) => r !== null);
461
+ const unknown = results.filter(
462
+ (r) => !SOURCE_ORDER.includes(r.source)
463
+ );
464
+ return [...known, ...unknown];
465
+ }
466
+ function formatAge(minutes) {
467
+ if (minutes < 60) return `${minutes} min`;
468
+ const hours = Math.floor(minutes / 60);
469
+ if (hours < 24) {
470
+ const remMin = minutes % 60;
471
+ return remMin === 0 ? `${hours}h` : `${hours}h ${remMin}m`;
472
+ }
473
+ const days = Math.floor(hours / 24);
474
+ return `${days}d`;
475
+ }
476
+ async function callPresenceRoute(kind) {
477
+ const res = await fetch(`${API_BASE2}/plugins/${PLUGIN_ID2}/presence`, {
478
+ method: "POST",
479
+ headers: {
480
+ "Content-Type": "application/json",
481
+ // Required by emdash's catch-all plugin-route handler for any
482
+ // POST/PUT/PATCH/DELETE on a private (default) plugin route.
483
+ // Without it the host returns 403 CSRF_REJECTED before the
484
+ // plugin handler even runs. Same convention as the /facts and
485
+ // /data routes the other admin components use.
486
+ "X-EmDash-Request": "1"
487
+ },
488
+ body: JSON.stringify({ kind })
489
+ });
490
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
491
+ const body = await res.json();
492
+ if ("error" in body) {
493
+ throw new Error(body.error.message ?? "presence route returned error");
494
+ }
495
+ return body.data;
496
+ }
497
+
498
+ // src/admin/Facts.tsx
499
+ import { useCallback as useCallback3, useEffect as useEffect3, useState as useState3 } from "react";
500
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
501
+ var PLUGIN_ID3 = "aexeo-emdash";
502
+ var API_BASE3 = "/_emdash/api";
503
+ function Facts() {
504
+ const [data, setData] = useState3(null);
505
+ const [loadError, setLoadError] = useState3(null);
506
+ const [draft, setDraft] = useState3("");
507
+ const [validation, setValidation] = useState3(null);
508
+ const [busy, setBusy] = useState3(null);
509
+ const [toast, setToast] = useState3(null);
510
+ const loadCurrent = useCallback3(async () => {
14
511
  setLoadError(null);
15
512
  try {
16
513
  const res = await callRoute("data", {});
@@ -23,10 +520,10 @@ function Facts() {
23
520
  setLoadError(err instanceof Error ? err.message : String(err));
24
521
  }
25
522
  }, []);
26
- useEffect(() => {
523
+ useEffect3(() => {
27
524
  void loadCurrent();
28
525
  }, [loadCurrent]);
29
- const onCopyPrompt = useCallback(async () => {
526
+ const onCopyPrompt = useCallback3(async () => {
30
527
  setBusy("prompt");
31
528
  setToast(null);
32
529
  try {
@@ -43,7 +540,7 @@ function Facts() {
43
540
  setBusy(null);
44
541
  }
45
542
  }, []);
46
- const onValidate = useCallback(async () => {
543
+ const onValidate = useCallback3(async () => {
47
544
  setBusy("validate");
48
545
  setToast(null);
49
546
  setValidation(null);
@@ -60,7 +557,7 @@ function Facts() {
60
557
  setBusy(null);
61
558
  }
62
559
  }, [draft]);
63
- const onSave = useCallback(async () => {
560
+ const onSave = useCallback3(async () => {
64
561
  setBusy("save");
65
562
  setToast(null);
66
563
  try {
@@ -76,15 +573,15 @@ function Facts() {
76
573
  }
77
574
  }, [draft, loadCurrent]);
78
575
  const validationOk = validation !== null && validation.validation.valid && validation.validation.errors.length === 0 && validation.assessment.mismatches.filter((m) => m.severity === "error").length === 0;
79
- return /* @__PURE__ */ jsxs("div", { className: "space-y-6 p-4", children: [
80
- /* @__PURE__ */ jsxs("header", { children: [
81
- /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "Truth manifest" }),
82
- /* @__PURE__ */ jsxs("p", { className: "mt-1 text-sm text-kumo-subtle", children: [
576
+ return /* @__PURE__ */ jsxs3("div", { className: "space-y-6 p-4", children: [
577
+ /* @__PURE__ */ jsxs3("header", { children: [
578
+ /* @__PURE__ */ jsx5("h1", { className: "text-xl font-semibold", children: "Truth manifest" }),
579
+ /* @__PURE__ */ jsxs3("p", { className: "mt-1 text-sm text-kumo-subtle", children: [
83
580
  "Author a ",
84
- /* @__PURE__ */ jsx("code", { children: "facts.json" }),
581
+ /* @__PURE__ */ jsx5("code", { children: "facts.json" }),
85
582
  " using your LLM of choice. The plugin frames the question, validates the answer, and stores the result. Read",
86
583
  " ",
87
- /* @__PURE__ */ jsx(
584
+ /* @__PURE__ */ jsx5(
88
585
  "a",
89
586
  {
90
587
  href: "https://github.com/schiste/Aexeo/blob/main/docs/facts-manifest.md",
@@ -98,18 +595,18 @@ function Facts() {
98
595
  "for the philosophy behind this flow."
99
596
  ] })
100
597
  ] }),
101
- loadError !== null && /* @__PURE__ */ jsxs("div", { className: "rounded border border-red-500/40 bg-red-500/10 p-3 text-sm text-red-700", children: [
598
+ loadError !== null && /* @__PURE__ */ jsxs3("div", { className: "rounded border border-red-500/40 bg-red-500/10 p-3 text-sm text-red-700", children: [
102
599
  "Failed to load manifest state: ",
103
600
  loadError
104
601
  ] }),
105
- /* @__PURE__ */ jsxs("section", { className: "rounded border border-kumo-line p-3", children: [
106
- /* @__PURE__ */ jsx("h2", { className: "text-sm font-medium", children: "Status" }),
107
- /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm", children: data === null ? "Loading\u2026" : data.present ? "\u2713 A truth manifest is stored. Editing the JSON below replaces it on Save." : "No truth manifest stored. Generate the prompt below to author one." })
602
+ /* @__PURE__ */ jsxs3("section", { className: "rounded border border-kumo-line p-3", children: [
603
+ /* @__PURE__ */ jsx5("h2", { className: "text-sm font-medium", children: "Status" }),
604
+ /* @__PURE__ */ jsx5("p", { className: "mt-2 text-sm", children: data === null ? "Loading\u2026" : data.present ? "\u2713 A truth manifest is stored. Editing the JSON below replaces it on Save." : "No truth manifest stored. Generate the prompt below to author one." })
108
605
  ] }),
109
- /* @__PURE__ */ jsxs("section", { className: "space-y-3 rounded border border-kumo-line p-3", children: [
110
- /* @__PURE__ */ jsx("h2", { className: "text-sm font-medium", children: "1. Generate authoring prompt" }),
111
- /* @__PURE__ */ jsx("p", { className: "text-sm text-kumo-subtle", children: "Copies a populated prompt to your clipboard. Paste it into Claude/GPT/etc., answer the LLM's questions, and bring the resulting JSON back here." }),
112
- /* @__PURE__ */ jsx(
606
+ /* @__PURE__ */ jsxs3("section", { className: "space-y-3 rounded border border-kumo-line p-3", children: [
607
+ /* @__PURE__ */ jsx5("h2", { className: "text-sm font-medium", children: "1. Generate authoring prompt" }),
608
+ /* @__PURE__ */ jsx5("p", { className: "text-sm text-kumo-subtle", children: "Copies a populated prompt to your clipboard. Paste it into Claude/GPT/etc., answer the LLM's questions, and bring the resulting JSON back here." }),
609
+ /* @__PURE__ */ jsx5(
113
610
  "button",
114
611
  {
115
612
  type: "button",
@@ -120,9 +617,9 @@ function Facts() {
120
617
  }
121
618
  )
122
619
  ] }),
123
- /* @__PURE__ */ jsxs("section", { className: "space-y-3 rounded border border-kumo-line p-3", children: [
124
- /* @__PURE__ */ jsx("h2", { className: "text-sm font-medium", children: "2. Paste & validate" }),
125
- /* @__PURE__ */ jsx(
620
+ /* @__PURE__ */ jsxs3("section", { className: "space-y-3 rounded border border-kumo-line p-3", children: [
621
+ /* @__PURE__ */ jsx5("h2", { className: "text-sm font-medium", children: "2. Paste & validate" }),
622
+ /* @__PURE__ */ jsx5(
126
623
  "textarea",
127
624
  {
128
625
  value: draft,
@@ -133,8 +630,8 @@ function Facts() {
133
630
  className: "w-full rounded border border-kumo-line bg-kumo-tint p-2 font-mono text-xs"
134
631
  }
135
632
  ),
136
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
137
- /* @__PURE__ */ jsx(
633
+ /* @__PURE__ */ jsxs3("div", { className: "flex gap-2", children: [
634
+ /* @__PURE__ */ jsx5(
138
635
  "button",
139
636
  {
140
637
  type: "button",
@@ -144,7 +641,7 @@ function Facts() {
144
641
  children: busy === "validate" ? "Validating\u2026" : "Validate"
145
642
  }
146
643
  ),
147
- /* @__PURE__ */ jsx(
644
+ /* @__PURE__ */ jsx5(
148
645
  "button",
149
646
  {
150
647
  type: "button",
@@ -157,8 +654,8 @@ function Facts() {
157
654
  )
158
655
  ] })
159
656
  ] }),
160
- toast !== null && /* @__PURE__ */ jsx("div", { className: "rounded border border-kumo-line bg-kumo-tint p-3 text-sm", children: toast }),
161
- validation !== null && /* @__PURE__ */ jsx(ValidationReport, { result: validation })
657
+ toast !== null && /* @__PURE__ */ jsx5("div", { className: "rounded border border-kumo-line bg-kumo-tint p-3 text-sm", children: toast }),
658
+ validation !== null && /* @__PURE__ */ jsx5(ValidationReport, { result: validation })
162
659
  ] });
163
660
  }
164
661
  function ValidationReport({
@@ -170,13 +667,13 @@ function ValidationReport({
170
667
  const warnMismatches = result.assessment.mismatches.filter(
171
668
  (m) => m.severity === "warning"
172
669
  );
173
- return /* @__PURE__ */ jsxs("section", { className: "space-y-3 rounded border border-kumo-line p-3", children: [
174
- /* @__PURE__ */ jsx("h2", { className: "text-sm font-medium", children: "Validation result" }),
175
- /* @__PURE__ */ jsxs("div", { className: "text-sm", children: [
176
- /* @__PURE__ */ jsxs("div", { children: [
670
+ return /* @__PURE__ */ jsxs3("section", { className: "space-y-3 rounded border border-kumo-line p-3", children: [
671
+ /* @__PURE__ */ jsx5("h2", { className: "text-sm font-medium", children: "Validation result" }),
672
+ /* @__PURE__ */ jsxs3("div", { className: "text-sm", children: [
673
+ /* @__PURE__ */ jsxs3("div", { children: [
177
674
  "Shape:",
178
675
  " ",
179
- /* @__PURE__ */ jsx("strong", { children: result.validation.valid ? "valid" : "invalid" }),
676
+ /* @__PURE__ */ jsx5("strong", { children: result.validation.valid ? "valid" : "invalid" }),
180
677
  " ",
181
678
  "\xB7 org",
182
679
  " ",
@@ -192,14 +689,14 @@ function ValidationReport({
192
689
  result.validation.forbidden_term_count,
193
690
  " forbidden terms"
194
691
  ] }),
195
- /* @__PURE__ */ jsxs("div", { children: [
692
+ /* @__PURE__ */ jsxs3("div", { children: [
196
693
  "Score: ",
197
- /* @__PURE__ */ jsx("strong", { children: result.assessment.score }),
694
+ /* @__PURE__ */ jsx5("strong", { children: result.assessment.score }),
198
695
  "/",
199
696
  result.assessment.score_ceiling,
200
697
  " \xB7 source =",
201
698
  " ",
202
- /* @__PURE__ */ jsx("code", { children: result.assessment.structured_truth_source }),
699
+ /* @__PURE__ */ jsx5("code", { children: result.assessment.structured_truth_source }),
203
700
  " \xB7",
204
701
  " ",
205
702
  result.assessment.pages_analyzed,
@@ -209,23 +706,23 @@ function ValidationReport({
209
706
  " with schema.org"
210
707
  ] })
211
708
  ] }),
212
- result.validation.errors.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded border border-red-500/40 bg-red-500/10 p-2 text-sm", children: [
213
- /* @__PURE__ */ jsx("strong", { children: "Schema errors:" }),
214
- /* @__PURE__ */ jsx("ul", { className: "mt-1 list-disc pl-5", children: result.validation.errors.map((e, i) => /* @__PURE__ */ jsx("li", { children: e }, i)) })
709
+ result.validation.errors.length > 0 && /* @__PURE__ */ jsxs3("div", { className: "rounded border border-red-500/40 bg-red-500/10 p-2 text-sm", children: [
710
+ /* @__PURE__ */ jsx5("strong", { children: "Schema errors:" }),
711
+ /* @__PURE__ */ jsx5("ul", { className: "mt-1 list-disc pl-5", children: result.validation.errors.map((e, i) => /* @__PURE__ */ jsx5("li", { children: e }, i)) })
215
712
  ] }),
216
- result.validation.warnings.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded border border-yellow-500/40 bg-yellow-500/10 p-2 text-sm", children: [
217
- /* @__PURE__ */ jsx("strong", { children: "Schema warnings:" }),
218
- /* @__PURE__ */ jsx("ul", { className: "mt-1 list-disc pl-5", children: result.validation.warnings.map((w, i) => /* @__PURE__ */ jsx("li", { children: w }, i)) })
713
+ result.validation.warnings.length > 0 && /* @__PURE__ */ jsxs3("div", { className: "rounded border border-yellow-500/40 bg-yellow-500/10 p-2 text-sm", children: [
714
+ /* @__PURE__ */ jsx5("strong", { children: "Schema warnings:" }),
715
+ /* @__PURE__ */ jsx5("ul", { className: "mt-1 list-disc pl-5", children: result.validation.warnings.map((w, i) => /* @__PURE__ */ jsx5("li", { children: w }, i)) })
219
716
  ] }),
220
- errMismatches.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded border border-red-500/40 bg-red-500/10 p-2 text-sm", children: [
221
- /* @__PURE__ */ jsx("strong", { children: "Mismatches with site (errors):" }),
222
- /* @__PURE__ */ jsx(MismatchList, { items: errMismatches })
717
+ errMismatches.length > 0 && /* @__PURE__ */ jsxs3("div", { className: "rounded border border-red-500/40 bg-red-500/10 p-2 text-sm", children: [
718
+ /* @__PURE__ */ jsx5("strong", { children: "Mismatches with site (errors):" }),
719
+ /* @__PURE__ */ jsx5(MismatchList, { items: errMismatches })
223
720
  ] }),
224
- warnMismatches.length > 0 && /* @__PURE__ */ jsxs("div", { className: "rounded border border-yellow-500/40 bg-yellow-500/10 p-2 text-sm", children: [
225
- /* @__PURE__ */ jsx("strong", { children: "Mismatches with site (warnings):" }),
226
- /* @__PURE__ */ jsx(MismatchList, { items: warnMismatches })
721
+ warnMismatches.length > 0 && /* @__PURE__ */ jsxs3("div", { className: "rounded border border-yellow-500/40 bg-yellow-500/10 p-2 text-sm", children: [
722
+ /* @__PURE__ */ jsx5("strong", { children: "Mismatches with site (warnings):" }),
723
+ /* @__PURE__ */ jsx5(MismatchList, { items: warnMismatches })
227
724
  ] }),
228
- result.validation.errors.length === 0 && result.assessment.mismatches.length === 0 && /* @__PURE__ */ jsxs("div", { className: "rounded border border-green-500/40 bg-green-500/10 p-2 text-sm", children: [
725
+ result.validation.errors.length === 0 && result.assessment.mismatches.length === 0 && /* @__PURE__ */ jsxs3("div", { className: "rounded border border-green-500/40 bg-green-500/10 p-2 text-sm", children: [
229
726
  "\u2713 Manifest is valid and agrees with on-page schema.org. Click",
230
727
  " ",
231
728
  "Save to persist."
@@ -235,18 +732,18 @@ function ValidationReport({
235
732
  function MismatchList({
236
733
  items
237
734
  }) {
238
- return /* @__PURE__ */ jsx("ul", { className: "mt-1 list-disc pl-5", children: items.map((m, i) => /* @__PURE__ */ jsxs("li", { children: [
239
- /* @__PURE__ */ jsx("code", { className: "text-xs", children: m.route }),
735
+ return /* @__PURE__ */ jsx5("ul", { className: "mt-1 list-disc pl-5", children: items.map((m, i) => /* @__PURE__ */ jsxs3("li", { children: [
736
+ /* @__PURE__ */ jsx5("code", { className: "text-xs", children: m.route }),
240
737
  " \xB7 ",
241
738
  m.field,
242
739
  ": expected",
243
740
  " ",
244
- /* @__PURE__ */ jsx("code", { className: "text-xs", children: m.expected }),
741
+ /* @__PURE__ */ jsx5("code", { className: "text-xs", children: m.expected }),
245
742
  ", observed",
246
743
  " ",
247
- /* @__PURE__ */ jsx("code", { className: "text-xs", children: m.observed }),
744
+ /* @__PURE__ */ jsx5("code", { className: "text-xs", children: m.observed }),
248
745
  " ",
249
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-kumo-subtle", children: [
746
+ /* @__PURE__ */ jsxs3("span", { className: "text-xs text-kumo-subtle", children: [
250
747
  "(",
251
748
  m.source,
252
749
  ")"
@@ -254,7 +751,7 @@ function MismatchList({
254
751
  ] }, i)) });
255
752
  }
256
753
  async function callRoute(kind, extra) {
257
- const res = await fetch(`${API_BASE}/plugins/${PLUGIN_ID}/facts`, {
754
+ const res = await fetch(`${API_BASE3}/plugins/${PLUGIN_ID3}/facts`, {
258
755
  method: "POST",
259
756
  headers: {
260
757
  "Content-Type": "application/json",
@@ -272,29 +769,48 @@ async function callRoute(kind, extra) {
272
769
  return body.data;
273
770
  }
274
771
 
772
+ // src/admin/EntityLegitimacy.tsx
773
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
774
+ function EntityLegitimacy() {
775
+ return /* @__PURE__ */ jsx6(
776
+ PillarView,
777
+ {
778
+ layer: "entity_legitimacy",
779
+ extraSlot: /* @__PURE__ */ jsxs4("div", { className: "space-y-3", children: [
780
+ /* @__PURE__ */ jsxs4("section", { className: "rounded border border-kumo-line p-3", children: [
781
+ /* @__PURE__ */ jsx6("h2", { className: "text-sm font-medium", children: "Truth manifest" }),
782
+ /* @__PURE__ */ jsx6("p", { className: "mt-1 text-xs text-kumo-subtle", children: "The structured assertion of who the organization is, what the products are, and the terminology to use/avoid. This is the primary entity-legitimacy signal Aexeo can write \u2014 the rest comes from third-party presence Aexeo can only observe." }),
783
+ /* @__PURE__ */ jsx6("div", { className: "mt-3", children: /* @__PURE__ */ jsx6(Facts, {}) })
784
+ ] }),
785
+ /* @__PURE__ */ jsx6("section", { className: "rounded border border-kumo-line p-3", children: /* @__PURE__ */ jsx6(EntityPresence, {}) })
786
+ ] })
787
+ }
788
+ );
789
+ }
790
+
275
791
  // src/admin/Findings.tsx
276
- import { useCallback as useCallback2, useEffect as useEffect2, useState as useState2 } from "react";
277
- import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
278
- var PLUGIN_ID2 = "aexeo-emdash";
279
- var API_BASE2 = "/_emdash/api";
792
+ import { useCallback as useCallback4, useEffect as useEffect4, useState as useState4 } from "react";
793
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
794
+ var PLUGIN_ID4 = "aexeo-emdash";
795
+ var API_BASE4 = "/_emdash/api";
280
796
  function Findings() {
281
- const [state, setState] = useState2({
797
+ const [state, setState] = useState4({
282
798
  loading: true,
283
799
  error: null,
284
800
  payload: null
285
801
  });
286
- const [refreshing, setRefreshing] = useState2(false);
287
- const [toast, setToast] = useState2(null);
288
- const load = useCallback2(async () => {
802
+ const [refreshing, setRefreshing] = useState4(false);
803
+ const [toast, setToast] = useState4(null);
804
+ const load = useCallback4(async () => {
289
805
  setState((s) => ({ ...s, loading: true, error: null }));
290
806
  try {
291
- const res = await fetch(`${API_BASE2}/plugins/${PLUGIN_ID2}/data`, {
807
+ const res = await fetch(`${API_BASE4}/plugins/${PLUGIN_ID4}/data`, {
292
808
  method: "POST",
293
- headers: csrfHeaders(),
809
+ headers: csrfHeaders2(),
294
810
  body: "{}"
295
811
  });
296
812
  const body = await res.json();
297
- const payload2 = unwrap(body, res);
813
+ const payload2 = unwrap2(body, res);
298
814
  setState({ loading: false, error: null, payload: payload2 });
299
815
  } catch (err) {
300
816
  setState({
@@ -304,17 +820,17 @@ function Findings() {
304
820
  });
305
821
  }
306
822
  }, []);
307
- const refresh = useCallback2(async () => {
823
+ const refresh = useCallback4(async () => {
308
824
  setRefreshing(true);
309
825
  setToast(null);
310
826
  try {
311
- const res = await fetch(`${API_BASE2}/plugins/${PLUGIN_ID2}/refresh`, {
827
+ const res = await fetch(`${API_BASE4}/plugins/${PLUGIN_ID4}/refresh`, {
312
828
  method: "POST",
313
- headers: csrfHeaders(),
829
+ headers: csrfHeaders2(),
314
830
  body: "{}"
315
831
  });
316
832
  const body = await res.json();
317
- const data = unwrap(body, res);
833
+ const data = unwrap2(body, res);
318
834
  setState({ loading: false, error: null, payload: data.payload });
319
835
  if (data.summary) {
320
836
  const { documentsScanned, routesUpdated, totalFindings, errors } = data.summary;
@@ -334,20 +850,20 @@ function Findings() {
334
850
  setRefreshing(false);
335
851
  }
336
852
  }, []);
337
- useEffect2(() => {
853
+ useEffect4(() => {
338
854
  void load();
339
855
  }, [load]);
340
856
  if (state.loading && state.payload === null) {
341
- return /* @__PURE__ */ jsx2("div", { className: "p-4 text-sm text-kumo-subtle", children: "Loading findings\u2026" });
857
+ return /* @__PURE__ */ jsx7("div", { className: "p-4 text-sm text-kumo-subtle", children: "Loading findings\u2026" });
342
858
  }
343
859
  if (state.error !== null) {
344
- return /* @__PURE__ */ jsxs2("div", { className: "p-4", children: [
345
- /* @__PURE__ */ jsx2("h1", { className: "text-xl font-semibold", children: "SEO findings" }),
346
- /* @__PURE__ */ jsxs2("p", { className: "mt-2 rounded border border-red-500/40 bg-red-500/10 p-3 text-sm text-red-700", children: [
860
+ return /* @__PURE__ */ jsxs5("div", { className: "p-4", children: [
861
+ /* @__PURE__ */ jsx7("h1", { className: "text-xl font-semibold", children: "SEO findings" }),
862
+ /* @__PURE__ */ jsxs5("p", { className: "mt-2 rounded border border-red-500/40 bg-red-500/10 p-3 text-sm text-red-700", children: [
347
863
  "Failed to load: ",
348
864
  state.error
349
865
  ] }),
350
- /* @__PURE__ */ jsx2(
866
+ /* @__PURE__ */ jsx7(
351
867
  "button",
352
868
  {
353
869
  type: "button",
@@ -360,17 +876,17 @@ function Findings() {
360
876
  }
361
877
  const payload = state.payload;
362
878
  if (payload === null) {
363
- return /* @__PURE__ */ jsx2("div", { className: "p-4 text-sm text-kumo-subtle", children: "No data." });
879
+ return /* @__PURE__ */ jsx7("div", { className: "p-4 text-sm text-kumo-subtle", children: "No data." });
364
880
  }
365
881
  const routesByPath = /* @__PURE__ */ new Map();
366
882
  for (const route of payload.routes) {
367
883
  routesByPath.set(route.route, route);
368
884
  }
369
- return /* @__PURE__ */ jsxs2("div", { className: "space-y-4 p-4", children: [
370
- /* @__PURE__ */ jsxs2("header", { className: "flex items-center justify-between gap-4", children: [
371
- /* @__PURE__ */ jsxs2("div", { children: [
372
- /* @__PURE__ */ jsx2("h1", { className: "text-xl font-semibold", children: "SEO findings" }),
373
- /* @__PURE__ */ jsx2("p", { className: "mt-1 text-sm text-kumo-subtle", children: payload.totals.findings === 0 ? /* @__PURE__ */ jsx2(Fragment, { children: "No findings yet \u2014 click Refresh to evaluate the site." }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
885
+ return /* @__PURE__ */ jsxs5("div", { className: "space-y-4 p-4", children: [
886
+ /* @__PURE__ */ jsxs5("header", { className: "flex items-center justify-between gap-4", children: [
887
+ /* @__PURE__ */ jsxs5("div", { children: [
888
+ /* @__PURE__ */ jsx7("h1", { className: "text-xl font-semibold", children: "SEO findings" }),
889
+ /* @__PURE__ */ jsx7("p", { className: "mt-1 text-sm text-kumo-subtle", children: payload.totals.findings === 0 ? /* @__PURE__ */ jsx7(Fragment2, { children: "No findings yet \u2014 click Refresh to evaluate the site." }) : /* @__PURE__ */ jsxs5(Fragment2, { children: [
374
890
  payload.totals.findings,
375
891
  " findings across ",
376
892
  payload.totals.routes,
@@ -382,7 +898,7 @@ function Findings() {
382
898
  " warnings."
383
899
  ] }) })
384
900
  ] }),
385
- /* @__PURE__ */ jsx2(
901
+ /* @__PURE__ */ jsx7(
386
902
  "button",
387
903
  {
388
904
  type: "button",
@@ -393,51 +909,51 @@ function Findings() {
393
909
  }
394
910
  )
395
911
  ] }),
396
- toast !== null && /* @__PURE__ */ jsx2("div", { className: "rounded border border-kumo-line bg-kumo-tint p-3 text-sm", children: toast }),
397
- payload.sitewideFindings.length > 0 && /* @__PURE__ */ jsxs2("section", { className: "rounded border border-kumo-line p-3", children: [
398
- /* @__PURE__ */ jsx2("h2", { className: "text-sm font-medium", children: "Sitewide" }),
399
- /* @__PURE__ */ jsx2("ul", { className: "mt-2 space-y-1 text-sm", children: payload.sitewideFindings.map((finding, i) => /* @__PURE__ */ jsxs2("li", { className: "flex items-start gap-2", children: [
400
- /* @__PURE__ */ jsx2(SeverityBadge, { severity: finding.severity }),
401
- /* @__PURE__ */ jsx2("code", { className: "font-mono text-xs", children: finding.rule_id }),
402
- /* @__PURE__ */ jsx2("span", { className: "flex-1", children: finding.message })
912
+ toast !== null && /* @__PURE__ */ jsx7("div", { className: "rounded border border-kumo-line bg-kumo-tint p-3 text-sm", children: toast }),
913
+ payload.sitewideFindings.length > 0 && /* @__PURE__ */ jsxs5("section", { className: "rounded border border-kumo-line p-3", children: [
914
+ /* @__PURE__ */ jsx7("h2", { className: "text-sm font-medium", children: "Sitewide" }),
915
+ /* @__PURE__ */ jsx7("ul", { className: "mt-2 space-y-1 text-sm", children: payload.sitewideFindings.map((finding, i) => /* @__PURE__ */ jsxs5("li", { className: "flex items-start gap-2", children: [
916
+ /* @__PURE__ */ jsx7(SeverityBadge2, { severity: finding.severity }),
917
+ /* @__PURE__ */ jsx7("code", { className: "font-mono text-xs", children: finding.rule_id }),
918
+ /* @__PURE__ */ jsx7("span", { className: "flex-1", children: finding.message })
403
919
  ] }, i)) })
404
920
  ] }),
405
- payload.findings.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "text-sm text-kumo-subtle", children: payload.totals.documentsIndexed === 0 ? "No documents indexed \u2014 click Refresh to do an initial sweep." : "All indexed documents are clean." }) : /* @__PURE__ */ jsx2("div", { className: "overflow-x-auto rounded border border-kumo-line", children: /* @__PURE__ */ jsxs2("table", { className: "w-full text-left text-sm", children: [
406
- /* @__PURE__ */ jsx2("thead", { className: "bg-kumo-tint", children: /* @__PURE__ */ jsxs2("tr", { children: [
407
- /* @__PURE__ */ jsx2("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Route" }),
408
- /* @__PURE__ */ jsx2("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Rule" }),
409
- /* @__PURE__ */ jsx2("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Severity" }),
410
- /* @__PURE__ */ jsx2("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Message" })
921
+ payload.findings.length === 0 ? /* @__PURE__ */ jsx7("p", { className: "text-sm text-kumo-subtle", children: payload.totals.documentsIndexed === 0 ? "No documents indexed \u2014 click Refresh to do an initial sweep." : "All indexed documents are clean." }) : /* @__PURE__ */ jsx7("div", { className: "overflow-x-auto rounded border border-kumo-line", children: /* @__PURE__ */ jsxs5("table", { className: "w-full text-left text-sm", children: [
922
+ /* @__PURE__ */ jsx7("thead", { className: "bg-kumo-tint", children: /* @__PURE__ */ jsxs5("tr", { children: [
923
+ /* @__PURE__ */ jsx7("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Route" }),
924
+ /* @__PURE__ */ jsx7("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Rule" }),
925
+ /* @__PURE__ */ jsx7("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Severity" }),
926
+ /* @__PURE__ */ jsx7("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Message" })
411
927
  ] }) }),
412
- /* @__PURE__ */ jsx2("tbody", { children: payload.findings.map((finding, i) => {
928
+ /* @__PURE__ */ jsx7("tbody", { children: payload.findings.map((finding, i) => {
413
929
  const routeInfo = routesByPath.get(finding.route);
414
- return /* @__PURE__ */ jsxs2("tr", { className: "border-t border-kumo-line", children: [
415
- /* @__PURE__ */ jsx2("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx2(RouteLinks, { finding, info: routeInfo }) }),
416
- /* @__PURE__ */ jsx2("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx2("code", { className: "rounded bg-kumo-tint px-1.5 py-0.5 font-mono text-xs", children: finding.rule_id }) }),
417
- /* @__PURE__ */ jsx2("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx2(SeverityBadge, { severity: finding.severity }) }),
418
- /* @__PURE__ */ jsx2("td", { className: "px-3 py-2 align-top", children: finding.message })
930
+ return /* @__PURE__ */ jsxs5("tr", { className: "border-t border-kumo-line", children: [
931
+ /* @__PURE__ */ jsx7("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx7(RouteLinks2, { finding, info: routeInfo }) }),
932
+ /* @__PURE__ */ jsx7("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx7("code", { className: "rounded bg-kumo-tint px-1.5 py-0.5 font-mono text-xs", children: finding.rule_id }) }),
933
+ /* @__PURE__ */ jsx7("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx7(SeverityBadge2, { severity: finding.severity }) }),
934
+ /* @__PURE__ */ jsx7("td", { className: "px-3 py-2 align-top", children: finding.message })
419
935
  ] }, i);
420
936
  }) })
421
937
  ] }) })
422
938
  ] });
423
939
  }
424
- function RouteLinks({
940
+ function RouteLinks2({
425
941
  finding,
426
942
  info
427
943
  }) {
428
944
  if (info === void 0) {
429
- return /* @__PURE__ */ jsx2("span", { className: "font-mono text-xs", children: finding.route });
945
+ return /* @__PURE__ */ jsx7("span", { className: "font-mono text-xs", children: finding.route });
430
946
  }
431
- return /* @__PURE__ */ jsxs2("span", { className: "space-x-2", children: [
432
- info.editUrl !== "" ? /* @__PURE__ */ jsx2(
947
+ return /* @__PURE__ */ jsxs5("span", { className: "space-x-2", children: [
948
+ info.editUrl !== "" ? /* @__PURE__ */ jsx7(
433
949
  "a",
434
950
  {
435
951
  href: info.editUrl,
436
952
  className: "font-mono text-xs text-kumo-link hover:underline",
437
953
  children: finding.route
438
954
  }
439
- ) : /* @__PURE__ */ jsx2("span", { className: "font-mono text-xs", children: finding.route }),
440
- info.publishedUrl !== "" && /* @__PURE__ */ jsx2(
955
+ ) : /* @__PURE__ */ jsx7("span", { className: "font-mono text-xs", children: finding.route }),
956
+ info.publishedUrl !== "" && /* @__PURE__ */ jsx7(
441
957
  "a",
442
958
  {
443
959
  href: info.publishedUrl,
@@ -447,12 +963,12 @@ function RouteLinks({
447
963
  children: "live \u2197"
448
964
  }
449
965
  ),
450
- info.status !== "" && info.status !== "published" && /* @__PURE__ */ jsx2("span", { className: "text-xs text-kumo-subtle", children: info.status })
966
+ info.status !== "" && info.status !== "published" && /* @__PURE__ */ jsx7("span", { className: "text-xs text-kumo-subtle", children: info.status })
451
967
  ] });
452
968
  }
453
- function SeverityBadge({ severity }) {
969
+ function SeverityBadge2({ severity }) {
454
970
  const classes = severity === "error" ? "bg-red-500/10 text-red-700 border-red-500/40" : severity === "warning" ? "bg-yellow-500/10 text-yellow-800 border-yellow-500/40" : "bg-kumo-tint text-kumo-subtle border-kumo-line";
455
- return /* @__PURE__ */ jsx2(
971
+ return /* @__PURE__ */ jsx7(
456
972
  "span",
457
973
  {
458
974
  className: `inline-block rounded border px-1.5 py-0.5 text-xs font-medium ${classes}`,
@@ -460,7 +976,7 @@ function SeverityBadge({ severity }) {
460
976
  }
461
977
  );
462
978
  }
463
- function csrfHeaders() {
979
+ function csrfHeaders2() {
464
980
  return {
465
981
  "Content-Type": "application/json",
466
982
  // emdash's apiFetch wrapper sets this on every mutation;
@@ -469,7 +985,7 @@ function csrfHeaders() {
469
985
  "X-EmDash-Request": "1"
470
986
  };
471
987
  }
472
- function unwrap(body, res) {
988
+ function unwrap2(body, res) {
473
989
  if (body.error !== void 0) {
474
990
  throw new Error(`${body.error.code}: ${body.error.message}`);
475
991
  }
@@ -479,12 +995,25 @@ function unwrap(body, res) {
479
995
  return body.data;
480
996
  }
481
997
 
998
+ // src/admin/Retrievability.tsx
999
+ import { jsx as jsx8 } from "react/jsx-runtime";
1000
+ function Retrievability() {
1001
+ return /* @__PURE__ */ jsx8(PillarView, { layer: "retrievability" });
1002
+ }
1003
+
482
1004
  // src/admin.tsx
483
1005
  var pages = {
1006
+ // Four pillar pages — the canonical organization going forward.
1007
+ "/retrievability": Retrievability,
1008
+ "/citability": Citability,
1009
+ "/absorbability": Absorbability,
1010
+ "/entity-legitimacy": EntityLegitimacy,
1011
+ // /findings stays as a cross-pillar flat view for users who want
1012
+ // every finding in one list. Not removed because it's still useful
1013
+ // for triage — "show me everything that's wrong" is a real workflow.
484
1014
  "/findings": Findings,
485
- // /facts is the truth-manifest authoring page. Block Kit can't render
486
- // a textarea or copy-to-clipboard button, so this path goes through
487
- // the React adminEntry. Same mechanism as /findings.
1015
+ // /facts kept as an alias so old bookmarks don't 404. Renders the
1016
+ // same Facts component the EntityLegitimacy page composes.
488
1017
  "/facts": Facts
489
1018
  };
490
1019
  export {