* i18n(fr): translate in French the 89 missing keys
* example.com
Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com>
---------
Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com>
Co-authored-by: Matt Kane <mkane@cloudflare.com>
* fix(core): generate row types for repeater fields
`fieldTypeToTypeScript` had no `repeater` case, so every repeater field
fell through to `default: return "unknown"` -- while the zod side derives
a precise row schema from the same `validation.subFields` metadata.
This was an oversight rather than a decision. The emitter's switch,
including its deliberate `case "json": return "unknown"`, dates from
43fcb9a1 and predates the field type: #111 added `repeater` without
touching this file at all, so neither the zod case nor the TS case
arrived with the feature. #2458 then added the zod case and left the TS
side as it was. Type generation is now the only consumer of `subFields`
that discards it -- media-usage extraction and the admin field editor
both read it.
Consumers cannot index or iterate `unknown`, so they declare row shapes
by hand and cast to them. Those casts silence the compiler: when a
sub-field is renamed or retyped, the generated type stays `unknown`, the
cast keeps asserting the old shape, and the drift surfaces as a silently
blank render rather than a type error.
- Build an inline row type from `subFields`, reusing the existing
per-type mapping so a `select` sub-field enumerates its options and an
`image` sub-field emits the same media literal as a top-level image.
- Type a sub-field that is not required as `T | null`, since
`generateRepeaterRowSchema` applies `.nullish()` to it. A bare `T`
would trade an honest `unknown` for an unsound type.
- Keep emitting `unknown` when `subFields` is absent or empty. A repeater
with no declared rows is schema-valid, and `{}[]` would be falsely
permissive.
`fieldTypeToTypeScript` now takes a structural subset of `Field` so a
sub-field can reuse the mapping without a type assertion.
Sub-field slugs match `/^[a-z][a-z0-9_]*$/`, so they are valid bare
identifiers and need no quoting in the emitted literal.
* fix(core): emit valid repeater row types from unvalidated sub-fields
Quote member names, keep the last declaration of a duplicated slug, and
fall back to `unknown` when `subFields` is not an array.
---------
Co-authored-by: Matt Kane <mkane@cloudflare.com>
* fix(taxonomies): respect active locale in admin surfaces
Group translated definitions by logical identity before rendering admin navigation and editor choices. Scope visible term counts and cache entries to the resolved content locale.
* fix(taxonomies): preserve locale in admin term views
* ci: update query-count snapshots
* fix(admin): preserve locale sidebar active state
* fix(taxonomies): preserve exact locale updates
* test(perf): update locale query snapshots
---------
Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com>
* fix(scheduler): surface missed scheduled work
Persist maintenance heartbeats so authenticated editors are warned when overdue content indicates the scheduler has stopped.
* fix(scheduler): keep heartbeat writes non-fatal
A transient operator-health write must not reject maintenance that already completed successfully.
* test(scheduler): cover heartbeat upsert failures
Keep the non-fatal heartbeat contract exercised for both initial inserts and existing-row updates.
* feat(cli): detect broken scheduler wiring
Catch Cloudflare deployments that would silently miss scheduled work before they are deployed.
* fix(cli): keep doctor type-aware lint clean
* fix(cli): preserve additive doctor checks
Keep explicit database diagnosis intact, recognize Wrangler TOML, and avoid flagging Workers that do not opt into EmDash scheduled maintenance.
* fix(cli): make scheduler fixes config-aware
Keep doctor guidance copy-pasteable for Wrangler TOML users as well as JSON and JSONC sites.
* fix(cli): finish TOML doctor guidance
Keep every copy-pasteable scheduler fix and the command help accurate for all supported Wrangler formats.
* fix(loader): make folded bylines available to live entries
* fix(loader): keep folded byline hydration in sync
* test(loader): assert the public byline credit type
* test(loader): pin folded custom field behavior
* test(loader): isolate byline field cache by dialect
* feat(content): filter indexed custom fields
* fix(content): normalize field filters before validation
* test(content): cover indexed field filter contracts
* fix(content): cap indexed filter bind budget
* fix(content): preserve indexed filter query plans
* fix(content): keep the missing-collection error contract with field filters
* fix(content): settle both list queries before rethrowing
`findMany` races the page query against the count query. A collection whose
table is missing rejects both, and `Promise.all` returns on the first while
the other stays in flight holding a pooled connection. A Postgres pool
destroyed inside that window never finishes closing.
This predates the branch. The missing-collection regression test added here
is the first to reach the path on Postgres, so the suite cannot pass without
it.
* fix(content): resolve a missing collection before filter identifiers
Identifier validation ran first, so a request naming both a collection that
does not exist and an invalid filter field answered VALIDATION_ERROR while
the same request without filters answered COLLECTION_NOT_FOUND. The field
names reach the lookup as bound parameters, so deferring validation past the
collection check keeps the query parameterized.
Drop four comments that justify decisions rather than record an invariant.
* fix(content): resolve a missing collection before the filter cap
The cap on filter count threw before the collection lookup, so a request
naming a collection that does not exist alongside more than the allowed
number of filters answered VALIDATION_ERROR while the same request without
filters answered COLLECTION_NOT_FOUND. The cap stays where it is, since it
also bounds how many field slugs reach the lookup as bound parameters.
Drop the comment above the settled list queries. The missing-collection test
fails without them, which is where that contract belongs.
* refactor(content): drop the note above the filter cap
The tests around the cap already say what it protects.
---------
Co-authored-by: logelog <194732487+logelog@users.noreply.github.com>
Co-authored-by: Matt Kane <mkane@cloudflare.com>
* feat(plugins): expose authenticated caller to route handlers as ctx.user
The plugin API catch-all resolves and authorizes the caller, then drops
it before dispatch, leaving plugins no safe way to know who is calling
a private route. Thread the validated user through handlePluginApiRoute
into RouteContext (native format) and routeCtx.user (standard format,
in-process and worker sandboxes alike) as the read-only UserInfo shape.
Public routes and machine tokens with no bound user receive undefined;
the catch-all only forwards the caller after private-route auth, so an
ambient admin session is never bound to a public route.
Fixes#812
* docs(core): use canonical users capability name
* fix(core): forward callers to MCP plugin routes
---------
Co-authored-by: Matt Kane <mkane@cloudflare.com>
* fix(core): remove stale restampEntryPivot call that crashes content publish
The merge of #2460 and #2461 left main with a publish path that calls
restampEntryPivot, a method #2461 deleted together with the pivot
denormalization it maintained. Every content publish now throws
"this.restampEntryPivot is not a function".
No replacement call is needed: since migration 068 the pivot columns the
call re-stamped are legacy, and reads join the authoritative ec_* row.
* chore: add changeset for the publish crash fix
The fix changes the published emdash package, and release notes are
built from changeset files, so the crash fix needs an entry even though
the crash itself never shipped in a release.