@aeptus/aexeo-emdash 0.1.2 → 0.2.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/CHANGELOG.md CHANGED
@@ -6,6 +6,59 @@ and the project follows [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.2.0] - 2026-04-28
10
+
11
+ Adds the long-asked clickable routes on the findings page. Block
12
+ Kit can't render external links in any element type, so /findings
13
+ now ships as a React component registered via the plugin's
14
+ `adminEntry`. /document and the dashboard widget continue to use
15
+ Block Kit (no link needs there).
16
+
17
+ **Compatibility:** verified against emdash `0.7.0` and `0.8.0`.
18
+
19
+ ### Added
20
+
21
+ - **Clickable routes on /findings.** Each row's route links to the
22
+ emdash edit URL for that document
23
+ (`/_emdash/admin/content/<collection>/<id>`). Published documents
24
+ also get a "live ↗" link to the public URL
25
+ (`<emdash:site_url><route>`). Drafts surface their status
26
+ (`draft`, `scheduled`, etc.) inline so editorial state is visible
27
+ at a glance.
28
+ - **`adminEntry` field** on the configured-mode plugin descriptor.
29
+ Points at `@aeptus/aexeo-emdash/admin`, which exports `pages`
30
+ registering the `<Findings/>` React component for `/findings`.
31
+ emdash's `usePluginPage` hook picks this up and renders the
32
+ React component in place of the Block Kit fallback.
33
+ - **Two new HTTP routes** the React component consumes:
34
+ - `POST /_emdash/api/plugins/aexeo-seogeo/data` — read current
35
+ findings + per-route metadata + computed URLs (no
36
+ re-evaluation).
37
+ - `POST /_emdash/api/plugins/aexeo-seogeo/refresh` — sweep the
38
+ configured collections, write findings to KV, return the same
39
+ payload as `/data` plus a refresh summary.
40
+ Both routes return JSON in the shape exported as `FindingsPayload`
41
+ from `dist/data-route.d.ts`. The shape is the wire contract
42
+ between plugin and admin component and is intentionally
43
+ additive — fields will be appended in future versions, never
44
+ renamed or removed.
45
+ - `react` declared as an optional peer dependency
46
+ (`^18.0.0 || ^19.0.0`). Optional because plugins that don't use
47
+ the React findings page (e.g. ones that override `adminEntry`)
48
+ don't need it; emdash hosts that DO render adminEntry components
49
+ always have React installed already.
50
+
51
+ ### Changed
52
+
53
+ - `documentKey(route)` KV entries now store
54
+ `{ document, meta: { id, collection, status, slug, title } }`
55
+ instead of the bare `EmdashDocument`. The new metadata is what
56
+ the React component reads to construct edit / public URLs without
57
+ an extra DB round-trip per row. Pre-0.2.0 entries are
58
+ backwards-compatible — `readAllStoredDocuments` synthesizes a
59
+ minimal meta blob for legacy entries so a single corrupted-shape
60
+ row doesn't break the page.
61
+
9
62
  ## [0.1.2] - 2026-04-28
10
63
 
11
64
  Functional patch. Three bugs flagged by the aeptus web team after
@@ -100,7 +153,8 @@ First public release.
100
153
  `astro.config.mjs` for the WASM import to resolve to a
101
154
  precompiled `WebAssembly.Module`. Not optional.
102
155
 
103
- [Unreleased]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.2...HEAD
156
+ [Unreleased]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.2.0...HEAD
157
+ [0.2.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.2...aexeo-emdash-v0.2.0
104
158
  [0.1.2]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.1...aexeo-emdash-v0.1.2
105
159
  [0.1.1]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.0...aexeo-emdash-v0.1.1
106
160
  [0.1.0]: https://github.com/schiste/Aexeo/releases/tag/aexeo-emdash-v0.1.0
package/dist/adapter.d.ts CHANGED
@@ -12,5 +12,17 @@ export interface EmdashContentItem {
12
12
  canonical?: string | null;
13
13
  };
14
14
  }
15
+ export interface EmdashContentMeta {
16
+ id: string;
17
+ collection: string;
18
+ slug: string | null;
19
+ status: string;
20
+ title: string;
21
+ }
22
+ export interface AdaptedContent {
23
+ document: EmdashDocument;
24
+ meta: EmdashContentMeta;
25
+ }
15
26
  export declare function contentItemToEmdashDocument(content: EmdashContentItem): EmdashDocument;
27
+ export declare function adaptContentItem(content: EmdashContentItem): AdaptedContent;
16
28
  //# sourceMappingURL=adapter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAC;AAmBpE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,iBAAiB,GACzB,cAAc,CAyBhB"}
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAqB,MAAM,YAAY,CAAC;AAmBpE,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC9B,GAAG,CAAC,EAAE;QACJ,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACtB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QAC5B,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;KAC3B,CAAC;CACH;AAOD,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC;IAIf,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,cAAc,CAAC;IACzB,IAAI,EAAE,iBAAiB,CAAC;CACzB;AAED,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,iBAAiB,GACzB,cAAc,CAEhB;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,iBAAiB,GAAG,cAAc,CAkC3E"}
@@ -0,0 +1,3 @@
1
+ import * as React from "react";
2
+ export declare function Findings(): React.JSX.Element;
3
+ //# sourceMappingURL=Findings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Findings.d.ts","sourceRoot":"","sources":["../../src/admin/Findings.tsx"],"names":[],"mappings":"AA2BA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAa/B,wBAAgB,QAAQ,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CA2L5C"}
@@ -0,0 +1,5 @@
1
+ import { Findings } from "./admin/Findings.js";
2
+ export declare const pages: {
3
+ "/findings": typeof Findings;
4
+ };
5
+ //# sourceMappingURL=admin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C,eAAO,MAAM,KAAK;;CAEjB,CAAC"}
package/dist/admin.js ADDED
@@ -0,0 +1,214 @@
1
+ // src/admin/Findings.tsx
2
+ import { useCallback, useEffect, useState } from "react";
3
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
4
+ var PLUGIN_ID = "aexeo-seogeo";
5
+ var API_BASE = "/_emdash/api";
6
+ function Findings() {
7
+ const [state, setState] = useState({
8
+ loading: true,
9
+ error: null,
10
+ payload: null
11
+ });
12
+ const [refreshing, setRefreshing] = useState(false);
13
+ const [toast, setToast] = useState(null);
14
+ const load = useCallback(async () => {
15
+ setState((s) => ({ ...s, loading: true, error: null }));
16
+ try {
17
+ const res = await fetch(`${API_BASE}/plugins/${PLUGIN_ID}/data`, {
18
+ method: "POST",
19
+ headers: csrfHeaders(),
20
+ body: "{}"
21
+ });
22
+ const body = await res.json();
23
+ const payload2 = unwrap(body, res);
24
+ setState({ loading: false, error: null, payload: payload2 });
25
+ } catch (err) {
26
+ setState({
27
+ loading: false,
28
+ error: err instanceof Error ? err.message : String(err),
29
+ payload: null
30
+ });
31
+ }
32
+ }, []);
33
+ const refresh = useCallback(async () => {
34
+ setRefreshing(true);
35
+ setToast(null);
36
+ try {
37
+ const res = await fetch(`${API_BASE}/plugins/${PLUGIN_ID}/refresh`, {
38
+ method: "POST",
39
+ headers: csrfHeaders(),
40
+ body: "{}"
41
+ });
42
+ const body = await res.json();
43
+ const data = unwrap(body, res);
44
+ setState({ loading: false, error: null, payload: data.payload });
45
+ if (data.summary) {
46
+ const { documentsScanned, routesUpdated, totalFindings, errors } = data.summary;
47
+ if (errors.length > 0) {
48
+ setToast(`Refresh completed with ${errors.length} errors: ${errors.join(" \u2022 ")}`);
49
+ } else {
50
+ setToast(
51
+ `Refreshed ${routesUpdated} routes (${totalFindings} findings across ${documentsScanned} documents)`
52
+ );
53
+ }
54
+ }
55
+ } catch (err) {
56
+ setToast(
57
+ `Refresh failed: ${err instanceof Error ? err.message : String(err)}`
58
+ );
59
+ } finally {
60
+ setRefreshing(false);
61
+ }
62
+ }, []);
63
+ useEffect(() => {
64
+ void load();
65
+ }, [load]);
66
+ if (state.loading && state.payload === null) {
67
+ return /* @__PURE__ */ jsx("div", { className: "p-4 text-sm text-kumo-subtle", children: "Loading findings\u2026" });
68
+ }
69
+ if (state.error !== null) {
70
+ return /* @__PURE__ */ jsxs("div", { className: "p-4", children: [
71
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "SEO findings" }),
72
+ /* @__PURE__ */ jsxs("p", { className: "mt-2 rounded border border-red-500/40 bg-red-500/10 p-3 text-sm text-red-700", children: [
73
+ "Failed to load: ",
74
+ state.error
75
+ ] }),
76
+ /* @__PURE__ */ jsx(
77
+ "button",
78
+ {
79
+ type: "button",
80
+ onClick: () => void load(),
81
+ className: "mt-3 rounded border border-kumo-line px-3 py-1.5 text-sm hover:bg-kumo-tint",
82
+ children: "Retry"
83
+ }
84
+ )
85
+ ] });
86
+ }
87
+ const payload = state.payload;
88
+ if (payload === null) {
89
+ return /* @__PURE__ */ jsx("div", { className: "p-4 text-sm text-kumo-subtle", children: "No data." });
90
+ }
91
+ const routesByPath = /* @__PURE__ */ new Map();
92
+ for (const route of payload.routes) {
93
+ routesByPath.set(route.route, route);
94
+ }
95
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4 p-4", children: [
96
+ /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between gap-4", children: [
97
+ /* @__PURE__ */ jsxs("div", { children: [
98
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold", children: "SEO findings" }),
99
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-kumo-subtle", children: payload.totals.findings === 0 ? /* @__PURE__ */ jsx(Fragment, { children: "No findings yet \u2014 click Refresh to evaluate the site." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
100
+ payload.totals.findings,
101
+ " findings across ",
102
+ payload.totals.routes,
103
+ " routes \u2014",
104
+ " ",
105
+ payload.totals.errors,
106
+ " errors, ",
107
+ payload.totals.warnings,
108
+ " warnings."
109
+ ] }) })
110
+ ] }),
111
+ /* @__PURE__ */ jsx(
112
+ "button",
113
+ {
114
+ type: "button",
115
+ onClick: () => void refresh(),
116
+ disabled: refreshing,
117
+ className: "rounded bg-kumo-brand px-3 py-1.5 text-sm font-medium text-white hover:bg-kumo-brand/90 disabled:opacity-50",
118
+ children: refreshing ? "Refreshing\u2026" : "Refresh"
119
+ }
120
+ )
121
+ ] }),
122
+ toast !== null && /* @__PURE__ */ jsx("div", { className: "rounded border border-kumo-line bg-kumo-tint p-3 text-sm", children: toast }),
123
+ payload.sitewideFindings.length > 0 && /* @__PURE__ */ jsxs("section", { className: "rounded border border-kumo-line p-3", children: [
124
+ /* @__PURE__ */ jsx("h2", { className: "text-sm font-medium", children: "Sitewide" }),
125
+ /* @__PURE__ */ jsx("ul", { className: "mt-2 space-y-1 text-sm", children: payload.sitewideFindings.map((finding, i) => /* @__PURE__ */ jsxs("li", { className: "flex items-start gap-2", children: [
126
+ /* @__PURE__ */ jsx(SeverityBadge, { severity: finding.severity }),
127
+ /* @__PURE__ */ jsx("code", { className: "font-mono text-xs", children: finding.rule_id }),
128
+ /* @__PURE__ */ jsx("span", { className: "flex-1", children: finding.message })
129
+ ] }, i)) })
130
+ ] }),
131
+ payload.findings.length === 0 ? /* @__PURE__ */ jsx("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__ */ jsx("div", { className: "overflow-x-auto rounded border border-kumo-line", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-left text-sm", children: [
132
+ /* @__PURE__ */ jsx("thead", { className: "bg-kumo-tint", children: /* @__PURE__ */ jsxs("tr", { children: [
133
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Route" }),
134
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Rule" }),
135
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Severity" }),
136
+ /* @__PURE__ */ jsx("th", { className: "px-3 py-2 font-medium text-kumo-subtle", children: "Message" })
137
+ ] }) }),
138
+ /* @__PURE__ */ jsx("tbody", { children: payload.findings.map((finding, i) => {
139
+ const routeInfo = routesByPath.get(finding.route);
140
+ return /* @__PURE__ */ jsxs("tr", { className: "border-t border-kumo-line", children: [
141
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx(RouteLinks, { finding, info: routeInfo }) }),
142
+ /* @__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 }) }),
143
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: /* @__PURE__ */ jsx(SeverityBadge, { severity: finding.severity }) }),
144
+ /* @__PURE__ */ jsx("td", { className: "px-3 py-2 align-top", children: finding.message })
145
+ ] }, i);
146
+ }) })
147
+ ] }) })
148
+ ] });
149
+ }
150
+ function RouteLinks({
151
+ finding,
152
+ info
153
+ }) {
154
+ if (info === void 0) {
155
+ return /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: finding.route });
156
+ }
157
+ return /* @__PURE__ */ jsxs("span", { className: "space-x-2", children: [
158
+ info.editUrl !== "" ? /* @__PURE__ */ jsx(
159
+ "a",
160
+ {
161
+ href: info.editUrl,
162
+ className: "font-mono text-xs text-kumo-link hover:underline",
163
+ children: finding.route
164
+ }
165
+ ) : /* @__PURE__ */ jsx("span", { className: "font-mono text-xs", children: finding.route }),
166
+ info.publishedUrl !== "" && /* @__PURE__ */ jsx(
167
+ "a",
168
+ {
169
+ href: info.publishedUrl,
170
+ target: "_blank",
171
+ rel: "noopener noreferrer",
172
+ className: "text-xs text-kumo-link hover:underline",
173
+ children: "live \u2197"
174
+ }
175
+ ),
176
+ info.status !== "" && info.status !== "published" && /* @__PURE__ */ jsx("span", { className: "text-xs text-kumo-subtle", children: info.status })
177
+ ] });
178
+ }
179
+ function SeverityBadge({ severity }) {
180
+ 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";
181
+ return /* @__PURE__ */ jsx(
182
+ "span",
183
+ {
184
+ className: `inline-block rounded border px-1.5 py-0.5 text-xs font-medium ${classes}`,
185
+ children: severity
186
+ }
187
+ );
188
+ }
189
+ function csrfHeaders() {
190
+ return {
191
+ "Content-Type": "application/json",
192
+ // emdash's apiFetch wrapper sets this on every mutation;
193
+ // mirroring it here so the plugin's REST endpoints get past the
194
+ // server's CSRF check the same way emdash's own admin does.
195
+ "X-EmDash-Request": "1"
196
+ };
197
+ }
198
+ function unwrap(body, res) {
199
+ if (body.error !== void 0) {
200
+ throw new Error(`${body.error.code}: ${body.error.message}`);
201
+ }
202
+ if (body.data === void 0) {
203
+ throw new Error(`Empty response (HTTP ${res.status})`);
204
+ }
205
+ return body.data;
206
+ }
207
+
208
+ // src/admin.tsx
209
+ var pages = {
210
+ "/findings": Findings
211
+ };
212
+ export {
213
+ pages
214
+ };
@@ -1 +1 @@
1
- {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AA6EA,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CAmD3E;AAID,eAAe,YAAY,CAAC"}
1
+ {"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AA8EA,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CA0E3E;AAID,eAAe,YAAY,CAAC"}
@@ -5,7 +5,7 @@ var __export = (target, all) => {
5
5
  };
6
6
 
7
7
  // src/adapter.ts
8
- function contentItemToEmdashDocument(content) {
8
+ function adaptContentItem(content) {
9
9
  const route = deriveRoute(content);
10
10
  const title = stringOrEmpty(
11
11
  content.seo?.title ?? content.data["title"] ?? content.slug ?? content.id
@@ -29,7 +29,16 @@ function contentItemToEmdashDocument(content) {
29
29
  if (body !== null) {
30
30
  document.body = body;
31
31
  }
32
- return document;
32
+ return {
33
+ document,
34
+ meta: {
35
+ id: content.id,
36
+ collection: content.type,
37
+ slug: content.slug,
38
+ status: content.status,
39
+ title
40
+ }
41
+ };
33
42
  }
34
43
  function deriveRoute(content) {
35
44
  if (typeof content.slug === "string" && content.slug.length > 0) {
@@ -65,9 +74,11 @@ function isPortableTextArray(value) {
65
74
 
66
75
  // src/plugin.ts
67
76
  async function handleAfterSaveConfigured(event, ctx, evaluator) {
68
- const document = contentItemToEmdashDocument(event.content);
77
+ const adapted = adaptContentItem(event.content);
78
+ const document = adapted.document;
69
79
  const { kv, log } = ctx;
70
- await kv.set(documentKey(document.route), document);
80
+ const stored = { document, meta: adapted.meta };
81
+ await kv.set(documentKey(document.route), stored);
71
82
  const result = await evaluator([document]);
72
83
  if (!result.ok) {
73
84
  log?.error?.(`afterSave evaluator failure (${result.reason})`, {
@@ -94,6 +105,7 @@ async function evaluateAndPersistAll(ctx, options) {
94
105
  errors: []
95
106
  };
96
107
  const documents = [];
108
+ const adaptedByRoute = /* @__PURE__ */ new Map();
97
109
  const documentRoutes = /* @__PURE__ */ new Set();
98
110
  for (const collection of collections) {
99
111
  let cursor;
@@ -104,9 +116,13 @@ async function evaluateAndPersistAll(ctx, options) {
104
116
  ...cursor === void 0 ? {} : { cursor }
105
117
  });
106
118
  for (const item of page.items) {
107
- const document = contentItemToEmdashDocument(item);
108
- documents.push(document);
109
- documentRoutes.add(document.route);
119
+ const adapted = adaptContentItem(item);
120
+ documents.push(adapted.document);
121
+ documentRoutes.add(adapted.document.route);
122
+ adaptedByRoute.set(adapted.document.route, {
123
+ document: adapted.document,
124
+ meta: adapted.meta
125
+ });
110
126
  }
111
127
  cursor = page.hasMore ? page.cursor : void 0;
112
128
  } catch (err) {
@@ -117,8 +133,8 @@ async function evaluateAndPersistAll(ctx, options) {
117
133
  } while (cursor !== void 0);
118
134
  }
119
135
  summary.documentsScanned = documents.length;
120
- for (const document of documents) {
121
- await kv.set(documentKey(document.route), document);
136
+ for (const stored of adaptedByRoute.values()) {
137
+ await kv.set(documentKey(stored.document.route), stored);
122
138
  }
123
139
  const result = await options.evaluator(documents);
124
140
  if (!result.ok) {
@@ -154,8 +170,33 @@ function documentKey(route) {
154
170
  return `document:${normalized}`;
155
171
  }
156
172
  async function readAllDocuments(kv) {
173
+ return (await readAllStoredDocuments(kv)).map((s) => s.document);
174
+ }
175
+ async function readAllStoredDocuments(kv) {
157
176
  const entries = await kv.list("document:");
158
- return entries.map((entry) => entry.value).filter((value) => value !== null);
177
+ const out = [];
178
+ for (const entry of entries) {
179
+ const value = entry.value;
180
+ if (value === null || typeof value !== "object") continue;
181
+ if ("document" in value && "meta" in value) {
182
+ out.push(value);
183
+ continue;
184
+ }
185
+ if ("route" in value) {
186
+ const document = value;
187
+ out.push({
188
+ document,
189
+ meta: {
190
+ id: document.route,
191
+ collection: "",
192
+ slug: null,
193
+ status: "",
194
+ title: document.title ?? ""
195
+ }
196
+ });
197
+ }
198
+ }
199
+ return out;
159
200
  }
160
201
  async function readFindings(kv, route) {
161
202
  const stored = await kv.get(findingsKey(route));
@@ -165,6 +206,97 @@ async function readFindings(kv, route) {
165
206
  return stored.findings;
166
207
  }
167
208
 
209
+ // src/data-route.ts
210
+ async function handleDataRoute(ctx, options) {
211
+ let summary = null;
212
+ if (options.refresh) {
213
+ summary = await evaluateAndPersistAll(ctx, {
214
+ collections: options.collections,
215
+ evaluator: options.evaluator
216
+ });
217
+ }
218
+ const payload = await buildFindingsPayload(ctx);
219
+ return { payload, summary };
220
+ }
221
+ async function buildFindingsPayload(ctx) {
222
+ const stored = await readAllStoredDocuments(ctx.kv);
223
+ const documents = /* @__PURE__ */ new Map();
224
+ for (const entry of stored) {
225
+ documents.set(entry.document.route, entry);
226
+ }
227
+ const entries = await ctx.kv.list(
228
+ "findings:"
229
+ );
230
+ const findings = [];
231
+ const sitewide = [];
232
+ const perRoute = /* @__PURE__ */ new Map();
233
+ for (const entry of entries) {
234
+ if (entry.value === null) continue;
235
+ const route = entry.key.replace(/^findings:/, "");
236
+ if (route === "*") {
237
+ sitewide.push(...entry.value.findings);
238
+ continue;
239
+ }
240
+ let bucket = perRoute.get(route);
241
+ if (bucket === void 0) {
242
+ bucket = { findings: [], errors: 0, warnings: 0 };
243
+ perRoute.set(route, bucket);
244
+ }
245
+ for (const finding of entry.value.findings) {
246
+ bucket.findings.push(finding);
247
+ if (finding.severity === "error") bucket.errors += 1;
248
+ else if (finding.severity === "warning") bucket.warnings += 1;
249
+ findings.push({ ...finding, route });
250
+ }
251
+ }
252
+ findings.sort((a, b) => severityRank(a.severity) - severityRank(b.severity));
253
+ const siteUrl = ctx.site?.url ?? "";
254
+ const routes = [];
255
+ for (const [route, bucket] of perRoute) {
256
+ const stored2 = documents.get(route);
257
+ routes.push({
258
+ route,
259
+ title: stored2?.meta.title ?? route,
260
+ status: stored2?.meta.status ?? "",
261
+ collection: stored2?.meta.collection ?? "",
262
+ id: stored2?.meta.id ?? "",
263
+ editUrl: stored2 ? buildEditUrl(stored2.meta) : "",
264
+ publishedUrl: stored2 ? buildPublishedUrl(stored2.meta, route, siteUrl) : "",
265
+ findingCount: bucket.findings.length,
266
+ errorCount: bucket.errors,
267
+ warningCount: bucket.warnings
268
+ });
269
+ }
270
+ routes.sort((a, b) => b.errorCount - a.errorCount || a.route.localeCompare(b.route));
271
+ return {
272
+ routes,
273
+ findings,
274
+ totals: {
275
+ routes: routes.length,
276
+ findings: findings.length,
277
+ errors: findings.filter((f) => f.severity === "error").length,
278
+ warnings: findings.filter((f) => f.severity === "warning").length,
279
+ documentsIndexed: documents.size
280
+ },
281
+ sitewideFindings: sitewide
282
+ };
283
+ }
284
+ function severityRank(severity) {
285
+ if (severity === "error") return 0;
286
+ if (severity === "warning") return 1;
287
+ return 2;
288
+ }
289
+ function buildEditUrl(meta) {
290
+ if (meta.collection === "" || meta.id === "") return "";
291
+ return `/_emdash/admin/content/${encodeURIComponent(meta.collection)}/${encodeURIComponent(meta.id)}`;
292
+ }
293
+ function buildPublishedUrl(meta, route, siteUrl) {
294
+ if (meta.status !== "published") return "";
295
+ if (siteUrl === "") return "";
296
+ const trimmed = siteUrl.endsWith("/") ? siteUrl.slice(0, -1) : siteUrl;
297
+ return `${trimmed}${route}`;
298
+ }
299
+
168
300
  // wasm/aexeo_emdash_bridge_bg.js
169
301
  var aexeo_emdash_bridge_bg_exports = {};
170
302
  __export(aexeo_emdash_bridge_bg_exports, {
@@ -375,6 +507,27 @@ function createPlugin(options = {}) {
375
507
  // every request handles the same set without re-reading options.
376
508
  admin: {
377
509
  handler: (ctx) => handleAdminRoute(ctx, collections)
510
+ },
511
+ // JSON data endpoint for the React adminEntry. Two routes:
512
+ // POST /_emdash/api/plugins/aexeo-seogeo/data — read current findings
513
+ // POST /_emdash/api/plugins/aexeo-seogeo/refresh — sweep + read
514
+ // Returning JSON (not Block Kit blocks) lets the React
515
+ // <Findings/> component render proper <a href> links to the
516
+ // emdash edit page and the public site, which Block Kit can't
517
+ // express in any element type as of emdash 0.8.0.
518
+ data: {
519
+ handler: async (ctx) => (await handleDataRoute(ctx, {
520
+ collections,
521
+ evaluator: inProcessEvaluator,
522
+ refresh: false
523
+ })).payload
524
+ },
525
+ refresh: {
526
+ handler: (ctx) => handleDataRoute(ctx, {
527
+ collections,
528
+ evaluator: inProcessEvaluator,
529
+ refresh: true
530
+ })
378
531
  }
379
532
  },
380
533
  admin: {
@@ -0,0 +1,48 @@
1
+ import type { EvaluatorFn, RefreshSummary, SandboxCtx } from "./plugin.js";
2
+ import type { Finding } from "./types.js";
3
+ export interface FindingsPayload {
4
+ routes: RouteSummary[];
5
+ findings: FindingRow[];
6
+ totals: {
7
+ routes: number;
8
+ findings: number;
9
+ errors: number;
10
+ warnings: number;
11
+ documentsIndexed: number;
12
+ };
13
+ sitewideFindings: Finding[];
14
+ }
15
+ export interface RouteSummary {
16
+ route: string;
17
+ title: string;
18
+ status: string;
19
+ collection: string;
20
+ id: string;
21
+ editUrl: string;
22
+ publishedUrl: string;
23
+ findingCount: number;
24
+ errorCount: number;
25
+ warningCount: number;
26
+ }
27
+ export interface FindingRow {
28
+ route: string;
29
+ rule_id: string;
30
+ severity: string;
31
+ message: string;
32
+ path: string;
33
+ line: number;
34
+ column: number;
35
+ scope: string;
36
+ suggestion: string | null;
37
+ }
38
+ interface DataRouteOptions {
39
+ collections: readonly string[];
40
+ evaluator: EvaluatorFn;
41
+ refresh: boolean;
42
+ }
43
+ export declare function handleDataRoute(ctx: SandboxCtx, options: DataRouteOptions): Promise<{
44
+ payload: FindingsPayload;
45
+ summary: RefreshSummary | null;
46
+ }>;
47
+ export {};
48
+ //# sourceMappingURL=data-route.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"data-route.d.ts","sourceRoot":"","sources":["../src/data-route.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EACV,WAAW,EACX,cAAc,EACd,UAAU,EAEX,MAAM,aAAa,CAAC;AAMrB,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAK1C,MAAM,WAAW,eAAe;IAG9B,MAAM,EAAE,YAAY,EAAE,CAAC;IAIvB,QAAQ,EAAE,UAAU,EAAE,CAAC;IAIvB,MAAM,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;QACjB,gBAAgB,EAAE,MAAM,CAAC;KAC1B,CAAC;IAIF,gBAAgB,EAAE,OAAO,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IAGd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IAIX,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B;AAED,UAAU,gBAAgB;IACxB,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,SAAS,EAAE,WAAW,CAAC;IAIvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,UAAU,EACf,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC;IACT,OAAO,EAAE,eAAe,CAAC;IACzB,OAAO,EAAE,cAAc,GAAG,IAAI,CAAC;CAChC,CAAC,CAUD"}
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ export interface ConfiguredPluginDescriptor {
4
4
  id: string;
5
5
  version: string;
6
6
  entrypoint: string;
7
+ adminEntry?: string;
7
8
  options: Record<string, unknown>;
8
9
  capabilities: readonly string[];
9
10
  allowedHosts?: readonly string[];
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAWtB,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,UAAU,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,YAAY,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACxE;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,0BAA0B,CAsC5B"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAqBA,OAAO,EAAE,qBAAqB,EAAE,MAAM,cAAc,CAAC;AACrD,YAAY,EACV,eAAe,EACf,iBAAiB,EACjB,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,cAAc,CAAC;AAWtB,MAAM,WAAW,0BAA0B;IACzC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IAOnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;IAChC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,UAAU,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACvD,YAAY,EAAE,SAAS;QAAE,EAAE,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;CACxE;AAED,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;;;;;;;;;;;;OAmBG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACjC;AAED,wBAAgB,YAAY,CAC1B,OAAO,GAAE,mBAAwB,GAChC,0BAA0B,CA+C5B"}
package/dist/index.js CHANGED
@@ -57,6 +57,15 @@ function seogeoPlugin(options = {}) {
57
57
  // time — must match the package.json `exports["./configured"]`
58
58
  // entry.
59
59
  entrypoint: "@aeptus/aexeo-emdash/configured",
60
+ // React admin module — registers a custom <Findings/> component
61
+ // for /findings so the route column can render proper <a href>
62
+ // links. Block Kit can't express clickable links in any element
63
+ // type as of emdash 0.8.0. The React module's `pages` object is
64
+ // imported by emdash's admin registry codegen at build time; if
65
+ // the consumer's bundler doesn't pick up tsx (rare — every
66
+ // emdash adapter does), the rest of the plugin still works
67
+ // because /document and the dashboard widget stay on Block Kit.
68
+ adminEntry: "@aeptus/aexeo-emdash/admin",
60
69
  // emdash's astro integration JSON-serializes `options` and emits
61
70
  // an importer that does `createPlugin(<options>)` at boot.
62
71
  // Anything we put here is what the runtime entry's createPlugin
package/dist/plugin.d.ts CHANGED
@@ -1,6 +1,10 @@
1
1
  import type { EmdashDocument, Finding } from "./types.js";
2
- import { type EmdashContentItem } from "./adapter.js";
2
+ import { type EmdashContentItem, type EmdashContentMeta } from "./adapter.js";
3
3
  import type { SidecarHttp } from "./sidecar.js";
4
+ export interface StoredDocument {
5
+ document: EmdashDocument;
6
+ meta: EmdashContentMeta;
7
+ }
4
8
  export declare function buildCapabilities(evaluatorHost: string | null): readonly string[];
5
9
  export declare function buildAllowedHosts(evaluatorHost: string | null): readonly string[];
6
10
  export declare const capabilities: readonly string[];
@@ -37,6 +41,11 @@ export interface SandboxCtx {
37
41
  kv: KvNamespace;
38
42
  http: SidecarHttp;
39
43
  content: SandboxContentApi;
44
+ site?: {
45
+ url: string;
46
+ name?: string;
47
+ locale?: string;
48
+ };
40
49
  log?: {
41
50
  info(msg: string, data?: unknown): void;
42
51
  warn(msg: string, data?: unknown): void;
@@ -73,5 +82,6 @@ export declare function evaluateAndPersistAll(ctx: SandboxCtx, options: {
73
82
  export declare function findingsKey(route: string): string;
74
83
  export declare function documentKey(route: string): string;
75
84
  export declare function readAllDocuments(kv: KvNamespace): Promise<EmdashDocument[]>;
85
+ export declare function readAllStoredDocuments(kv: KvNamespace): Promise<StoredDocument[]>;
76
86
  export declare function readFindings(kv: KvNamespace, route: string): Promise<Finding[]>;
77
87
  //# sourceMappingURL=plugin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  // src/adapter.ts
2
- function contentItemToEmdashDocument(content) {
2
+ function adaptContentItem(content) {
3
3
  const route = deriveRoute(content);
4
4
  const title = stringOrEmpty(
5
5
  content.seo?.title ?? content.data["title"] ?? content.slug ?? content.id
@@ -23,7 +23,16 @@ function contentItemToEmdashDocument(content) {
23
23
  if (body !== null) {
24
24
  document.body = body;
25
25
  }
26
- return document;
26
+ return {
27
+ document,
28
+ meta: {
29
+ id: content.id,
30
+ collection: content.type,
31
+ slug: content.slug,
32
+ status: content.status,
33
+ title
34
+ }
35
+ };
27
36
  }
28
37
  function deriveRoute(content) {
29
38
  if (typeof content.slug === "string" && content.slug.length > 0) {
@@ -88,6 +97,7 @@ async function evaluateAndPersistAll(ctx, options) {
88
97
  errors: []
89
98
  };
90
99
  const documents = [];
100
+ const adaptedByRoute = /* @__PURE__ */ new Map();
91
101
  const documentRoutes = /* @__PURE__ */ new Set();
92
102
  for (const collection of collections) {
93
103
  let cursor;
@@ -98,9 +108,13 @@ async function evaluateAndPersistAll(ctx, options) {
98
108
  ...cursor === void 0 ? {} : { cursor }
99
109
  });
100
110
  for (const item of page.items) {
101
- const document = contentItemToEmdashDocument(item);
102
- documents.push(document);
103
- documentRoutes.add(document.route);
111
+ const adapted = adaptContentItem(item);
112
+ documents.push(adapted.document);
113
+ documentRoutes.add(adapted.document.route);
114
+ adaptedByRoute.set(adapted.document.route, {
115
+ document: adapted.document,
116
+ meta: adapted.meta
117
+ });
104
118
  }
105
119
  cursor = page.hasMore ? page.cursor : void 0;
106
120
  } catch (err) {
@@ -111,8 +125,8 @@ async function evaluateAndPersistAll(ctx, options) {
111
125
  } while (cursor !== void 0);
112
126
  }
113
127
  summary.documentsScanned = documents.length;
114
- for (const document of documents) {
115
- await kv.set(documentKey(document.route), document);
128
+ for (const stored of adaptedByRoute.values()) {
129
+ await kv.set(documentKey(stored.document.route), stored);
116
130
  }
117
131
  const result = await options.evaluator(documents);
118
132
  if (!result.ok) {
@@ -148,8 +162,33 @@ function documentKey(route) {
148
162
  return `document:${normalized}`;
149
163
  }
150
164
  async function readAllDocuments(kv) {
165
+ return (await readAllStoredDocuments(kv)).map((s) => s.document);
166
+ }
167
+ async function readAllStoredDocuments(kv) {
151
168
  const entries = await kv.list("document:");
152
- return entries.map((entry) => entry.value).filter((value) => value !== null);
169
+ const out = [];
170
+ for (const entry of entries) {
171
+ const value = entry.value;
172
+ if (value === null || typeof value !== "object") continue;
173
+ if ("document" in value && "meta" in value) {
174
+ out.push(value);
175
+ continue;
176
+ }
177
+ if ("route" in value) {
178
+ const document = value;
179
+ out.push({
180
+ document,
181
+ meta: {
182
+ id: document.route,
183
+ collection: "",
184
+ slug: null,
185
+ status: "",
186
+ title: document.title ?? ""
187
+ }
188
+ });
189
+ }
190
+ }
191
+ return out;
153
192
  }
154
193
  async function readFindings(kv, route) {
155
194
  const stored = await kv.get(findingsKey(route));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aeptus/aexeo-emdash",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Aexeo seogeo SEO/GEO content evaluator as an emdash plugin",
5
5
  "license": "MIT",
6
6
  "author": "Aexeo",
@@ -36,6 +36,11 @@
36
36
  "import": "./dist/configured.js",
37
37
  "default": "./dist/configured.js"
38
38
  },
39
+ "./admin": {
40
+ "types": "./dist/admin.d.ts",
41
+ "import": "./dist/admin.js",
42
+ "default": "./dist/admin.js"
43
+ },
39
44
  "./sandbox": {
40
45
  "types": "./dist/sandbox-entry.d.ts",
41
46
  "import": "./dist/sandbox-entry.js",
@@ -59,7 +64,8 @@
59
64
  },
60
65
  "peerDependencies": {
61
66
  "@emdash-cms/core": "*",
62
- "emdash": ">=0.7.0"
67
+ "emdash": ">=0.7.0",
68
+ "react": "^18.0.0 || ^19.0.0"
63
69
  },
64
70
  "peerDependenciesMeta": {
65
71
  "@emdash-cms/core": {
@@ -67,12 +73,17 @@
67
73
  },
68
74
  "emdash": {
69
75
  "optional": true
76
+ },
77
+ "react": {
78
+ "optional": true
70
79
  }
71
80
  },
72
81
  "devDependencies": {
73
82
  "@types/node": "^22.0.0",
83
+ "@types/react": "^19.2.14",
74
84
  "emdash": "^0.7.0",
75
85
  "esbuild": "^0.28.0",
86
+ "react": "^19.2.5",
76
87
  "typescript": "^5.6.0"
77
88
  }
78
89
  }