@aeptus/aexeo-emdash 0.2.0 → 0.4.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 +116 -1
- package/dist/admin/Facts.d.ts +3 -0
- package/dist/admin/Facts.d.ts.map +1 -0
- package/dist/admin.d.ts +2 -0
- package/dist/admin.d.ts.map +1 -1
- package/dist/admin.js +327 -49
- package/dist/configured.d.ts +8 -0
- package/dist/configured.d.ts.map +1 -1
- package/dist/configured.js +447 -109
- package/dist/data-route.d.ts +2 -0
- package/dist/data-route.d.ts.map +1 -1
- package/dist/facts-route.d.ts +7 -0
- package/dist/facts-route.d.ts.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -3
- package/dist/plugin.d.ts +5 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/sandbox-entry.js +6 -2
- package/dist/sandbox.d.ts.map +1 -1
- package/dist/suppressions.d.ts +10 -0
- package/dist/suppressions.d.ts.map +1 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/wasm-init.d.ts +3 -1
- package/dist/wasm-init.d.ts.map +1 -1
- package/package.json +4 -3
- package/wasm/aexeo_emdash_bridge.d.ts +16 -1
- package/wasm/aexeo_emdash_bridge.js +1 -1
- package/wasm/aexeo_emdash_bridge_bg.d.ts +12 -3
- package/wasm/aexeo_emdash_bridge_bg.js +65 -2
- package/wasm/aexeo_emdash_bridge_bg.wasm +0 -0
- package/wasm/aexeo_emdash_bridge_bg.wasm.d.ts +13 -11
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,119 @@ and the project follows [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.4.0] - 2026-05-01
|
|
10
|
+
|
|
11
|
+
Adds upstreamed support for editor-workflow finding suppressions.
|
|
12
|
+
Removes the last reason for consumer sites to maintain a local
|
|
13
|
+
patch over the published plugin.
|
|
14
|
+
|
|
15
|
+
**Compatibility:** verified against emdash `0.7.0` and `0.8.0`.
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- **`seogeoPlugin({ suppressions })` option.** Each rule silences
|
|
20
|
+
findings matching the route pattern (glob) AND/OR a rule-id set.
|
|
21
|
+
Applied before findings are persisted to KV — suppressed findings
|
|
22
|
+
never reach the dashboard, /findings, or the per-document panel.
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
seogeoPlugin({
|
|
26
|
+
suppressions: [
|
|
27
|
+
{ routePattern: "/privacy", ruleIds: ["RULE001"] },
|
|
28
|
+
{ routePattern: "/fr-fr/**", ruleIds: ["RULE002"] },
|
|
29
|
+
{ ruleIds: ["RULE999"] }, // applies to every route
|
|
30
|
+
],
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Glob: `*` matches non-`/` chars, `**` matches across `/`, `?`
|
|
35
|
+
matches one non-`/` char. Patterns are anchored. Empty rules
|
|
36
|
+
(`{}` with neither field) are rejected at plugin construction
|
|
37
|
+
with a clear error — that's a kill-switch for all findings
|
|
38
|
+
everywhere and almost certainly an editor mistake.
|
|
39
|
+
|
|
40
|
+
- **`Suppression` type** re-exported from the package root for hosts
|
|
41
|
+
that want to type their config:
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import type { Suppression } from "@aeptus/aexeo-emdash";
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Notes for hosts upgrading from 0.3.x
|
|
48
|
+
|
|
49
|
+
- No automatic behavior change. `suppressions` defaults to undefined
|
|
50
|
+
and the filter is a no-op until the host opts in.
|
|
51
|
+
- Suppressions are plugin-only by design. The CLI `check` continues
|
|
52
|
+
to surface every finding; if you want to silence findings at the
|
|
53
|
+
CLI layer, use `seogeo.toml`'s `[ignore]` block. The two surfaces
|
|
54
|
+
serve different audiences: the plugin is editorial workflow, the
|
|
55
|
+
CLI is build-gating.
|
|
56
|
+
|
|
57
|
+
## [0.3.0] - 2026-05-01
|
|
58
|
+
|
|
59
|
+
Adds an LLM-assisted authoring flow for the truth manifest
|
|
60
|
+
(`facts.json`) — a structured assertion of who the organization is,
|
|
61
|
+
what the products are, and the terminology to use/avoid. AI
|
|
62
|
+
assistants and search crawlers read it for citation grounding;
|
|
63
|
+
authoring it well is a content task, not engineering, but the
|
|
64
|
+
manifest's failure mode (a published manifest with hallucinated
|
|
65
|
+
content) is high-impact.
|
|
66
|
+
|
|
67
|
+
The plugin frames the question, validates the answer, and persists
|
|
68
|
+
the result. The editor's LLM generates. The split is enforced by
|
|
69
|
+
the prompt template, which mandates an interview phase before
|
|
70
|
+
producing JSON: the LLM must ask up to 4 prioritized questions
|
|
71
|
+
(terminology > identity > product/org split > descriptors) so the
|
|
72
|
+
failure mode shifts from confident hallucination to honest gaps.
|
|
73
|
+
|
|
74
|
+
**Compatibility:** verified against emdash `0.7.0` and `0.8.0`.
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
|
|
78
|
+
- **`/facts` admin page.** New entry in the plugin's adminPages.
|
|
79
|
+
Three-section UI: status of stored manifest, generate-prompt
|
|
80
|
+
(copy to clipboard for pasting into the editor's LLM), and
|
|
81
|
+
paste-and-validate (textarea with Validate + Save buttons; Save
|
|
82
|
+
is gated on a clean validation).
|
|
83
|
+
- **Manifest-aware truth scoring.** `scoreIntelligence` now accepts
|
|
84
|
+
an optional manifest argument; the plugin reads it from KV
|
|
85
|
+
(`facts:current`) before scoring and badges the dashboard widget's
|
|
86
|
+
truth stat with the actual signal source — `Truth (manifest+schema)`,
|
|
87
|
+
`Truth (schema only)`, etc. Closes the prior UX-honesty gap where
|
|
88
|
+
the truth score was computed in schema-only mode without telling
|
|
89
|
+
the editor.
|
|
90
|
+
- **`FACTS001` / `FACTS003` findings** on /findings. Surface
|
|
91
|
+
"no manifest authored yet" and "manifest disagrees with on-page
|
|
92
|
+
schema.org" so editors discover the authoring flow through their
|
|
93
|
+
existing surface. Cached under `meta:facts-findings` and
|
|
94
|
+
refreshed on Refresh + on Save, so the data path reads at zero
|
|
95
|
+
WASM cost.
|
|
96
|
+
- **Bridge surface:** `generateFactsPrompt(documents)` and
|
|
97
|
+
`validateFactsManifest(manifest, documents)` WASM exports.
|
|
98
|
+
`scoreIntelligence` gained an optional `manifest_json` parameter.
|
|
99
|
+
|
|
100
|
+
### Changed
|
|
101
|
+
|
|
102
|
+
- `wasm/aexeo_emdash_bridge_bg.d.ts` is now tracked in git
|
|
103
|
+
(gitignored everything else in `wasm/`). wasm-bindgen 0.2.118
|
|
104
|
+
doesn't emit a `_bg.d.ts` for the bundler target, so this file
|
|
105
|
+
is hand-maintained. The build script snapshots its contents
|
|
106
|
+
before each wasm-bindgen run and restores it (saving the new
|
|
107
|
+
emit under `.wasm-bindgen` for inspection) if a future toolchain
|
|
108
|
+
version begins emitting one — loud warning rather than silent
|
|
109
|
+
regression.
|
|
110
|
+
|
|
111
|
+
### Notes for hosts upgrading from 0.2.x
|
|
112
|
+
|
|
113
|
+
- A new sidebar entry "Truth manifest" appears under the plugin's
|
|
114
|
+
admin pages. No action required to enable.
|
|
115
|
+
- The dashboard widget's truth-score label now reads
|
|
116
|
+
`Truth (schema only)` until a manifest is authored. Same number,
|
|
117
|
+
more honest framing — no behavioral change.
|
|
118
|
+
- A new sitewide finding (`FACTS001`) appears on /findings until a
|
|
119
|
+
manifest is saved. Severity: warning. Authoring via the new
|
|
120
|
+
/facts page dismisses it.
|
|
121
|
+
|
|
9
122
|
## [0.2.0] - 2026-04-28
|
|
10
123
|
|
|
11
124
|
Adds the long-asked clickable routes on the findings page. Block
|
|
@@ -153,7 +266,9 @@ First public release.
|
|
|
153
266
|
`astro.config.mjs` for the WASM import to resolve to a
|
|
154
267
|
precompiled `WebAssembly.Module`. Not optional.
|
|
155
268
|
|
|
156
|
-
[Unreleased]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.
|
|
269
|
+
[Unreleased]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.4.0...HEAD
|
|
270
|
+
[0.4.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.3.0...aexeo-emdash-v0.4.0
|
|
271
|
+
[0.3.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.2.0...aexeo-emdash-v0.3.0
|
|
157
272
|
[0.2.0]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.2...aexeo-emdash-v0.2.0
|
|
158
273
|
[0.1.2]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.1...aexeo-emdash-v0.1.2
|
|
159
274
|
[0.1.1]: https://github.com/schiste/Aexeo/compare/aexeo-emdash-v0.1.0...aexeo-emdash-v0.1.1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Facts.d.ts","sourceRoot":"","sources":["../../src/admin/Facts.tsx"],"names":[],"mappings":"AAgBA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAsC/B,wBAAgB,KAAK,IAAI,KAAK,CAAC,GAAG,CAAC,OAAO,CAyLzC"}
|
package/dist/admin.d.ts
CHANGED
package/dist/admin.d.ts.map
CHANGED
|
@@ -1 +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
|
|
1
|
+
{"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../src/admin.tsx"],"names":[],"mappings":"AAgBA,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAE/C,eAAO,MAAM,KAAK;;;CAMjB,CAAC"}
|
package/dist/admin.js
CHANGED
|
@@ -1,20 +1,294 @@
|
|
|
1
|
-
// src/admin/
|
|
1
|
+
// src/admin/Facts.tsx
|
|
2
2
|
import { useCallback, useEffect, useState } from "react";
|
|
3
|
-
import {
|
|
3
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
var PLUGIN_ID = "aexeo-seogeo";
|
|
5
5
|
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);
|
|
12
|
+
const [toast, setToast] = useState(null);
|
|
13
|
+
const loadCurrent = useCallback(async () => {
|
|
14
|
+
setLoadError(null);
|
|
15
|
+
try {
|
|
16
|
+
const res = await callRoute("data", {});
|
|
17
|
+
setData(res);
|
|
18
|
+
const stored = res.manifest;
|
|
19
|
+
if (stored !== null) {
|
|
20
|
+
setDraft(JSON.stringify(stored, null, 2));
|
|
21
|
+
}
|
|
22
|
+
} catch (err) {
|
|
23
|
+
setLoadError(err instanceof Error ? err.message : String(err));
|
|
24
|
+
}
|
|
25
|
+
}, []);
|
|
26
|
+
useEffect(() => {
|
|
27
|
+
void loadCurrent();
|
|
28
|
+
}, [loadCurrent]);
|
|
29
|
+
const onCopyPrompt = useCallback(async () => {
|
|
30
|
+
setBusy("prompt");
|
|
31
|
+
setToast(null);
|
|
32
|
+
try {
|
|
33
|
+
const res = await callRoute("prompt", {});
|
|
34
|
+
await navigator.clipboard.writeText(res.prompt);
|
|
35
|
+
setToast(
|
|
36
|
+
"Prompt copied to clipboard \u2014 paste it into Claude/GPT, answer the LLM's questions, then come back and paste the resulting JSON below."
|
|
37
|
+
);
|
|
38
|
+
} catch (err) {
|
|
39
|
+
setToast(
|
|
40
|
+
`Failed to generate prompt: ${err instanceof Error ? err.message : String(err)}`
|
|
41
|
+
);
|
|
42
|
+
} finally {
|
|
43
|
+
setBusy(null);
|
|
44
|
+
}
|
|
45
|
+
}, []);
|
|
46
|
+
const onValidate = useCallback(async () => {
|
|
47
|
+
setBusy("validate");
|
|
48
|
+
setToast(null);
|
|
49
|
+
setValidation(null);
|
|
50
|
+
try {
|
|
51
|
+
const res = await callRoute("validate", {
|
|
52
|
+
manifest_json: draft
|
|
53
|
+
});
|
|
54
|
+
setValidation(res);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
setToast(
|
|
57
|
+
`Validation failed: ${err instanceof Error ? err.message : String(err)}`
|
|
58
|
+
);
|
|
59
|
+
} finally {
|
|
60
|
+
setBusy(null);
|
|
61
|
+
}
|
|
62
|
+
}, [draft]);
|
|
63
|
+
const onSave = useCallback(async () => {
|
|
64
|
+
setBusy("save");
|
|
65
|
+
setToast(null);
|
|
66
|
+
try {
|
|
67
|
+
await callRoute("save", { manifest_json: draft });
|
|
68
|
+
setToast("Manifest saved. Site truth score will reflect it on next refresh.");
|
|
69
|
+
await loadCurrent();
|
|
70
|
+
} catch (err) {
|
|
71
|
+
setToast(
|
|
72
|
+
`Save failed: ${err instanceof Error ? err.message : String(err)}`
|
|
73
|
+
);
|
|
74
|
+
} finally {
|
|
75
|
+
setBusy(null);
|
|
76
|
+
}
|
|
77
|
+
}, [draft, loadCurrent]);
|
|
78
|
+
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: [
|
|
83
|
+
"Author a ",
|
|
84
|
+
/* @__PURE__ */ jsx("code", { children: "facts.json" }),
|
|
85
|
+
" using your LLM of choice. The plugin frames the question, validates the answer, and stores the result. Read",
|
|
86
|
+
" ",
|
|
87
|
+
/* @__PURE__ */ jsx(
|
|
88
|
+
"a",
|
|
89
|
+
{
|
|
90
|
+
href: "https://github.com/schiste/Aexeo/blob/main/docs/facts-manifest.md",
|
|
91
|
+
target: "_blank",
|
|
92
|
+
rel: "noopener noreferrer",
|
|
93
|
+
className: "text-kumo-link hover:underline",
|
|
94
|
+
children: "the authoring guide"
|
|
95
|
+
}
|
|
96
|
+
),
|
|
97
|
+
" ",
|
|
98
|
+
"for the philosophy behind this flow."
|
|
99
|
+
] })
|
|
100
|
+
] }),
|
|
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: [
|
|
102
|
+
"Failed to load manifest state: ",
|
|
103
|
+
loadError
|
|
104
|
+
] }),
|
|
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." })
|
|
108
|
+
] }),
|
|
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(
|
|
113
|
+
"button",
|
|
114
|
+
{
|
|
115
|
+
type: "button",
|
|
116
|
+
onClick: () => void onCopyPrompt(),
|
|
117
|
+
disabled: busy !== null,
|
|
118
|
+
className: "rounded bg-kumo-brand px-3 py-1.5 text-sm font-medium text-white hover:bg-kumo-brand/90 disabled:opacity-50",
|
|
119
|
+
children: busy === "prompt" ? "Copying\u2026" : "Copy prompt"
|
|
120
|
+
}
|
|
121
|
+
)
|
|
122
|
+
] }),
|
|
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(
|
|
126
|
+
"textarea",
|
|
127
|
+
{
|
|
128
|
+
value: draft,
|
|
129
|
+
onChange: (e) => setDraft(e.target.value),
|
|
130
|
+
spellCheck: false,
|
|
131
|
+
rows: 16,
|
|
132
|
+
placeholder: '{ "version": 1, "organization": { ... }, ... }',
|
|
133
|
+
className: "w-full rounded border border-kumo-line bg-kumo-tint p-2 font-mono text-xs"
|
|
134
|
+
}
|
|
135
|
+
),
|
|
136
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
137
|
+
/* @__PURE__ */ jsx(
|
|
138
|
+
"button",
|
|
139
|
+
{
|
|
140
|
+
type: "button",
|
|
141
|
+
onClick: () => void onValidate(),
|
|
142
|
+
disabled: busy !== null || draft.trim().length === 0,
|
|
143
|
+
className: "rounded border border-kumo-line px-3 py-1.5 text-sm hover:bg-kumo-tint disabled:opacity-50",
|
|
144
|
+
children: busy === "validate" ? "Validating\u2026" : "Validate"
|
|
145
|
+
}
|
|
146
|
+
),
|
|
147
|
+
/* @__PURE__ */ jsx(
|
|
148
|
+
"button",
|
|
149
|
+
{
|
|
150
|
+
type: "button",
|
|
151
|
+
onClick: () => void onSave(),
|
|
152
|
+
disabled: busy !== null || !validationOk,
|
|
153
|
+
title: validationOk ? "Save to KV" : "Run Validate first; only valid manifests can be saved.",
|
|
154
|
+
className: "rounded bg-kumo-brand px-3 py-1.5 text-sm font-medium text-white hover:bg-kumo-brand/90 disabled:opacity-50",
|
|
155
|
+
children: busy === "save" ? "Saving\u2026" : "Save"
|
|
156
|
+
}
|
|
157
|
+
)
|
|
158
|
+
] })
|
|
159
|
+
] }),
|
|
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 })
|
|
162
|
+
] });
|
|
163
|
+
}
|
|
164
|
+
function ValidationReport({
|
|
165
|
+
result
|
|
166
|
+
}) {
|
|
167
|
+
const errMismatches = result.assessment.mismatches.filter(
|
|
168
|
+
(m) => m.severity === "error"
|
|
169
|
+
);
|
|
170
|
+
const warnMismatches = result.assessment.mismatches.filter(
|
|
171
|
+
(m) => m.severity === "warning"
|
|
172
|
+
);
|
|
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: [
|
|
177
|
+
"Shape:",
|
|
178
|
+
" ",
|
|
179
|
+
/* @__PURE__ */ jsx("strong", { children: result.validation.valid ? "valid" : "invalid" }),
|
|
180
|
+
" ",
|
|
181
|
+
"\xB7 org",
|
|
182
|
+
" ",
|
|
183
|
+
result.validation.organization_present ? "present" : "missing",
|
|
184
|
+
" \xB7",
|
|
185
|
+
" ",
|
|
186
|
+
result.validation.product_count,
|
|
187
|
+
" products \xB7",
|
|
188
|
+
" ",
|
|
189
|
+
result.validation.preferred_term_count,
|
|
190
|
+
" preferred terms \xB7",
|
|
191
|
+
" ",
|
|
192
|
+
result.validation.forbidden_term_count,
|
|
193
|
+
" forbidden terms"
|
|
194
|
+
] }),
|
|
195
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
196
|
+
"Score: ",
|
|
197
|
+
/* @__PURE__ */ jsx("strong", { children: result.assessment.score }),
|
|
198
|
+
"/",
|
|
199
|
+
result.assessment.score_ceiling,
|
|
200
|
+
" \xB7 source =",
|
|
201
|
+
" ",
|
|
202
|
+
/* @__PURE__ */ jsx("code", { children: result.assessment.structured_truth_source }),
|
|
203
|
+
" \xB7",
|
|
204
|
+
" ",
|
|
205
|
+
result.assessment.pages_analyzed,
|
|
206
|
+
" pages,",
|
|
207
|
+
" ",
|
|
208
|
+
result.assessment.pages_with_schema,
|
|
209
|
+
" with schema.org"
|
|
210
|
+
] })
|
|
211
|
+
] }),
|
|
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)) })
|
|
215
|
+
] }),
|
|
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)) })
|
|
219
|
+
] }),
|
|
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 })
|
|
223
|
+
] }),
|
|
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 })
|
|
227
|
+
] }),
|
|
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: [
|
|
229
|
+
"\u2713 Manifest is valid and agrees with on-page schema.org. Click",
|
|
230
|
+
" ",
|
|
231
|
+
"Save to persist."
|
|
232
|
+
] })
|
|
233
|
+
] });
|
|
234
|
+
}
|
|
235
|
+
function MismatchList({
|
|
236
|
+
items
|
|
237
|
+
}) {
|
|
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 }),
|
|
240
|
+
" \xB7 ",
|
|
241
|
+
m.field,
|
|
242
|
+
": expected",
|
|
243
|
+
" ",
|
|
244
|
+
/* @__PURE__ */ jsx("code", { className: "text-xs", children: m.expected }),
|
|
245
|
+
", observed",
|
|
246
|
+
" ",
|
|
247
|
+
/* @__PURE__ */ jsx("code", { className: "text-xs", children: m.observed }),
|
|
248
|
+
" ",
|
|
249
|
+
/* @__PURE__ */ jsxs("span", { className: "text-xs text-kumo-subtle", children: [
|
|
250
|
+
"(",
|
|
251
|
+
m.source,
|
|
252
|
+
")"
|
|
253
|
+
] })
|
|
254
|
+
] }, i)) });
|
|
255
|
+
}
|
|
256
|
+
async function callRoute(kind, extra) {
|
|
257
|
+
const res = await fetch(`${API_BASE}/plugins/${PLUGIN_ID}/facts`, {
|
|
258
|
+
method: "POST",
|
|
259
|
+
headers: {
|
|
260
|
+
"Content-Type": "application/json",
|
|
261
|
+
"X-EmDash-Request": "1"
|
|
262
|
+
},
|
|
263
|
+
body: JSON.stringify({ kind, ...extra })
|
|
264
|
+
});
|
|
265
|
+
const body = await res.json();
|
|
266
|
+
if (body.error !== void 0) {
|
|
267
|
+
throw new Error(`${body.error.code}: ${body.error.message}`);
|
|
268
|
+
}
|
|
269
|
+
if (body.data === void 0) {
|
|
270
|
+
throw new Error(`Empty response (HTTP ${res.status})`);
|
|
271
|
+
}
|
|
272
|
+
return body.data;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// 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-seogeo";
|
|
279
|
+
var API_BASE2 = "/_emdash/api";
|
|
6
280
|
function Findings() {
|
|
7
|
-
const [state, setState] =
|
|
281
|
+
const [state, setState] = useState2({
|
|
8
282
|
loading: true,
|
|
9
283
|
error: null,
|
|
10
284
|
payload: null
|
|
11
285
|
});
|
|
12
|
-
const [refreshing, setRefreshing] =
|
|
13
|
-
const [toast, setToast] =
|
|
14
|
-
const load =
|
|
286
|
+
const [refreshing, setRefreshing] = useState2(false);
|
|
287
|
+
const [toast, setToast] = useState2(null);
|
|
288
|
+
const load = useCallback2(async () => {
|
|
15
289
|
setState((s) => ({ ...s, loading: true, error: null }));
|
|
16
290
|
try {
|
|
17
|
-
const res = await fetch(`${
|
|
291
|
+
const res = await fetch(`${API_BASE2}/plugins/${PLUGIN_ID2}/data`, {
|
|
18
292
|
method: "POST",
|
|
19
293
|
headers: csrfHeaders(),
|
|
20
294
|
body: "{}"
|
|
@@ -30,11 +304,11 @@ function Findings() {
|
|
|
30
304
|
});
|
|
31
305
|
}
|
|
32
306
|
}, []);
|
|
33
|
-
const refresh =
|
|
307
|
+
const refresh = useCallback2(async () => {
|
|
34
308
|
setRefreshing(true);
|
|
35
309
|
setToast(null);
|
|
36
310
|
try {
|
|
37
|
-
const res = await fetch(`${
|
|
311
|
+
const res = await fetch(`${API_BASE2}/plugins/${PLUGIN_ID2}/refresh`, {
|
|
38
312
|
method: "POST",
|
|
39
313
|
headers: csrfHeaders(),
|
|
40
314
|
body: "{}"
|
|
@@ -60,20 +334,20 @@ function Findings() {
|
|
|
60
334
|
setRefreshing(false);
|
|
61
335
|
}
|
|
62
336
|
}, []);
|
|
63
|
-
|
|
337
|
+
useEffect2(() => {
|
|
64
338
|
void load();
|
|
65
339
|
}, [load]);
|
|
66
340
|
if (state.loading && state.payload === null) {
|
|
67
|
-
return /* @__PURE__ */
|
|
341
|
+
return /* @__PURE__ */ jsx2("div", { className: "p-4 text-sm text-kumo-subtle", children: "Loading findings\u2026" });
|
|
68
342
|
}
|
|
69
343
|
if (state.error !== null) {
|
|
70
|
-
return /* @__PURE__ */
|
|
71
|
-
/* @__PURE__ */
|
|
72
|
-
/* @__PURE__ */
|
|
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: [
|
|
73
347
|
"Failed to load: ",
|
|
74
348
|
state.error
|
|
75
349
|
] }),
|
|
76
|
-
/* @__PURE__ */
|
|
350
|
+
/* @__PURE__ */ jsx2(
|
|
77
351
|
"button",
|
|
78
352
|
{
|
|
79
353
|
type: "button",
|
|
@@ -86,17 +360,17 @@ function Findings() {
|
|
|
86
360
|
}
|
|
87
361
|
const payload = state.payload;
|
|
88
362
|
if (payload === null) {
|
|
89
|
-
return /* @__PURE__ */
|
|
363
|
+
return /* @__PURE__ */ jsx2("div", { className: "p-4 text-sm text-kumo-subtle", children: "No data." });
|
|
90
364
|
}
|
|
91
365
|
const routesByPath = /* @__PURE__ */ new Map();
|
|
92
366
|
for (const route of payload.routes) {
|
|
93
367
|
routesByPath.set(route.route, route);
|
|
94
368
|
}
|
|
95
|
-
return /* @__PURE__ */
|
|
96
|
-
/* @__PURE__ */
|
|
97
|
-
/* @__PURE__ */
|
|
98
|
-
/* @__PURE__ */
|
|
99
|
-
/* @__PURE__ */
|
|
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: [
|
|
100
374
|
payload.totals.findings,
|
|
101
375
|
" findings across ",
|
|
102
376
|
payload.totals.routes,
|
|
@@ -108,7 +382,7 @@ function Findings() {
|
|
|
108
382
|
" warnings."
|
|
109
383
|
] }) })
|
|
110
384
|
] }),
|
|
111
|
-
/* @__PURE__ */
|
|
385
|
+
/* @__PURE__ */ jsx2(
|
|
112
386
|
"button",
|
|
113
387
|
{
|
|
114
388
|
type: "button",
|
|
@@ -119,29 +393,29 @@ function Findings() {
|
|
|
119
393
|
}
|
|
120
394
|
)
|
|
121
395
|
] }),
|
|
122
|
-
toast !== null && /* @__PURE__ */
|
|
123
|
-
payload.sitewideFindings.length > 0 && /* @__PURE__ */
|
|
124
|
-
/* @__PURE__ */
|
|
125
|
-
/* @__PURE__ */
|
|
126
|
-
/* @__PURE__ */
|
|
127
|
-
/* @__PURE__ */
|
|
128
|
-
/* @__PURE__ */
|
|
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 })
|
|
129
403
|
] }, i)) })
|
|
130
404
|
] }),
|
|
131
|
-
payload.findings.length === 0 ? /* @__PURE__ */
|
|
132
|
-
/* @__PURE__ */
|
|
133
|
-
/* @__PURE__ */
|
|
134
|
-
/* @__PURE__ */
|
|
135
|
-
/* @__PURE__ */
|
|
136
|
-
/* @__PURE__ */
|
|
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" })
|
|
137
411
|
] }) }),
|
|
138
|
-
/* @__PURE__ */
|
|
412
|
+
/* @__PURE__ */ jsx2("tbody", { children: payload.findings.map((finding, i) => {
|
|
139
413
|
const routeInfo = routesByPath.get(finding.route);
|
|
140
|
-
return /* @__PURE__ */
|
|
141
|
-
/* @__PURE__ */
|
|
142
|
-
/* @__PURE__ */
|
|
143
|
-
/* @__PURE__ */
|
|
144
|
-
/* @__PURE__ */
|
|
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 })
|
|
145
419
|
] }, i);
|
|
146
420
|
}) })
|
|
147
421
|
] }) })
|
|
@@ -152,18 +426,18 @@ function RouteLinks({
|
|
|
152
426
|
info
|
|
153
427
|
}) {
|
|
154
428
|
if (info === void 0) {
|
|
155
|
-
return /* @__PURE__ */
|
|
429
|
+
return /* @__PURE__ */ jsx2("span", { className: "font-mono text-xs", children: finding.route });
|
|
156
430
|
}
|
|
157
|
-
return /* @__PURE__ */
|
|
158
|
-
info.editUrl !== "" ? /* @__PURE__ */
|
|
431
|
+
return /* @__PURE__ */ jsxs2("span", { className: "space-x-2", children: [
|
|
432
|
+
info.editUrl !== "" ? /* @__PURE__ */ jsx2(
|
|
159
433
|
"a",
|
|
160
434
|
{
|
|
161
435
|
href: info.editUrl,
|
|
162
436
|
className: "font-mono text-xs text-kumo-link hover:underline",
|
|
163
437
|
children: finding.route
|
|
164
438
|
}
|
|
165
|
-
) : /* @__PURE__ */
|
|
166
|
-
info.publishedUrl !== "" && /* @__PURE__ */
|
|
439
|
+
) : /* @__PURE__ */ jsx2("span", { className: "font-mono text-xs", children: finding.route }),
|
|
440
|
+
info.publishedUrl !== "" && /* @__PURE__ */ jsx2(
|
|
167
441
|
"a",
|
|
168
442
|
{
|
|
169
443
|
href: info.publishedUrl,
|
|
@@ -173,12 +447,12 @@ function RouteLinks({
|
|
|
173
447
|
children: "live \u2197"
|
|
174
448
|
}
|
|
175
449
|
),
|
|
176
|
-
info.status !== "" && info.status !== "published" && /* @__PURE__ */
|
|
450
|
+
info.status !== "" && info.status !== "published" && /* @__PURE__ */ jsx2("span", { className: "text-xs text-kumo-subtle", children: info.status })
|
|
177
451
|
] });
|
|
178
452
|
}
|
|
179
453
|
function SeverityBadge({ severity }) {
|
|
180
454
|
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__ */
|
|
455
|
+
return /* @__PURE__ */ jsx2(
|
|
182
456
|
"span",
|
|
183
457
|
{
|
|
184
458
|
className: `inline-block rounded border px-1.5 py-0.5 text-xs font-medium ${classes}`,
|
|
@@ -207,7 +481,11 @@ function unwrap(body, res) {
|
|
|
207
481
|
|
|
208
482
|
// src/admin.tsx
|
|
209
483
|
var pages = {
|
|
210
|
-
"/findings": Findings
|
|
484
|
+
"/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.
|
|
488
|
+
"/facts": Facts
|
|
211
489
|
};
|
|
212
490
|
export {
|
|
213
491
|
pages
|
package/dist/configured.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { Suppression } from "./suppressions.js";
|
|
1
2
|
export interface ConfiguredPluginOptions {
|
|
2
3
|
/**
|
|
3
4
|
* emdash collections the Refresh button sweeps. Comes through from
|
|
@@ -6,6 +7,13 @@ export interface ConfiguredPluginOptions {
|
|
|
6
7
|
* (`["posts", "pages"]`).
|
|
7
8
|
*/
|
|
8
9
|
collections?: readonly string[];
|
|
10
|
+
/**
|
|
11
|
+
* Editor-workflow suppressions. See `Suppression` in suppressions.ts
|
|
12
|
+
* for the rule shape and matching semantics. Compiled once at plugin
|
|
13
|
+
* construction; the resulting filter is captured in the closures
|
|
14
|
+
* attached to the route handlers and the afterSave hook.
|
|
15
|
+
*/
|
|
16
|
+
suppressions?: readonly Suppression[];
|
|
9
17
|
}
|
|
10
18
|
export declare function createPlugin(options?: ConfiguredPluginOptions): unknown;
|
|
11
19
|
export default createPlugin;
|
package/dist/configured.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"configured.d.ts","sourceRoot":"","sources":["../src/configured.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAoDrD,MAAM,WAAW,uBAAuB;IACtC;;;;;OAKG;IACH,WAAW,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAEhC;;;;;OAKG;IACH,YAAY,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CACvC;AAED,wBAAgB,YAAY,CAAC,OAAO,GAAE,uBAA4B,GAAG,OAAO,CA+F3E;AAID,eAAe,YAAY,CAAC"}
|