@aeptus/aexeo-emdash 0.6.0 → 0.8.0

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,505 @@
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: { "Content-Type": "application/json" },
480
+ body: JSON.stringify({ kind })
481
+ });
482
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
483
+ const body = await res.json();
484
+ if ("error" in body) {
485
+ throw new Error(body.error.message ?? "presence route returned error");
486
+ }
487
+ return body.data;
488
+ }
489
+
490
+ // src/admin/Facts.tsx
491
+ import { useCallback as useCallback3, useEffect as useEffect3, useState as useState3 } from "react";
492
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
493
+ var PLUGIN_ID3 = "aexeo-emdash";
494
+ var API_BASE3 = "/_emdash/api";
495
+ function Facts() {
496
+ const [data, setData] = useState3(null);
497
+ const [loadError, setLoadError] = useState3(null);
498
+ const [draft, setDraft] = useState3("");
499
+ const [validation, setValidation] = useState3(null);
500
+ const [busy, setBusy] = useState3(null);
501
+ const [toast, setToast] = useState3(null);
502
+ const loadCurrent = useCallback3(async () => {
14
503
  setLoadError(null);
15
504
  try {
16
505
  const res = await callRoute("data", {});
@@ -23,10 +512,10 @@ function Facts() {
23
512
  setLoadError(err instanceof Error ? err.message : String(err));
24
513
  }
25
514
  }, []);
26
- useEffect(() => {
515
+ useEffect3(() => {
27
516
  void loadCurrent();
28
517
  }, [loadCurrent]);
29
- const onCopyPrompt = useCallback(async () => {
518
+ const onCopyPrompt = useCallback3(async () => {
30
519
  setBusy("prompt");
31
520
  setToast(null);
32
521
  try {
@@ -43,7 +532,7 @@ function Facts() {
43
532
  setBusy(null);
44
533
  }
45
534
  }, []);
46
- const onValidate = useCallback(async () => {
535
+ const onValidate = useCallback3(async () => {
47
536
  setBusy("validate");
48
537
  setToast(null);
49
538
  setValidation(null);
@@ -60,7 +549,7 @@ function Facts() {
60
549
  setBusy(null);
61
550
  }
62
551
  }, [draft]);
63
- const onSave = useCallback(async () => {
552
+ const onSave = useCallback3(async () => {
64
553
  setBusy("save");
65
554
  setToast(null);
66
555
  try {
@@ -76,15 +565,15 @@ function Facts() {
76
565
  }
77
566
  }, [draft, loadCurrent]);
78
567
  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: [
568
+ return /* @__PURE__ */ jsxs3("div", { className: "space-y-6 p-4", children: [
569
+ /* @__PURE__ */ jsxs3("header", { children: [
570
+ /* @__PURE__ */ jsx5("h1", { className: "text-xl font-semibold", children: "Truth manifest" }),
571
+ /* @__PURE__ */ jsxs3("p", { className: "mt-1 text-sm text-kumo-subtle", children: [
83
572
  "Author a ",
84
- /* @__PURE__ */ jsx("code", { children: "facts.json" }),
573
+ /* @__PURE__ */ jsx5("code", { children: "facts.json" }),
85
574
  " using your LLM of choice. The plugin frames the question, validates the answer, and stores the result. Read",
86
575
  " ",
87
- /* @__PURE__ */ jsx(
576
+ /* @__PURE__ */ jsx5(
88
577
  "a",
89
578
  {
90
579
  href: "https://github.com/schiste/Aexeo/blob/main/docs/facts-manifest.md",
@@ -98,18 +587,18 @@ function Facts() {
98
587
  "for the philosophy behind this flow."
99
588
  ] })
100
589
  ] }),
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: [
590
+ 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
591
  "Failed to load manifest state: ",
103
592
  loadError
104
593
  ] }),
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." })
594
+ /* @__PURE__ */ jsxs3("section", { className: "rounded border border-kumo-line p-3", children: [
595
+ /* @__PURE__ */ jsx5("h2", { className: "text-sm font-medium", children: "Status" }),
596
+ /* @__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
597
  ] }),
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(
598
+ /* @__PURE__ */ jsxs3("section", { className: "space-y-3 rounded border border-kumo-line p-3", children: [
599
+ /* @__PURE__ */ jsx5("h2", { className: "text-sm font-medium", children: "1. Generate authoring prompt" }),
600
+ /* @__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." }),
601
+ /* @__PURE__ */ jsx5(
113
602
  "button",
114
603
  {
115
604
  type: "button",
@@ -120,9 +609,9 @@ function Facts() {
120
609
  }
121
610
  )
122
611
  ] }),
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(
612
+ /* @__PURE__ */ jsxs3("section", { className: "space-y-3 rounded border border-kumo-line p-3", children: [
613
+ /* @__PURE__ */ jsx5("h2", { className: "text-sm font-medium", children: "2. Paste & validate" }),
614
+ /* @__PURE__ */ jsx5(
126
615
  "textarea",
127
616
  {
128
617
  value: draft,
@@ -133,8 +622,8 @@ function Facts() {
133
622
  className: "w-full rounded border border-kumo-line bg-kumo-tint p-2 font-mono text-xs"
134
623
  }
135
624
  ),
136
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
137
- /* @__PURE__ */ jsx(
625
+ /* @__PURE__ */ jsxs3("div", { className: "flex gap-2", children: [
626
+ /* @__PURE__ */ jsx5(
138
627
  "button",
139
628
  {
140
629
  type: "button",
@@ -144,7 +633,7 @@ function Facts() {
144
633
  children: busy === "validate" ? "Validating\u2026" : "Validate"
145
634
  }
146
635
  ),
147
- /* @__PURE__ */ jsx(
636
+ /* @__PURE__ */ jsx5(
148
637
  "button",
149
638
  {
150
639
  type: "button",
@@ -157,8 +646,8 @@ function Facts() {
157
646
  )
158
647
  ] })
159
648
  ] }),
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 })
649
+ toast !== null && /* @__PURE__ */ jsx5("div", { className: "rounded border border-kumo-line bg-kumo-tint p-3 text-sm", children: toast }),
650
+ validation !== null && /* @__PURE__ */ jsx5(ValidationReport, { result: validation })
162
651
  ] });
163
652
  }
164
653
  function ValidationReport({
@@ -170,13 +659,13 @@ function ValidationReport({
170
659
  const warnMismatches = result.assessment.mismatches.filter(
171
660
  (m) => m.severity === "warning"
172
661
  );
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: [
662
+ return /* @__PURE__ */ jsxs3("section", { className: "space-y-3 rounded border border-kumo-line p-3", children: [
663
+ /* @__PURE__ */ jsx5("h2", { className: "text-sm font-medium", children: "Validation result" }),
664
+ /* @__PURE__ */ jsxs3("div", { className: "text-sm", children: [
665
+ /* @__PURE__ */ jsxs3("div", { children: [
177
666
  "Shape:",
178
667
  " ",
179
- /* @__PURE__ */ jsx("strong", { children: result.validation.valid ? "valid" : "invalid" }),
668
+ /* @__PURE__ */ jsx5("strong", { children: result.validation.valid ? "valid" : "invalid" }),
180
669
  " ",
181
670
  "\xB7 org",
182
671
  " ",
@@ -192,14 +681,14 @@ function ValidationReport({
192
681
  result.validation.forbidden_term_count,
193
682
  " forbidden terms"
194
683
  ] }),
195
- /* @__PURE__ */ jsxs("div", { children: [
684
+ /* @__PURE__ */ jsxs3("div", { children: [
196
685
  "Score: ",
197
- /* @__PURE__ */ jsx("strong", { children: result.assessment.score }),
686
+ /* @__PURE__ */ jsx5("strong", { children: result.assessment.score }),
198
687
  "/",
199
688
  result.assessment.score_ceiling,
200
689
  " \xB7 source =",
201
690
  " ",
202
- /* @__PURE__ */ jsx("code", { children: result.assessment.structured_truth_source }),
691
+ /* @__PURE__ */ jsx5("code", { children: result.assessment.structured_truth_source }),
203
692
  " \xB7",
204
693
  " ",
205
694
  result.assessment.pages_analyzed,
@@ -209,23 +698,23 @@ function ValidationReport({
209
698
  " with schema.org"
210
699
  ] })
211
700
  ] }),
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)) })
701
+ result.validation.errors.length > 0 && /* @__PURE__ */ jsxs3("div", { className: "rounded border border-red-500/40 bg-red-500/10 p-2 text-sm", children: [
702
+ /* @__PURE__ */ jsx5("strong", { children: "Schema errors:" }),
703
+ /* @__PURE__ */ jsx5("ul", { className: "mt-1 list-disc pl-5", children: result.validation.errors.map((e, i) => /* @__PURE__ */ jsx5("li", { children: e }, i)) })
215
704
  ] }),
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)) })
705
+ result.validation.warnings.length > 0 && /* @__PURE__ */ jsxs3("div", { className: "rounded border border-yellow-500/40 bg-yellow-500/10 p-2 text-sm", children: [
706
+ /* @__PURE__ */ jsx5("strong", { children: "Schema warnings:" }),
707
+ /* @__PURE__ */ jsx5("ul", { className: "mt-1 list-disc pl-5", children: result.validation.warnings.map((w, i) => /* @__PURE__ */ jsx5("li", { children: w }, i)) })
219
708
  ] }),
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 })
709
+ errMismatches.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: "Mismatches with site (errors):" }),
711
+ /* @__PURE__ */ jsx5(MismatchList, { items: errMismatches })
223
712
  ] }),
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 })
713
+ warnMismatches.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: "Mismatches with site (warnings):" }),
715
+ /* @__PURE__ */ jsx5(MismatchList, { items: warnMismatches })
227
716
  ] }),
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: [
717
+ 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
718
  "\u2713 Manifest is valid and agrees with on-page schema.org. Click",
230
719
  " ",
231
720
  "Save to persist."
@@ -235,18 +724,18 @@ function ValidationReport({
235
724
  function MismatchList({
236
725
  items
237
726
  }) {
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 }),
727
+ return /* @__PURE__ */ jsx5("ul", { className: "mt-1 list-disc pl-5", children: items.map((m, i) => /* @__PURE__ */ jsxs3("li", { children: [
728
+ /* @__PURE__ */ jsx5("code", { className: "text-xs", children: m.route }),
240
729
  " \xB7 ",
241
730
  m.field,
242
731
  ": expected",
243
732
  " ",
244
- /* @__PURE__ */ jsx("code", { className: "text-xs", children: m.expected }),
733
+ /* @__PURE__ */ jsx5("code", { className: "text-xs", children: m.expected }),
245
734
  ", observed",
246
735
  " ",
247
- /* @__PURE__ */ jsx("code", { className: "text-xs", children: m.observed }),
736
+ /* @__PURE__ */ jsx5("code", { className: "text-xs", children: m.observed }),
248
737
  " ",
249
- /* @__PURE__ */ jsxs("span", { className: "text-xs text-kumo-subtle", children: [
738
+ /* @__PURE__ */ jsxs3("span", { className: "text-xs text-kumo-subtle", children: [
250
739
  "(",
251
740
  m.source,
252
741
  ")"
@@ -254,7 +743,7 @@ function MismatchList({
254
743
  ] }, i)) });
255
744
  }
256
745
  async function callRoute(kind, extra) {
257
- const res = await fetch(`${API_BASE}/plugins/${PLUGIN_ID}/facts`, {
746
+ const res = await fetch(`${API_BASE3}/plugins/${PLUGIN_ID3}/facts`, {
258
747
  method: "POST",
259
748
  headers: {
260
749
  "Content-Type": "application/json",
@@ -272,29 +761,48 @@ async function callRoute(kind, extra) {
272
761
  return body.data;
273
762
  }
274
763
 
764
+ // src/admin/EntityLegitimacy.tsx
765
+ import { jsx as jsx6, jsxs as jsxs4 } from "react/jsx-runtime";
766
+ function EntityLegitimacy() {
767
+ return /* @__PURE__ */ jsx6(
768
+ PillarView,
769
+ {
770
+ layer: "entity_legitimacy",
771
+ extraSlot: /* @__PURE__ */ jsxs4("div", { className: "space-y-3", children: [
772
+ /* @__PURE__ */ jsxs4("section", { className: "rounded border border-kumo-line p-3", children: [
773
+ /* @__PURE__ */ jsx6("h2", { className: "text-sm font-medium", children: "Truth manifest" }),
774
+ /* @__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." }),
775
+ /* @__PURE__ */ jsx6("div", { className: "mt-3", children: /* @__PURE__ */ jsx6(Facts, {}) })
776
+ ] }),
777
+ /* @__PURE__ */ jsx6("section", { className: "rounded border border-kumo-line p-3", children: /* @__PURE__ */ jsx6(EntityPresence, {}) })
778
+ ] })
779
+ }
780
+ );
781
+ }
782
+
275
783
  // 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";
784
+ import { useCallback as useCallback4, useEffect as useEffect4, useState as useState4 } from "react";
785
+ import { Fragment as Fragment2, jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
786
+ var PLUGIN_ID4 = "aexeo-emdash";
787
+ var API_BASE4 = "/_emdash/api";
280
788
  function Findings() {
281
- const [state, setState] = useState2({
789
+ const [state, setState] = useState4({
282
790
  loading: true,
283
791
  error: null,
284
792
  payload: null
285
793
  });
286
- const [refreshing, setRefreshing] = useState2(false);
287
- const [toast, setToast] = useState2(null);
288
- const load = useCallback2(async () => {
794
+ const [refreshing, setRefreshing] = useState4(false);
795
+ const [toast, setToast] = useState4(null);
796
+ const load = useCallback4(async () => {
289
797
  setState((s) => ({ ...s, loading: true, error: null }));
290
798
  try {
291
- const res = await fetch(`${API_BASE2}/plugins/${PLUGIN_ID2}/data`, {
799
+ const res = await fetch(`${API_BASE4}/plugins/${PLUGIN_ID4}/data`, {
292
800
  method: "POST",
293
- headers: csrfHeaders(),
801
+ headers: csrfHeaders2(),
294
802
  body: "{}"
295
803
  });
296
804
  const body = await res.json();
297
- const payload2 = unwrap(body, res);
805
+ const payload2 = unwrap2(body, res);
298
806
  setState({ loading: false, error: null, payload: payload2 });
299
807
  } catch (err) {
300
808
  setState({
@@ -304,17 +812,17 @@ function Findings() {
304
812
  });
305
813
  }
306
814
  }, []);
307
- const refresh = useCallback2(async () => {
815
+ const refresh = useCallback4(async () => {
308
816
  setRefreshing(true);
309
817
  setToast(null);
310
818
  try {
311
- const res = await fetch(`${API_BASE2}/plugins/${PLUGIN_ID2}/refresh`, {
819
+ const res = await fetch(`${API_BASE4}/plugins/${PLUGIN_ID4}/refresh`, {
312
820
  method: "POST",
313
- headers: csrfHeaders(),
821
+ headers: csrfHeaders2(),
314
822
  body: "{}"
315
823
  });
316
824
  const body = await res.json();
317
- const data = unwrap(body, res);
825
+ const data = unwrap2(body, res);
318
826
  setState({ loading: false, error: null, payload: data.payload });
319
827
  if (data.summary) {
320
828
  const { documentsScanned, routesUpdated, totalFindings, errors } = data.summary;
@@ -334,20 +842,20 @@ function Findings() {
334
842
  setRefreshing(false);
335
843
  }
336
844
  }, []);
337
- useEffect2(() => {
845
+ useEffect4(() => {
338
846
  void load();
339
847
  }, [load]);
340
848
  if (state.loading && state.payload === null) {
341
- return /* @__PURE__ */ jsx2("div", { className: "p-4 text-sm text-kumo-subtle", children: "Loading findings\u2026" });
849
+ return /* @__PURE__ */ jsx7("div", { className: "p-4 text-sm text-kumo-subtle", children: "Loading findings\u2026" });
342
850
  }
343
851
  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: [
852
+ return /* @__PURE__ */ jsxs5("div", { className: "p-4", children: [
853
+ /* @__PURE__ */ jsx7("h1", { className: "text-xl font-semibold", children: "SEO findings" }),
854
+ /* @__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
855
  "Failed to load: ",
348
856
  state.error
349
857
  ] }),
350
- /* @__PURE__ */ jsx2(
858
+ /* @__PURE__ */ jsx7(
351
859
  "button",
352
860
  {
353
861
  type: "button",
@@ -360,17 +868,17 @@ function Findings() {
360
868
  }
361
869
  const payload = state.payload;
362
870
  if (payload === null) {
363
- return /* @__PURE__ */ jsx2("div", { className: "p-4 text-sm text-kumo-subtle", children: "No data." });
871
+ return /* @__PURE__ */ jsx7("div", { className: "p-4 text-sm text-kumo-subtle", children: "No data." });
364
872
  }
365
873
  const routesByPath = /* @__PURE__ */ new Map();
366
874
  for (const route of payload.routes) {
367
875
  routesByPath.set(route.route, route);
368
876
  }
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: [
877
+ return /* @__PURE__ */ jsxs5("div", { className: "space-y-4 p-4", children: [
878
+ /* @__PURE__ */ jsxs5("header", { className: "flex items-center justify-between gap-4", children: [
879
+ /* @__PURE__ */ jsxs5("div", { children: [
880
+ /* @__PURE__ */ jsx7("h1", { className: "text-xl font-semibold", children: "SEO findings" }),
881
+ /* @__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
882
  payload.totals.findings,
375
883
  " findings across ",
376
884
  payload.totals.routes,
@@ -382,7 +890,7 @@ function Findings() {
382
890
  " warnings."
383
891
  ] }) })
384
892
  ] }),
385
- /* @__PURE__ */ jsx2(
893
+ /* @__PURE__ */ jsx7(
386
894
  "button",
387
895
  {
388
896
  type: "button",
@@ -393,51 +901,51 @@ function Findings() {
393
901
  }
394
902
  )
395
903
  ] }),
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 })
904
+ toast !== null && /* @__PURE__ */ jsx7("div", { className: "rounded border border-kumo-line bg-kumo-tint p-3 text-sm", children: toast }),
905
+ payload.sitewideFindings.length > 0 && /* @__PURE__ */ jsxs5("section", { className: "rounded border border-kumo-line p-3", children: [
906
+ /* @__PURE__ */ jsx7("h2", { className: "text-sm font-medium", children: "Sitewide" }),
907
+ /* @__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: [
908
+ /* @__PURE__ */ jsx7(SeverityBadge2, { severity: finding.severity }),
909
+ /* @__PURE__ */ jsx7("code", { className: "font-mono text-xs", children: finding.rule_id }),
910
+ /* @__PURE__ */ jsx7("span", { className: "flex-1", children: finding.message })
403
911
  ] }, i)) })
404
912
  ] }),
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" })
913
+ 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: [
914
+ /* @__PURE__ */ jsx7("thead", { className: "bg-kumo-tint", children: /* @__PURE__ */ jsxs5("tr", { children: [
915
+ /* @__PURE__ */ jsx7("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Route" }),
916
+ /* @__PURE__ */ jsx7("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Rule" }),
917
+ /* @__PURE__ */ jsx7("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Severity" }),
918
+ /* @__PURE__ */ jsx7("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Message" })
411
919
  ] }) }),
412
- /* @__PURE__ */ jsx2("tbody", { children: payload.findings.map((finding, i) => {
920
+ /* @__PURE__ */ jsx7("tbody", { children: payload.findings.map((finding, i) => {
413
921
  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 })
922
+ return /* @__PURE__ */ jsxs5("tr", { className: "border-t border-kumo-line", children: [
923
+ /* @__PURE__ */ jsx7("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx7(RouteLinks2, { finding, info: routeInfo }) }),
924
+ /* @__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 }) }),
925
+ /* @__PURE__ */ jsx7("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx7(SeverityBadge2, { severity: finding.severity }) }),
926
+ /* @__PURE__ */ jsx7("td", { className: "px-3 py-2 align-top", children: finding.message })
419
927
  ] }, i);
420
928
  }) })
421
929
  ] }) })
422
930
  ] });
423
931
  }
424
- function RouteLinks({
932
+ function RouteLinks2({
425
933
  finding,
426
934
  info
427
935
  }) {
428
936
  if (info === void 0) {
429
- return /* @__PURE__ */ jsx2("span", { className: "font-mono text-xs", children: finding.route });
937
+ return /* @__PURE__ */ jsx7("span", { className: "font-mono text-xs", children: finding.route });
430
938
  }
431
- return /* @__PURE__ */ jsxs2("span", { className: "space-x-2", children: [
432
- info.editUrl !== "" ? /* @__PURE__ */ jsx2(
939
+ return /* @__PURE__ */ jsxs5("span", { className: "space-x-2", children: [
940
+ info.editUrl !== "" ? /* @__PURE__ */ jsx7(
433
941
  "a",
434
942
  {
435
943
  href: info.editUrl,
436
944
  className: "font-mono text-xs text-kumo-link hover:underline",
437
945
  children: finding.route
438
946
  }
439
- ) : /* @__PURE__ */ jsx2("span", { className: "font-mono text-xs", children: finding.route }),
440
- info.publishedUrl !== "" && /* @__PURE__ */ jsx2(
947
+ ) : /* @__PURE__ */ jsx7("span", { className: "font-mono text-xs", children: finding.route }),
948
+ info.publishedUrl !== "" && /* @__PURE__ */ jsx7(
441
949
  "a",
442
950
  {
443
951
  href: info.publishedUrl,
@@ -447,12 +955,12 @@ function RouteLinks({
447
955
  children: "live \u2197"
448
956
  }
449
957
  ),
450
- info.status !== "" && info.status !== "published" && /* @__PURE__ */ jsx2("span", { className: "text-xs text-kumo-subtle", children: info.status })
958
+ info.status !== "" && info.status !== "published" && /* @__PURE__ */ jsx7("span", { className: "text-xs text-kumo-subtle", children: info.status })
451
959
  ] });
452
960
  }
453
- function SeverityBadge({ severity }) {
961
+ function SeverityBadge2({ severity }) {
454
962
  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(
963
+ return /* @__PURE__ */ jsx7(
456
964
  "span",
457
965
  {
458
966
  className: `inline-block rounded border px-1.5 py-0.5 text-xs font-medium ${classes}`,
@@ -460,7 +968,7 @@ function SeverityBadge({ severity }) {
460
968
  }
461
969
  );
462
970
  }
463
- function csrfHeaders() {
971
+ function csrfHeaders2() {
464
972
  return {
465
973
  "Content-Type": "application/json",
466
974
  // emdash's apiFetch wrapper sets this on every mutation;
@@ -469,7 +977,7 @@ function csrfHeaders() {
469
977
  "X-EmDash-Request": "1"
470
978
  };
471
979
  }
472
- function unwrap(body, res) {
980
+ function unwrap2(body, res) {
473
981
  if (body.error !== void 0) {
474
982
  throw new Error(`${body.error.code}: ${body.error.message}`);
475
983
  }
@@ -479,12 +987,25 @@ function unwrap(body, res) {
479
987
  return body.data;
480
988
  }
481
989
 
990
+ // src/admin/Retrievability.tsx
991
+ import { jsx as jsx8 } from "react/jsx-runtime";
992
+ function Retrievability() {
993
+ return /* @__PURE__ */ jsx8(PillarView, { layer: "retrievability" });
994
+ }
995
+
482
996
  // src/admin.tsx
483
997
  var pages = {
998
+ // Four pillar pages — the canonical organization going forward.
999
+ "/retrievability": Retrievability,
1000
+ "/citability": Citability,
1001
+ "/absorbability": Absorbability,
1002
+ "/entity-legitimacy": EntityLegitimacy,
1003
+ // /findings stays as a cross-pillar flat view for users who want
1004
+ // every finding in one list. Not removed because it's still useful
1005
+ // for triage — "show me everything that's wrong" is a real workflow.
484
1006
  "/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.
1007
+ // /facts kept as an alias so old bookmarks don't 404. Renders the
1008
+ // same Facts component the EntityLegitimacy page composes.
488
1009
  "/facts": Facts
489
1010
  };
490
1011
  export {