@aeptus/aexeo-emdash 0.8.18 → 0.8.19

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.
Files changed (77) hide show
  1. package/CHANGELOG.md +104 -6
  2. package/INSTALL.md +13 -4
  3. package/LICENSE +0 -0
  4. package/README.md +0 -0
  5. package/dist/adapter.d.ts +0 -0
  6. package/dist/adapter.d.ts.map +0 -0
  7. package/dist/admin/Absorbability.d.ts +0 -0
  8. package/dist/admin/Absorbability.d.ts.map +0 -0
  9. package/dist/admin/Accessibility.d.ts +0 -0
  10. package/dist/admin/Accessibility.d.ts.map +0 -0
  11. package/dist/admin/Citability.d.ts +0 -0
  12. package/dist/admin/Citability.d.ts.map +0 -0
  13. package/dist/admin/EntityLegitimacy.d.ts +0 -0
  14. package/dist/admin/EntityLegitimacy.d.ts.map +0 -0
  15. package/dist/admin/EntityPresence.d.ts +0 -0
  16. package/dist/admin/EntityPresence.d.ts.map +0 -0
  17. package/dist/admin/Facts.d.ts +0 -0
  18. package/dist/admin/Facts.d.ts.map +0 -0
  19. package/dist/admin/Findings.d.ts +0 -0
  20. package/dist/admin/Findings.d.ts.map +0 -0
  21. package/dist/admin/PillarView.d.ts +0 -0
  22. package/dist/admin/PillarView.d.ts.map +0 -0
  23. package/dist/admin/Retrievability.d.ts +0 -0
  24. package/dist/admin/Retrievability.d.ts.map +0 -0
  25. package/dist/admin.d.ts +0 -0
  26. package/dist/admin.d.ts.map +0 -0
  27. package/dist/admin.js +0 -0
  28. package/dist/configured.d.ts +0 -0
  29. package/dist/configured.d.ts.map +1 -1
  30. package/dist/configured.js +331 -283
  31. package/dist/data-route.d.ts +0 -0
  32. package/dist/data-route.d.ts.map +0 -0
  33. package/dist/evaluator-sandbox.d.ts +0 -0
  34. package/dist/evaluator-sandbox.d.ts.map +0 -0
  35. package/dist/evaluator.d.ts +0 -0
  36. package/dist/evaluator.d.ts.map +0 -0
  37. package/dist/facts-route.d.ts +0 -0
  38. package/dist/facts-route.d.ts.map +0 -0
  39. package/dist/findings-view.d.ts +136 -0
  40. package/dist/findings-view.d.ts.map +1 -0
  41. package/dist/index.d.ts +0 -0
  42. package/dist/index.d.ts.map +0 -0
  43. package/dist/index.js +13 -2
  44. package/dist/plugin.d.ts +22 -2
  45. package/dist/plugin.d.ts.map +1 -1
  46. package/dist/presence-check.d.ts +0 -0
  47. package/dist/presence-check.d.ts.map +0 -0
  48. package/dist/presence-route.d.ts +0 -0
  49. package/dist/presence-route.d.ts.map +0 -0
  50. package/dist/sandbox-entry.d.ts +2 -22
  51. package/dist/sandbox-entry.d.ts.map +1 -1
  52. package/dist/sandbox-entry.js +410 -353
  53. package/dist/sandbox.d.ts +2 -0
  54. package/dist/sandbox.d.ts.map +1 -1
  55. package/dist/sidecar-url.d.ts +18 -0
  56. package/dist/sidecar-url.d.ts.map +1 -0
  57. package/dist/sidecar.d.ts +0 -0
  58. package/dist/sidecar.d.ts.map +0 -0
  59. package/dist/suppressions.d.ts +0 -0
  60. package/dist/suppressions.d.ts.map +0 -0
  61. package/dist/types.d.ts +0 -0
  62. package/dist/types.d.ts.map +0 -0
  63. package/dist/version.d.ts +1 -1
  64. package/dist/version.d.ts.map +0 -0
  65. package/dist/wasm-init.d.ts +0 -0
  66. package/dist/wasm-init.d.ts.map +0 -0
  67. package/package.json +4 -2
  68. package/wasm/aexeo_emdash_bridge.d.ts +0 -0
  69. package/wasm/aexeo_emdash_bridge.js +0 -0
  70. package/wasm/aexeo_emdash_bridge_bg.d.ts +0 -0
  71. package/wasm/aexeo_emdash_bridge_bg.js +0 -0
  72. package/wasm/aexeo_emdash_bridge_bg.wasm +0 -0
  73. package/wasm/aexeo_emdash_bridge_bg.wasm.d.ts +0 -0
  74. package/dist/indexnow.d.ts +0 -15
  75. package/dist/indexnow.d.ts.map +0 -1
  76. package/dist/mcp.d.ts +0 -28
  77. package/dist/mcp.d.ts.map +0 -1
@@ -198,6 +198,14 @@ function isRecord(value) {
198
198
  }
199
199
 
200
200
  // src/plugin.ts
201
+ var SETTINGS_TOKEN_KEY = "eval_token";
202
+ var PLUGIN_SETTINGS_SCHEMA = {
203
+ [SETTINGS_TOKEN_KEY]: {
204
+ type: "secret",
205
+ label: "EVAL_TOKEN",
206
+ description: "Bearer token for your aexeo-crawl-worker Worker. Encrypted at rest by the emdash host."
207
+ }
208
+ };
201
209
  async function handleAfterSaveConfigured(event, ctx, evaluator, suppressionFilter) {
202
210
  const adapted = await adaptContentItemWithContext(
203
211
  event.content,
@@ -394,6 +402,290 @@ async function readFindings(kv, route) {
394
402
  return stored.findings;
395
403
  }
396
404
 
405
+ // src/findings-view.ts
406
+ function normalizeInteraction(input) {
407
+ if (!input || typeof input !== "object") {
408
+ return { type: "page_load", page: "/findings" };
409
+ }
410
+ const raw = input;
411
+ if (raw.type === "page_load") {
412
+ return {
413
+ type: "page_load",
414
+ page: typeof raw.page === "string" && raw.page.length > 0 ? raw.page : "/findings"
415
+ };
416
+ }
417
+ if (raw.type === "block_action") {
418
+ return {
419
+ type: "block_action",
420
+ action_id: typeof raw.action_id === "string" ? raw.action_id : "",
421
+ ...typeof raw.block_id === "string" ? { block_id: raw.block_id } : {},
422
+ value: raw.value
423
+ };
424
+ }
425
+ if (raw.type === "form_submit") {
426
+ return {
427
+ type: "form_submit",
428
+ action_id: typeof raw.action_id === "string" ? raw.action_id : "",
429
+ ...typeof raw.block_id === "string" ? { block_id: raw.block_id } : {},
430
+ values: raw.values && typeof raw.values === "object" ? raw.values : {}
431
+ };
432
+ }
433
+ return { type: "page_load", page: "/findings" };
434
+ }
435
+ async function handleRefresh(options) {
436
+ let summary;
437
+ try {
438
+ summary = await evaluateAndPersistAll(options.ctx, {
439
+ collections: options.collections,
440
+ evaluator: options.evaluator,
441
+ // Always forwarded. See RefreshOptions.suppressionFilter.
442
+ suppressionFilter: options.suppressionFilter
443
+ });
444
+ } catch (err) {
445
+ const detail = err instanceof Error ? err.message : String(err);
446
+ const message = `Refresh failed: ${detail}`;
447
+ return {
448
+ blocks: [
449
+ { type: "header", text: "SEO findings" },
450
+ {
451
+ type: "banner",
452
+ title: message,
453
+ variant: options.tuning.refreshFailureVariant
454
+ }
455
+ ],
456
+ toast: { message, type: "error" }
457
+ };
458
+ }
459
+ const refreshed = await renderFindingsPage(options.ctx.kv, options.tuning);
460
+ const toastMessage = summary.errors.length === 0 ? `Refreshed ${summary.routesUpdated} routes (${summary.totalFindings} findings across ${summary.documentsScanned} documents)` : `Refresh completed with ${summary.errors.length} errors \u2014 see banner`;
461
+ if (summary.errors.length > 0) {
462
+ refreshed.blocks.unshift({
463
+ type: "banner",
464
+ title: `Refresh issues: ${summary.errors.join(" \u2022 ")}`,
465
+ variant: "alert"
466
+ });
467
+ }
468
+ return {
469
+ ...refreshed,
470
+ toast: {
471
+ message: toastMessage,
472
+ type: summary.errors.length === 0 ? "success" : "info"
473
+ }
474
+ };
475
+ }
476
+ async function readAllFindings(kv) {
477
+ const entries = await kv.list(
478
+ "findings:"
479
+ );
480
+ const out = [];
481
+ for (const entry of entries) {
482
+ if (entry.value === null) {
483
+ continue;
484
+ }
485
+ const route = entry.key.replace(/^findings:/, "");
486
+ for (const finding of entry.value.findings) {
487
+ out.push({ ...finding, document_route: route });
488
+ }
489
+ }
490
+ return out;
491
+ }
492
+ function severityFirst(a, b) {
493
+ const rank = (severity) => severity === "error" ? 0 : 1;
494
+ const diff = rank(a.severity) - rank(b.severity);
495
+ if (diff !== 0) {
496
+ return diff;
497
+ }
498
+ return a.rule_id.localeCompare(b.rule_id);
499
+ }
500
+ function uniqueRoutes(rows) {
501
+ const routes = /* @__PURE__ */ new Set();
502
+ for (const row of rows) {
503
+ routes.add(row.document_route);
504
+ }
505
+ return [...routes].sort();
506
+ }
507
+ async function renderFindingsPage(kv, tuning) {
508
+ const findings = await readAllFindings(kv);
509
+ const errors = findings.filter((finding) => finding.severity === "error");
510
+ const warnings = findings.filter(
511
+ (finding) => finding.severity === "warning"
512
+ );
513
+ const sorted = [...findings].sort(severityFirst);
514
+ const routes = uniqueRoutes(findings);
515
+ const blocks = [
516
+ { type: "header", text: "SEO findings" },
517
+ {
518
+ type: "context",
519
+ text: findings.length === 0 ? "No findings yet \u2014 click Refresh to evaluate the site." : `${findings.length} findings across ${routes.length} routes \u2014 ${errors.length} errors, ${warnings.length} warnings.`
520
+ },
521
+ { type: "divider" },
522
+ {
523
+ type: "actions",
524
+ elements: [
525
+ // Refresh evaluates every configured entry. Pressing it lists
526
+ // content via the live in-request bridge, evaluates it, and
527
+ // writes findings back to KV (with suppressions applied).
528
+ {
529
+ type: "button",
530
+ label: "Refresh",
531
+ action_id: "refresh_findings",
532
+ style: "primary"
533
+ },
534
+ { type: "button", label: "All", action_id: "filter:all" },
535
+ { type: "button", label: "Errors only", action_id: "filter:errors" },
536
+ {
537
+ type: "button",
538
+ label: "Warnings only",
539
+ action_id: "filter:warnings"
540
+ }
541
+ ]
542
+ },
543
+ sorted.length === 0 ? {
544
+ type: "context",
545
+ text: tuning.emptyFindingsText
546
+ } : findingsTable(sorted)
547
+ ];
548
+ if (routes.length > 0) {
549
+ blocks.push({
550
+ type: "form",
551
+ fields: [
552
+ {
553
+ type: "select",
554
+ action_id: "route_picker",
555
+ label: "Document to inspect",
556
+ options: routes.map((route) => ({ label: route, value: route }))
557
+ }
558
+ ],
559
+ submit: { label: "View document SEO", action_id: "view_document" }
560
+ });
561
+ }
562
+ return { blocks };
563
+ }
564
+ function findingsTable(rows) {
565
+ return {
566
+ type: "table",
567
+ columns: [
568
+ { key: "route", label: "Route" },
569
+ { key: "rule", label: "Rule", format: "code" },
570
+ { key: "severity", label: "Severity", format: "badge" },
571
+ { key: "message", label: "Message" }
572
+ ],
573
+ rows: rows.map((row) => ({
574
+ route: row.document_route,
575
+ rule: row.rule_id,
576
+ severity: row.severity,
577
+ message: row.message
578
+ }))
579
+ };
580
+ }
581
+ async function renderScoreWidget(kv, tuning) {
582
+ const documents = await readAllDocuments(kv);
583
+ if (documents.length === 0) {
584
+ return {
585
+ blocks: [
586
+ { type: "header", text: "SEO score" },
587
+ {
588
+ type: "context",
589
+ text: tuning.emptyScoreText
590
+ }
591
+ ]
592
+ };
593
+ }
594
+ const score = await tuning.score(documents, kv);
595
+ const blocks = [
596
+ {
597
+ type: "stats",
598
+ items: [
599
+ { label: "Overall", value: `${score.overall_score}` },
600
+ { label: "Citation", value: `${score.citation_readiness_score}` },
601
+ {
602
+ label: tuning.truthLabel(score),
603
+ value: `${score.truth_consistency_score}`
604
+ },
605
+ { label: "Answers", value: `${score.answer_pack_score}` }
606
+ ]
607
+ }
608
+ ];
609
+ if (score.overall_score < 60) {
610
+ blocks.unshift({
611
+ type: "banner",
612
+ title: `Site score is ${score.overall_score} \u2014 below the 60 quality threshold`,
613
+ variant: "alert"
614
+ });
615
+ }
616
+ if (score.blockers.length > 0) {
617
+ blocks.push({
618
+ type: "context",
619
+ text: topBlockersLine(score)
620
+ });
621
+ }
622
+ return { blocks };
623
+ }
624
+ function topBlockersLine(score) {
625
+ const top = score.blockers.slice(0, 3).map((blocker) => blocker.message);
626
+ if (top.length === 0) {
627
+ return "No blockers identified.";
628
+ }
629
+ return `Top blockers: ${top.join(" \u2022 ")}`;
630
+ }
631
+ async function renderDocumentPanel(kv, route) {
632
+ if (route === void 0) {
633
+ return {
634
+ blocks: [
635
+ { type: "header", text: "Document SEO" },
636
+ {
637
+ type: "context",
638
+ text: "Pick a document from the SEO findings list to see its rule findings here."
639
+ }
640
+ ]
641
+ };
642
+ }
643
+ const findings = await readFindings(kv, route);
644
+ const errors = findings.filter((finding) => finding.severity === "error");
645
+ const warnings = findings.filter(
646
+ (finding) => finding.severity === "warning"
647
+ );
648
+ const sorted = [...findings].sort(severityFirst);
649
+ return {
650
+ blocks: [
651
+ { type: "header", text: `Document SEO \u2014 ${route}` },
652
+ {
653
+ type: "context",
654
+ text: findings.length === 0 ? `No findings for ${route} \u2014 the document is currently clean.` : `${findings.length} findings \u2014 ${errors.length} errors, ${warnings.length} warnings.`
655
+ },
656
+ ...findings.length === 0 ? [] : [{ type: "divider" }, documentFindingsTable(sorted)]
657
+ ]
658
+ };
659
+ }
660
+ function documentFindingsTable(findings) {
661
+ return {
662
+ type: "table",
663
+ columns: [
664
+ { key: "rule", label: "Rule", format: "code" },
665
+ { key: "severity", label: "Severity", format: "badge" },
666
+ { key: "message", label: "Message" },
667
+ { key: "block", label: "Block", format: "code" }
668
+ ],
669
+ rows: findings.map((finding) => ({
670
+ rule: finding.rule_id,
671
+ severity: finding.severity,
672
+ message: finding.message,
673
+ // The bridge stamps every Portable Text block with id + data-pt-key
674
+ // when rendering; surfacing the path:line locator here lets authors
675
+ // locate the failing block via the editor's ⌘-F.
676
+ block: `${finding.path}:${finding.line}`
677
+ }))
678
+ };
679
+ }
680
+ function notFound(page) {
681
+ return {
682
+ blocks: [
683
+ { type: "header", text: "Not found" },
684
+ { type: "context", text: `unknown page: ${page}` }
685
+ ]
686
+ };
687
+ }
688
+
397
689
  // wasm/aexeo_emdash_bridge_bg.js
398
690
  var aexeo_emdash_bridge_bg_exports = {};
399
691
  __export(aexeo_emdash_bridge_bg_exports, {
@@ -1012,11 +1304,11 @@ async function checkWikipedia(input, fetcher) {
1012
1304
  const descriptions = body[2];
1013
1305
  const urls = body[3];
1014
1306
  if (!Array.isArray(titles) || !Array.isArray(urls) || titles.length === 0) {
1015
- return notFound("wikipedia");
1307
+ return notFound2("wikipedia");
1016
1308
  }
1017
1309
  const title = String(titles[0]);
1018
1310
  if (!fuzzyMatch(title, input.name)) {
1019
- return notFound("wikipedia");
1311
+ return notFound2("wikipedia");
1020
1312
  }
1021
1313
  const articleUrl = String(urls[0] ?? "");
1022
1314
  const description = Array.isArray(descriptions) ? String(descriptions[0] ?? "") : "";
@@ -1042,11 +1334,11 @@ async function checkWikidata(input, fetcher) {
1042
1334
  const body = await res.json();
1043
1335
  const top = body.search?.[0];
1044
1336
  if (!top || typeof top.id !== "string") {
1045
- return notFound("wikidata");
1337
+ return notFound2("wikidata");
1046
1338
  }
1047
1339
  const label = top.label ?? input.name;
1048
1340
  if (!fuzzyMatch(label, input.name)) {
1049
- return notFound("wikidata");
1341
+ return notFound2("wikidata");
1050
1342
  }
1051
1343
  return found("wikidata", {
1052
1344
  label: `${top.id}${top.label ? ` \u2014 ${top.label}` : ""}`,
@@ -1072,7 +1364,7 @@ async function checkGitHub(input, fetcher) {
1072
1364
  // it the API can return either v3 or experimental shapes.
1073
1365
  headers: { Accept: "application/vnd.github+json" }
1074
1366
  });
1075
- if (res.status === 404) return notFound("github");
1367
+ if (res.status === 404) return notFound2("github");
1076
1368
  if (res.status === 403 || res.status === 429) {
1077
1369
  return unreachable("github", "rate-limited (try Refresh later)");
1078
1370
  }
@@ -1104,7 +1396,7 @@ async function checkRdap(input, fetcher) {
1104
1396
  const url = `https://rdap.org/domain/${encodeURIComponent(host)}`;
1105
1397
  try {
1106
1398
  const res = await timedFetch(fetcher, url);
1107
- if (res.status === 404) return notFound("rdap");
1399
+ if (res.status === 404) return notFound2("rdap");
1108
1400
  if (!res.ok) return unreachable("rdap", `HTTP ${res.status}`);
1109
1401
  const body = await res.json();
1110
1402
  const reg = (body.events ?? []).find(
@@ -1141,13 +1433,13 @@ async function checkCommonCrawl(input, fetcher) {
1141
1433
  url.searchParams.set("limit", "1");
1142
1434
  try {
1143
1435
  const res = await timedFetch(fetcher, url.toString());
1144
- if (res.status === 404) return notFound("common_crawl");
1436
+ if (res.status === 404) return notFound2("common_crawl");
1145
1437
  if (!res.ok) return unreachable("common_crawl", `HTTP ${res.status}`);
1146
1438
  const text = (await res.text()).trim();
1147
- if (text.length === 0) return notFound("common_crawl");
1439
+ if (text.length === 0) return notFound2("common_crawl");
1148
1440
  let firstLine = text.split("\n")[0];
1149
1441
  if (firstLine === void 0 || firstLine.length === 0) {
1150
- return notFound("common_crawl");
1442
+ return notFound2("common_crawl");
1151
1443
  }
1152
1444
  let entry;
1153
1445
  try {
@@ -1189,7 +1481,7 @@ function found(source, details) {
1189
1481
  if (details.extra !== void 0) result.extra = details.extra;
1190
1482
  return result;
1191
1483
  }
1192
- function notFound(source) {
1484
+ function notFound2(source) {
1193
1485
  return { source, status: "not_found", checkedAt: nowIso() };
1194
1486
  }
1195
1487
  function unreachable(source, error) {
@@ -1258,7 +1550,7 @@ async function handlePresenceRoute(ctx) {
1258
1550
  case "data":
1259
1551
  return await handleData2(ctx);
1260
1552
  case "refresh":
1261
- return await handleRefresh(ctx);
1553
+ return await handleRefresh2(ctx);
1262
1554
  default:
1263
1555
  throw PluginRouteError2.badRequest(
1264
1556
  `unknown presence route kind: ${String(body.kind)}`
@@ -1285,7 +1577,7 @@ async function handleData2(ctx) {
1285
1577
  const state = ageMs !== null && ageMs > CACHE_TTL_MS ? "stale" : "fresh";
1286
1578
  return payload(state, cached.results, cached.generatedAt, entity.name);
1287
1579
  }
1288
- async function handleRefresh(ctx) {
1580
+ async function handleRefresh2(ctx) {
1289
1581
  const manifest = await readStoredFacts(ctx.kv);
1290
1582
  if (manifest === null) {
1291
1583
  return payload("no_manifest", [], null, null);
@@ -1373,7 +1665,7 @@ function globToMatcher(pattern) {
1373
1665
  }
1374
1666
 
1375
1667
  // src/version.ts
1376
- var PACKAGE_VERSION = "0.8.18";
1668
+ var PACKAGE_VERSION = "0.8.19";
1377
1669
 
1378
1670
  // src/configured.ts
1379
1671
  import { definePlugin } from "emdash";
@@ -1459,7 +1751,7 @@ function createPlugin(options = {}) {
1459
1751
  // one consumer of `collections`. Bind the resolved list here so
1460
1752
  // every request handles the same set without re-reading options.
1461
1753
  admin: {
1462
- handler: (ctx) => handleAdminRoute(ctx, collections)
1754
+ handler: (ctx) => handleAdminRoute(ctx, collections, suppressionFilter)
1463
1755
  },
1464
1756
  // JSON data endpoint for the React adminEntry. Two routes:
1465
1757
  // POST /_emdash/api/plugins/aexeo-emdash/data — read current findings
@@ -1529,246 +1821,59 @@ function createPlugin(options = {}) {
1529
1821
  });
1530
1822
  }
1531
1823
  var configured_default = createPlugin;
1532
- function normalizeInteraction(input) {
1533
- if (!input || typeof input !== "object") {
1534
- return { type: "page_load", page: "/findings" };
1535
- }
1536
- const raw = input;
1537
- if (raw.type === "page_load") {
1538
- return {
1539
- type: "page_load",
1540
- page: typeof raw.page === "string" && raw.page.length > 0 ? raw.page : "/findings"
1541
- };
1542
- }
1543
- if (raw.type === "block_action") {
1544
- return {
1545
- type: "block_action",
1546
- action_id: typeof raw.action_id === "string" ? raw.action_id : "",
1547
- ...typeof raw.block_id === "string" ? { block_id: raw.block_id } : {},
1548
- value: raw.value
1549
- };
1550
- }
1551
- if (raw.type === "form_submit") {
1552
- return {
1553
- type: "form_submit",
1554
- action_id: typeof raw.action_id === "string" ? raw.action_id : "",
1555
- ...typeof raw.block_id === "string" ? { block_id: raw.block_id } : {},
1556
- values: raw.values && typeof raw.values === "object" ? raw.values : {}
1557
- };
1558
- }
1559
- return { type: "page_load", page: "/findings" };
1560
- }
1561
- async function handleAdminRoute(ctx, collections) {
1824
+ var adminViewTuning = {
1825
+ emptyFindingsText: "Once a Refresh runs, findings will list here.",
1826
+ emptyScoreText: "No documents indexed \u2014 click Refresh on the Aexeo findings page.",
1827
+ refreshFailureVariant: "error",
1828
+ score: async (documents, kv) => scoreLocally(documents, await readStoredFacts(kv)),
1829
+ truthLabel: formatTruthLabel
1830
+ };
1831
+ async function handleAdminRoute(ctx, collections, suppressionFilter) {
1562
1832
  const body = normalizeInteraction(ctx.input);
1563
1833
  if (body.type === "page_load") {
1564
1834
  return handlePageLoad(ctx, body.page);
1565
1835
  }
1566
1836
  if (body.type === "block_action") {
1567
1837
  if (body.action_id === "view_document" && typeof body.value === "string") {
1568
- return renderDocumentPanel(ctx, body.value);
1838
+ return renderDocumentPanel(ctx.kv, body.value);
1569
1839
  }
1570
1840
  if (body.action_id === "refresh_findings") {
1571
- return handleRefresh2(ctx, collections);
1841
+ return handleRefresh({
1842
+ ctx,
1843
+ collections,
1844
+ evaluator: inProcessEvaluator,
1845
+ suppressionFilter,
1846
+ tuning: adminViewTuning
1847
+ });
1572
1848
  }
1573
1849
  if (body.action_id.startsWith("filter:")) {
1574
- return renderFindingsPage(ctx);
1850
+ return renderFindingsPage(ctx.kv, adminViewTuning);
1575
1851
  }
1576
- return notFound2(body.action_id);
1852
+ return notFound(body.action_id);
1577
1853
  }
1578
1854
  if (body.type === "form_submit") {
1579
1855
  if (body.action_id === "view_document") {
1580
1856
  const picked = body.values["route_picker"];
1581
1857
  if (typeof picked === "string" && picked.length > 0) {
1582
- return renderDocumentPanel(ctx, picked);
1858
+ return renderDocumentPanel(ctx.kv, picked);
1583
1859
  }
1584
1860
  }
1585
- return renderFindingsPage(ctx);
1861
+ return renderFindingsPage(ctx.kv, adminViewTuning);
1586
1862
  }
1587
- return renderFindingsPage(ctx);
1863
+ return renderFindingsPage(ctx.kv, adminViewTuning);
1588
1864
  }
1589
1865
  async function handlePageLoad(ctx, page) {
1590
1866
  const normalized = page.startsWith("/") ? page.slice(1) : page;
1591
1867
  if (normalized === "" || normalized === "findings") {
1592
- return renderFindingsPage(ctx);
1868
+ return renderFindingsPage(ctx.kv, adminViewTuning);
1593
1869
  }
1594
1870
  if (normalized === "widget:aexeo-score") {
1595
- return renderScoreWidget(ctx);
1871
+ return renderScoreWidget(ctx.kv, adminViewTuning);
1596
1872
  }
1597
1873
  if (normalized === "document") {
1598
- return renderDocumentPanel(ctx);
1874
+ return renderDocumentPanel(ctx.kv);
1599
1875
  }
1600
- return notFound2(page);
1601
- }
1602
- async function handleRefresh2(ctx, collections) {
1603
- let summary;
1604
- try {
1605
- summary = await evaluateAndPersistAll(ctx, {
1606
- evaluator: inProcessEvaluator,
1607
- collections
1608
- });
1609
- } catch (err) {
1610
- const detail = err instanceof Error ? err.message : String(err);
1611
- return {
1612
- blocks: [
1613
- { type: "header", text: "SEO findings" },
1614
- {
1615
- type: "banner",
1616
- title: `Refresh failed: ${detail}`,
1617
- variant: "error"
1618
- }
1619
- ],
1620
- toast: { message: `Refresh failed: ${detail}`, type: "error" }
1621
- };
1622
- }
1623
- const refreshed = await renderFindingsPage(ctx);
1624
- const toastMessage = summary.errors.length === 0 ? `Refreshed ${summary.routesUpdated} routes (${summary.totalFindings} findings across ${summary.documentsScanned} documents)` : `Refresh completed with ${summary.errors.length} errors \u2014 see banner`;
1625
- if (summary.errors.length > 0) {
1626
- refreshed.blocks.unshift({
1627
- type: "banner",
1628
- title: `Refresh issues: ${summary.errors.join(" \u2022 ")}`,
1629
- variant: "alert"
1630
- });
1631
- }
1632
- return {
1633
- ...refreshed,
1634
- toast: {
1635
- message: toastMessage,
1636
- type: summary.errors.length === 0 ? "success" : "info"
1637
- }
1638
- };
1639
- }
1640
- async function readAllFindings(ctx) {
1641
- const entries = await ctx.kv.list(
1642
- "findings:"
1643
- );
1644
- const out = [];
1645
- for (const entry of entries) {
1646
- if (entry.value === null) continue;
1647
- const route = entry.key.replace(/^findings:/, "");
1648
- for (const finding of entry.value.findings) {
1649
- out.push({ ...finding, document_route: route });
1650
- }
1651
- }
1652
- return out;
1653
- }
1654
- function severityFirst(a, b) {
1655
- const rank = (severity) => severity === "error" ? 0 : 1;
1656
- const diff = rank(a.severity) - rank(b.severity);
1657
- if (diff !== 0) return diff;
1658
- return a.rule_id.localeCompare(b.rule_id);
1659
- }
1660
- function uniqueRoutes(rows) {
1661
- const set = /* @__PURE__ */ new Set();
1662
- for (const row of rows) set.add(row.document_route);
1663
- return [...set].sort();
1664
- }
1665
- async function renderFindingsPage(ctx) {
1666
- const findings = await readAllFindings(ctx);
1667
- const errors = findings.filter((f) => f.severity === "error");
1668
- const warnings = findings.filter((f) => f.severity === "warning");
1669
- const sorted = [...findings].sort(severityFirst);
1670
- const routes = uniqueRoutes(findings);
1671
- const blocks = [
1672
- { type: "header", text: "SEO findings" },
1673
- {
1674
- type: "context",
1675
- text: findings.length === 0 ? "No findings yet \u2014 click Refresh to evaluate the site." : `${findings.length} findings across ${routes.length} routes \u2014 ${errors.length} errors, ${warnings.length} warnings.`
1676
- },
1677
- { type: "divider" },
1678
- {
1679
- type: "actions",
1680
- elements: [
1681
- {
1682
- type: "button",
1683
- label: "Refresh",
1684
- action_id: "refresh_findings",
1685
- style: "primary"
1686
- },
1687
- { type: "button", label: "All", action_id: "filter:all" },
1688
- { type: "button", label: "Errors only", action_id: "filter:errors" },
1689
- {
1690
- type: "button",
1691
- label: "Warnings only",
1692
- action_id: "filter:warnings"
1693
- }
1694
- ]
1695
- },
1696
- sorted.length === 0 ? {
1697
- type: "context",
1698
- text: "Once a Refresh runs, findings will list here."
1699
- } : findingsTable(sorted)
1700
- ];
1701
- if (routes.length > 0) {
1702
- blocks.push({
1703
- type: "form",
1704
- fields: [
1705
- {
1706
- type: "select",
1707
- action_id: "route_picker",
1708
- label: "Document to inspect",
1709
- options: routes.map((route) => ({ label: route, value: route }))
1710
- }
1711
- ],
1712
- submit: { label: "View document SEO", action_id: "view_document" }
1713
- });
1714
- }
1715
- return { blocks };
1716
- }
1717
- function findingsTable(rows) {
1718
- return {
1719
- type: "table",
1720
- columns: [
1721
- { key: "route", label: "Route" },
1722
- { key: "rule", label: "Rule", format: "code" },
1723
- { key: "severity", label: "Severity", format: "badge" },
1724
- { key: "message", label: "Message" }
1725
- ],
1726
- rows: rows.map((row) => ({
1727
- route: row.document_route,
1728
- rule: row.rule_id,
1729
- severity: row.severity,
1730
- message: row.message
1731
- }))
1732
- };
1733
- }
1734
- async function renderScoreWidget(ctx) {
1735
- const documents = await readAllDocuments(ctx.kv);
1736
- if (documents.length === 0) {
1737
- return {
1738
- blocks: [
1739
- { type: "header", text: "SEO score" },
1740
- {
1741
- type: "context",
1742
- text: "No documents indexed \u2014 click Refresh on the Aexeo findings page."
1743
- }
1744
- ]
1745
- };
1746
- }
1747
- const facts = await readStoredFacts(ctx.kv);
1748
- const score = await scoreLocally(documents, facts);
1749
- const truthLabel = formatTruthLabel(score);
1750
- const blocks = [
1751
- {
1752
- type: "stats",
1753
- items: [
1754
- { label: "Overall", value: `${score.overall_score}` },
1755
- { label: "Citation", value: `${score.citation_readiness_score}` },
1756
- { label: truthLabel, value: `${score.truth_consistency_score}` },
1757
- { label: "Answers", value: `${score.answer_pack_score}` }
1758
- ]
1759
- }
1760
- ];
1761
- if (score.overall_score < 60) {
1762
- blocks.unshift({
1763
- type: "banner",
1764
- title: `Site score is ${score.overall_score} \u2014 below the 60 quality threshold`,
1765
- variant: "alert"
1766
- });
1767
- }
1768
- if (score.blockers.length > 0) {
1769
- blocks.push({ type: "context", text: topBlockersLine(score) });
1770
- }
1771
- return { blocks };
1876
+ return notFound(page);
1772
1877
  }
1773
1878
  async function scoreLocally(documents, manifest = null) {
1774
1879
  const manifestJson = manifest === null ? null : JSON.stringify(manifest);
@@ -1789,63 +1894,6 @@ function formatTruthLabel(score) {
1789
1894
  return "Truth";
1790
1895
  }
1791
1896
  }
1792
- function topBlockersLine(score) {
1793
- const top = score.blockers.slice(0, 3).map((b) => b.message);
1794
- if (top.length === 0) return "No blockers identified.";
1795
- return `Top blockers: ${top.join(" \u2022 ")}`;
1796
- }
1797
- async function renderDocumentPanel(ctx, route) {
1798
- if (route === void 0) {
1799
- return {
1800
- blocks: [
1801
- { type: "header", text: "Document SEO" },
1802
- {
1803
- type: "context",
1804
- text: "Pick a document from the SEO findings list to see its rule findings here."
1805
- }
1806
- ]
1807
- };
1808
- }
1809
- const findings = await readFindings(ctx.kv, route);
1810
- const errors = findings.filter((f) => f.severity === "error");
1811
- const warnings = findings.filter((f) => f.severity === "warning");
1812
- const sorted = [...findings].sort(severityFirst);
1813
- return {
1814
- blocks: [
1815
- { type: "header", text: `Document SEO \u2014 ${route}` },
1816
- {
1817
- type: "context",
1818
- text: findings.length === 0 ? `No findings for ${route} \u2014 the document is currently clean.` : `${findings.length} findings \u2014 ${errors.length} errors, ${warnings.length} warnings.`
1819
- },
1820
- ...findings.length === 0 ? [] : [{ type: "divider" }, documentFindingsTable(sorted)]
1821
- ]
1822
- };
1823
- }
1824
- function documentFindingsTable(findings) {
1825
- return {
1826
- type: "table",
1827
- columns: [
1828
- { key: "rule", label: "Rule", format: "code" },
1829
- { key: "severity", label: "Severity", format: "badge" },
1830
- { key: "message", label: "Message" },
1831
- { key: "block", label: "Block", format: "code" }
1832
- ],
1833
- rows: findings.map((finding) => ({
1834
- rule: finding.rule_id,
1835
- severity: finding.severity,
1836
- message: finding.message,
1837
- block: `${finding.path}:${finding.line}`
1838
- }))
1839
- };
1840
- }
1841
- function notFound2(page) {
1842
- return {
1843
- blocks: [
1844
- { type: "header", text: "Not found" },
1845
- { type: "context", text: `unknown page: ${page}` }
1846
- ]
1847
- };
1848
- }
1849
1897
  export {
1850
1898
  createPlugin,
1851
1899
  configured_default as default