diff --git a/app/package-lock.json b/app/package-lock.json
index 1c776b8..c19cfb4 100644
--- a/app/package-lock.json
+++ b/app/package-lock.json
@@ -14,7 +14,8 @@
"emdash": "^0.10.0"
},
"devDependencies": {
- "@astrojs/check": "^0.9.7"
+ "@astrojs/check": "^0.9.7",
+ "@types/node": "^22"
}
},
"node_modules/@astrojs/check": {
@@ -3607,13 +3608,12 @@
}
},
"node_modules/@types/node": {
- "version": "25.9.1",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.1.tgz",
- "integrity": "sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==",
+ "version": "22.19.19",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz",
+ "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==",
"license": "MIT",
- "optional": true,
"dependencies": {
- "undici-types": ">=7.24.0 <7.24.7"
+ "undici-types": "~6.21.0"
}
},
"node_modules/@types/react": {
@@ -9505,11 +9505,10 @@
"license": "MIT"
},
"node_modules/undici-types": {
- "version": "7.24.6",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
- "integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
- "license": "MIT",
- "optional": true
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "license": "MIT"
},
"node_modules/unified": {
"version": "11.0.5",
diff --git a/app/package.json b/app/package.json
index 528be6d..ad81a12 100644
--- a/app/package.json
+++ b/app/package.json
@@ -22,6 +22,7 @@
"emdash": "^0.10.0"
},
"devDependencies": {
- "@astrojs/check": "^0.9.7"
+ "@astrojs/check": "^0.9.7",
+ "@types/node": "^22"
}
}
diff --git a/app/seed/seed.json b/app/seed/seed.json
index ab16a1e..7dfbb0f 100644
--- a/app/seed/seed.json
+++ b/app/seed/seed.json
@@ -47,13 +47,13 @@
"defaultValue": "proposed",
"searchable": true,
"options": [
- { "value": "port", "label": "Port — reimplement on target" },
- { "value": "built-in", "label": "Built-in — covered by target core" },
- { "value": "saas", "label": "SaaS — replaced by external service" },
- { "value": "drop", "label": "Drop — not needed" },
- { "value": "gated", "label": "Gated — pending decision" },
- { "value": "done", "label": "Done — ported and shipped" },
- { "value": "proposed", "label": "Proposed — newly submitted" }
+ { "value": "port", "label": "Port" },
+ { "value": "built-in", "label": "Built-in" },
+ { "value": "saas", "label": "SaaS" },
+ { "value": "drop", "label": "Drop" },
+ { "value": "gated", "label": "Gated" },
+ { "value": "done", "label": "Done" },
+ { "value": "proposed", "label": "Proposed" }
]
},
{ "slug": "source_repo_url", "label": "Source repo URL", "type": "string" },
diff --git a/app/src/components/PluginCard.astro b/app/src/components/PluginCard.astro
index 3ff81be..3193a54 100644
--- a/app/src/components/PluginCard.astro
+++ b/app/src/components/PluginCard.astro
@@ -20,7 +20,7 @@ const d = entry.data;
- {d.source_cms && {d.source_cms} → {d.target_cms ?? "—"}}
+ {d.source_cms && {d.source_cms}{d.target_cms ? ` → ${d.target_cms}` : ""}}
{d.purpose && {d.purpose}
}
diff --git a/app/src/components/StatusBadge.astro b/app/src/components/StatusBadge.astro
index e26c833..5affb4c 100644
--- a/app/src/components/StatusBadge.astro
+++ b/app/src/components/StatusBadge.astro
@@ -8,4 +8,4 @@ const value = (status ?? "proposed").toLowerCase();
const label = STATUS_LABELS[value] ?? value;
const desc = STATUSES.find((s) => s.value === value)?.desc;
---
-Status: {label}
+Status: {label}{desc ? ` — ${desc}` : ""}. {label}
diff --git a/app/src/components/StatusLegend.astro b/app/src/components/StatusLegend.astro
index d6860da..46371ec 100644
--- a/app/src/components/StatusLegend.astro
+++ b/app/src/components/StatusLegend.astro
@@ -1,9 +1,13 @@
---
import StatusBadge from "./StatusBadge.astro";
import { STATUSES } from "../lib/statuses";
+interface Props {
+ items?: typeof STATUSES;
+}
+const { items = STATUSES } = Astro.props;
---
- {STATUSES.map((i) => (
+ {items.map((i) => (
{i.desc}
))}
diff --git a/app/src/lib/statuses.ts b/app/src/lib/statuses.ts
index 7f70f16..b5c0008 100644
--- a/app/src/lib/statuses.ts
+++ b/app/src/lib/statuses.ts
@@ -1,3 +1,5 @@
+export const PLUGIN_FETCH_CAP = 10000;
+
export interface StatusDef {
value: string;
label: string;
@@ -5,13 +7,13 @@ export interface StatusDef {
}
export const STATUSES: StatusDef[] = [
- { value: "port", label: "Port", desc: "must be reimplemented on the target CMS" },
- { value: "built-in", label: "Built-in", desc: "covered by target CMS core / framework" },
- { value: "saas", label: "SaaS", desc: "replaced by an external service" },
- { value: "drop", label: "Drop", desc: "not needed on the target CMS" },
- { value: "gated", label: "Gated", desc: "fate depends on an unresolved decision" },
- { value: "done", label: "Done", desc: "ported and shipped" },
- { value: "proposed", label: "Proposed", desc: "submitted for cataloging" },
+ { value: "port", label: "Port", desc: "Reimplemented as a new Emdash plugin" },
+ { value: "built-in", label: "Built-in", desc: "Already covered by Emdash core" },
+ { value: "saas", label: "SaaS", desc: "Replaced by an external hosted service" },
+ { value: "drop", label: "Drop", desc: "Not needed after migration" },
+ { value: "gated", label: "Gated", desc: "Blocked on an open decision" },
+ { value: "done", label: "Done", desc: "Ported and shipped" },
+ { value: "proposed", label: "Proposed", desc: "Newly added, not yet classified" },
];
export const STATUS_LABELS: Record = Object.fromEntries(
diff --git a/app/src/pages/cms/[slug].astro b/app/src/pages/cms/[slug].astro
index 516530f..f736eee 100644
--- a/app/src/pages/cms/[slug].astro
+++ b/app/src/pages/cms/[slug].astro
@@ -2,6 +2,7 @@
import { getEmDashCollection, getEmDashEntry, decodeSlug } from "emdash";
import Base from "../../layouts/Base.astro";
import PluginCard from "../../components/PluginCard.astro";
+import { PLUGIN_FETCH_CAP } from "../../lib/statuses";
export const prerender = false;
@@ -14,7 +15,6 @@ if (!slug || !cms) {
}
if (cacheHint) Astro.cache.set(cacheHint);
-const PLUGIN_FETCH_CAP = 10000;
const { entries: all } = cms
? await getEmDashCollection("plugins", { orderBy: { title: "asc" }, limit: PLUGIN_FETCH_CAP })
: { entries: [] };
@@ -36,6 +36,7 @@ const targetingHere = all.filter((p) => p.data.target_cms === cmsName);
{cms.data.description && {cms.data.description}
}
Plugins from this CMS ({fromHere.length})
+ Filter these in the catalog
{fromHere.length === 0 ? (
No plugins cataloged for this CMS yet.
) : (
diff --git a/app/src/pages/cms/index.astro b/app/src/pages/cms/index.astro
index 3654ebd..ec01823 100644
--- a/app/src/pages/cms/index.astro
+++ b/app/src/pages/cms/index.astro
@@ -1,6 +1,7 @@
---
import { getEmDashCollection } from "emdash";
import Base from "../../layouts/Base.astro";
+import { PLUGIN_FETCH_CAP } from "../../lib/statuses";
export const prerender = false;
@@ -9,7 +10,6 @@ const { entries: cmses, cacheHint } = await getEmDashCollection("cmses", {
});
Astro.cache.set(cacheHint);
-const PLUGIN_FETCH_CAP = 10000;
const { entries: plugins } = await getEmDashCollection("plugins", { limit: PLUGIN_FETCH_CAP });
if (plugins.length >= PLUGIN_FETCH_CAP) console.warn("[cms] plugin fetch hit cap", PLUGIN_FETCH_CAP, "- counts/lists may be truncated");
const countBySource = new Map();
@@ -23,11 +23,12 @@ for (const p of plugins) {
---
By CMS
+ CMSes
{cmses.map((c) => (
-
-
{countBySource.get(c.data.title) ?? 0} from · {countByTarget.get(c.data.title) ?? 0} targeting
+ {countBySource.get(c.data.title) ?? 0} plugins from · {countByTarget.get(c.data.title) ?? 0} targeting
{c.data.description && {c.data.description}
}
))}
diff --git a/app/src/pages/index.astro b/app/src/pages/index.astro
index 78153db..df2decd 100644
--- a/app/src/pages/index.astro
+++ b/app/src/pages/index.astro
@@ -3,13 +3,15 @@ import { getEmDashCollection } from "emdash";
import Base from "../layouts/Base.astro";
import PluginCard from "../components/PluginCard.astro";
import StatusLegend from "../components/StatusLegend.astro";
-import { STATUSES } from "../lib/statuses";
+import { STATUSES, PLUGIN_FETCH_CAP } from "../lib/statuses";
export const prerender = false;
const { entries: plugins, cacheHint } = await getEmDashCollection("plugins", {
orderBy: { title: "asc" },
+ limit: PLUGIN_FETCH_CAP,
});
+if (plugins.length >= PLUGIN_FETCH_CAP) console.warn("[index] plugin fetch hit cap", PLUGIN_FETCH_CAP, "- list may be truncated");
Astro.cache.set(cacheHint);
const url = new URL(Astro.request.url);
@@ -29,33 +31,34 @@ const filtered = plugins.filter((p) => {
return true;
});
---
-
+
Plugins
- {(q || statusFilter || sourceFilter) ? `${filtered.length} of ${plugins.length} match.` : `${plugins.length} entries cataloged. Filter by status, source CMS, or search by name.`}
+ {(q || statusFilter || sourceFilter) ? `${filtered.length} of ${plugins.length} match.` : `${plugins.length} WordPress plugins cataloged with how each maps onto Emdash. Filter by status, source CMS, or search by name.`}
-
+
{filtered.length === 0 ? (
No plugins match {q ? `“${q}”` : "the current filters"}. Clear filters
) : (
+ Plugin results
{filtered.map((entry) => )}
diff --git a/app/src/pages/plugins/[slug].astro b/app/src/pages/plugins/[slug].astro
index f1591f1..20d06d7 100644
--- a/app/src/pages/plugins/[slug].astro
+++ b/app/src/pages/plugins/[slug].astro
@@ -18,8 +18,9 @@ if (cacheHint) Astro.cache.set(cacheHint);
const d = entry?.data;
const { entries: cmses } = entry ? await getEmDashCollection("cmses", {}) : { entries: [] };
-const cmsSlugByTitle = new Map(cmses.map((c) => [c.data.title as string, c.id]));
-const sourceCmsSlug = d?.source_cms ? cmsSlugByTitle.get(d.source_cms) : undefined;
+const norm = (s: string) => s.trim().toLowerCase();
+const cmsSlugByTitle = new Map(cmses.map((c) => [norm(c.data.title as string), c.id]));
+const sourceCmsSlug = d?.source_cms ? cmsSlugByTitle.get(norm(d.source_cms)) : undefined;
---
{!entry || !d ? (
@@ -31,7 +32,7 @@ const sourceCmsSlug = d?.source_cms ? cmsSlugByTitle.get(d.source_cms) : undefin
title={d.title}
description={d.purpose ?? undefined}
content={{ collection: "plugins", id: entry.data.id, slug: entry.id }}
- breadcrumbs={[{ name: "Plugins", url: "/" }, ...(sourceCmsSlug ? [{ name: d.source_cms, url: `/cms/${sourceCmsSlug}` }] : []), { name: d.title, url: Astro.url.pathname }]}
+ breadcrumbs={[{ name: "Plugins", url: "/" }, ...(d.source_cms ? [{ name: d.source_cms, url: sourceCmsSlug ? `/cms/${sourceCmsSlug}` : "/cms" }] : []), { name: d.title, url: Astro.url.pathname }]}
>