emdash reserves 'status' as a built-in entry field (publish state), so
'emdash seed' rejected the plugins collection's custom 'status' select
with 'Field slug status is reserved' — leaving the catalog empty. Rename
the domain field slug to parity_status (label stays 'Migration status')
across the seed field def + 39 entries, the collections type, and all
plugin-data reads. The public ?status= URL filter param and StatusBadge
prop name are unchanged.
emdash seed validates that every content entry has an id (validate.ts),
but seed/seed.json entries only had slug — so seed aborted with 'id is
required' and, under set -e, crash-looped the pod (502). Set id=slug for
all 42 entries (conflict-detection keys off slug, so id is just the
seed-local ref key). Also move the seed step out from under set -e: a bad
content seed should log loudly but not take the whole site down (init
migrations stay fatal).
- entrypoint: run 'emdash seed' after 'emdash init' (init no longer loads
JSON seeds in newer emdash, so the catalog booted empty). Idempotent
onConflict=skip.
- Base.astro: derive canonical/og:url base from EMDASH_SITE_URL (per-env
https URL the chart injects) instead of Astro.url.origin, which is plain
http behind Traefik TLS termination.
cms-plugins is an Astro/emdash web app whose image is built by npm/astro
against an upstream package-lock.json (better-sqlite3 native build) and
cannot be expressed as Nix on emmett/amd64, so it stays on docker buildx.
Apply the cluster #196 OCI escape-hatch: move all build/tag/registry truth
into ci/local.sh, parameterized by BUILDKIT_ADDR (local docker-container
default, dry-run; CI overrides to the in-cluster native arm64 remote +
PUBLISH=1). CI now runs the same script a developer runs, so CI and local
can't drift. The three-tag Flux ImagePolicy contract (0.1.<pipeline>,
<branch>, <branch>-latest) and the arm64/kotkan targeting are preserved
verbatim; the Dockerfile is unchanged.
While HRs are suspended (Phase 0) the staging/production tags are referenced by
no live workload, so gitea-oci-cleanup can reap them. Document the registry-pins
mitigation and a pre-resume existence check in the first-deploy checklist.
Interim fix for the free-text title-match footguns (issue #4); durable ULID
reference + seed migration tracked separately.
- New lib/cms.ts: single normCms() match key + cmsSlugByTitle / resolveCmsSlug,
used by all three join sites so a plugin can no longer link from its own page
yet vanish from a CMS list over case/whitespace drift.
- cms/index.astro + cms/[slug].astro: counts and "plugins from / targeting"
lists now use the normalized key (were exact-match).
- plugins/[slug].astro: drop the local normalize copy; link target_cms too
(was source-only) for parity.
- warnOrphanCmsRefs(): logs any source_cms/target_cms that resolves to no CMS,
so silent orphans surface in the server log.
- DL3008: explicit `hadolint ignore` on the two apt-get installs — bookworm-slim
tracks current security-patched versions; pinning is brittle (reference image
is also unpinned).
- DL3059: fold `npm run build` + `npm prune --omit=dev` into one RUN layer.
- #3 Liveness probe targets full SSR DB-querying / route, coupling pod liveness to SQLite
- #4 Chart values-staging/production.yaml are dead config under Flux; drift trap
- #6 tsconfig includes gitignored emdash-env.d.ts that only the dev server generates
- #7 Dockerfile package-lock glob + npm install fallback can silently build an unlocked image
- #8 Dockerfile creates runtime user without pinning its GID
- #9 entrypoint.sh gates `emdash init` on data.db absence, skipping migrations on PVC reuse
- #10 pullPolicy: Always vs digest pinning
- #11 Dockerfile state symlinks contradict the STATE_DIR contract; Dockerfile does not set ENV STATE_DIR
- #12 astro is a production dependency, so npm prune --omit=dev keeps build-only tooling
- #14 Two ImageUpdateAutomations write back to the same anton-helm-workloads main branch
- #16 memoryCache provider is per-process; correctness depends implicitly on replicas:1
- #17 Root catch-all [slug].astro couples nav links to pages-collection rows + DB hit per unmatched path
- #18 Detail pages render a 200-style body under a 404 status and have no try/catch around getEmDash* calls
- #19 vite allowedHosts hardcodes ddev hostnames (dev-only; no prod impact)
Architecture:
- Cap homepage plugin list at PLUGIN_FETCH_CAP like other pages
- Declare @types/node directly instead of relying on transitive dep
- Single-source status label text (statuses.ts vs seed.json drift)
UI/UX:
- Stop auto-submitting filter selects so keyboard navigation works
- Fix heading hierarchy (add h2) on flat list pages
- Improve homepage title beyond bare "Plugins"
- Make status taxonomy descriptions self-contained
- Render only relevant statuses in the legend, not all 7
- Fix PluginCard "WordPress -> —" for missing target
- Clarify "{n} from / {n} targeting" microcopy
- Use proper count meta markup on CMS list
- Allow header nav row to wrap
- Fix bare CMS URL horizontal overflow
- Add standard line-clamp fallback to cards
- Even out footer stacked paragraph spacing
- Center plugin detail status line (drop margin-left hack)
- Raise toolbar tap targets to 44px
- Surface status badge meaning beyond title attribute
- Include source-CMS breadcrumb step on lookup miss
- Add link into filtered catalog from CMS detail
The deploy/fleet-overlay templates had drifted from what actually runs in
anton-helm-workloads (verified live + against the emdash-kotkanagrilli
reference). Canonical design co-locates everything in the `kotkan` namespace:
- source.yaml: GitRepository flux-system -> kotkan, so the HelmRelease
chart sourceRef resolves same-namespace (no cross-namespace ref).
- secrets.yaml: deploy-key Secret -> kotkan, defined once in the staging
overlay; dropped the duplicate definition from the production overlay
(production references the shared key by name).
- image-automation.yaml: IUA write-back sourceRef
anton-workloads-image-automation/flux-system -> anton-helm-workloads/kotkan
(the existing read source already has push access).
- README.md / DEPLOYMENT.md: namespace + ownership docs corrected.
Multi-agent arch/UX review pass. Architecture: real HTTP 404 on not-found,
breadcrumb links by slug not lowercased title, CMS pages surface their
plugins, shared status taxonomy (src/lib/statuses.ts) consumed by all three
frontend consumers, data-driven status filter, typed emdash collections
(src/emdash-collections.d.ts), removed unused @astrojs/react + react deps and
dead pnpm block, tsconfig extends Astro strict preset, dev deps pruned from
the runtime image. UI/UX: fixed StatusBadge WCAG-AA contrast, labelled the
search/filter controls, canonical URL + BreadcrumbList JSON-LD + og:type,
human status labels, filtered result count + recoverable empty state,
auto-submit filters, mobile overflow fixes, skip-to-content, :focus-visible.
Commit the npm lockfile so the Dockerfile's `npm ci` path engages.
astro check: 0 errors / 0 warnings / 0 hints.
Fresh clones errored out on `ddev start` because the compose file's
env_file: pointed at app/.env (gitignored). No env vars are actually
required by the code — STATE_DIR is the only one and has a default.
- Switch env_file to the path:+required:false form (Docker Compose
2.24+; bundled with DDEV) so missing app/.env is non-fatal.
- Commit app/.env.example as documentation for contributors who do
need overrides.