Commit Graph

601 Commits

Author SHA1 Message Date
emdashbot[bot] 4c3edadeec ci: release (#2419)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-18 13:26:49 +01:00
Armand Philippot eba44f7c63 i18n(fr): translate in French the 89 missing keys (#2464)
* 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>
2026-08-18 10:00:02 +01:00
emdashbot[bot] d328bdd6a6 chore: extract locale catalogs [skip ci] 2026-08-18 07:20:38 +00:00
Frank Bartolitsch f8a4fcefd2 fix(taxonomies): respect active locale in admin surfaces (#2342)
* 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>
2026-08-18 07:18:22 +00:00
emdashbot[bot] 75860bf32e chore: extract locale catalogs [skip ci] 2026-08-17 09:46:58 +00:00
Matt Kane 5c216c2f92 fix: preserve taxonomy assignments across locale fallbacks (#2508)
* fix(taxonomies): preserve assignments across locale fallback

* fix(admin): clarify taxonomy locale resolution

* perf(taxonomies): join locale fallbacks once

* fix(admin): gate taxonomy creation by permission

* chore: retry external preview build

* fix(admin): show flat taxonomy creation errors
2026-08-17 09:45:55 +00:00
emdashbot[bot] f15d433a4f chore: extract locale catalogs [skip ci] 2026-08-17 09:19:44 +00:00
Matt Kane 5289385499 fix: generate inline taxonomy slugs server-side (#2506)
* fix: keep inline taxonomy terms Unicode-safe

* fix: retry concurrent taxonomy slug collisions
2026-08-17 10:17:39 +01:00
emdashbot[bot] a5d31d547f chore: extract locale catalogs [skip ci] 2026-08-17 09:14:42 +00:00
Matt Kane 515c08d17c fix(scheduler): warn when scheduled maintenance stops (#2512)
* 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.
2026-08-17 10:12:12 +01:00
emdashbot[bot] b87da7d12a chore: extract locale catalogs [skip ci] 2026-08-16 22:24:40 +00:00
Matt Kane 582823328e fix: preserve Unicode in routable content slugs (#2505)
* fix: keep routable content addressable across languages

* fix: keep invalid legacy slugs out of sitemaps

* fix: close routable publish bypasses

* fix: support slugless non-routable seeds

* fix: protect published routable slugs on update

* fix: normalize unusable slugs in seed exports

* fix: reject slugless routable schedules

* chore: clarify slug generation contract

* chore: remove editor implementation narrative

* test: keep revisionless fixture routable
2026-08-16 23:23:34 +01:00
emdashbot[bot] 140179959d chore: extract locale catalogs [skip ci] 2026-08-16 19:57:18 +00:00
Noah (Nguyen Pham) 318b821927 fix(admin): restore image and HTML toolbar actions (#2510)
* fix(admin): keep script formatting contextual

* fix(admin): restore image and HTML toolbar actions
2026-08-16 20:55:21 +01:00
emdashbot[bot] 1318b0cb32 chore: extract locale catalogs [skip ci] 2026-08-16 16:59:31 +00:00
Matt Kane 13db62c82f fix(core): match FileValue to persisted media snapshots (#2489)
* fix(core): match file values to persisted media snapshots

* fix(core): preserve sparse file snapshots safely
2026-08-16 17:02:11 +01:00
Matt Kane 2398b8d1bd fix: prevent unsupported Portable Text mark loss (#2493)
* fix: prevent unsupported Portable Text marks from being lost

* fix: keep editor open after rejected section insert
2026-08-16 16:59:22 +01:00
Matt Kane 964f51e5da Fix tag autocomplete navigation and overflow (#2491)
* fix(admin): make tag matches reachable

* test(admin): cover tag ranking and focus
2026-08-16 16:57:19 +01:00
emdashbot[bot] 38797a95c5 chore: extract locale catalogs [skip ci] 2026-08-16 12:14:49 +00:00
logelog 4c565ea7f9 feat(admin): allow trusted plugins to add content list columns (#2195)
* feat(admin): allow trusted plugins to add content list columns

* fix(admin): use ESM extensions for plugin columns

* test(admin): cover combined content list columns

* fix(admin): pass visible page to plugin columns

* fix(admin): use ASCII plugin column fallback

* fix(admin): enforce plugin column contracts

* docs(admin): use neutral column examples

* fix(admin): preserve plugin column state

* fix(admin): accept wrapped plugin column components

---------

Co-authored-by: logelog <194732487+logelog@users.noreply.github.com>
Co-authored-by: Matt Kane <mkane@cloudflare.com>
2026-08-16 12:13:37 +00:00
emdashbot[bot] 19978ffcfc chore: extract locale catalogs [skip ci] 2026-08-16 08:19:52 +00:00
Eyüp Can Akman 77080152db fix(admin): parse API token created date as UTC (#2467)
Co-authored-by: Matt Kane <mkane@cloudflare.com>
2026-08-16 09:18:45 +01:00
emdashbot[bot] 0991de0e09 chore: extract locale catalogs [skip ci] 2026-08-16 06:42:11 +00:00
CacheMeOwside 5224b5711a feat(core): configurable titleField and dateField per collection (#1973)
* feat(core): configurable displayField and dateField per collection

* style: format

* ci: update query-count snapshots

* add fixes as per PR comments

* ci: update query-count snapshots

* reject field type changes that break displayField/dateField

* rename displayField to titleField

* match content-list search and suggestions against configured titleField

* ci: update query-count snapshots

* update integration tests

* style: format

* removed reference to PR from comments

* Remove PR reference

Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com>

---------

Co-authored-by: emdashbot[bot] <emdashbot[bot]@users.noreply.github.com>
Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com>
2026-08-16 07:41:13 +01:00
Eyüp Can Akman 592a0e37c6 fix(admin): parse SQLite timestamps as UTC (#1121)
* fix(admin): parse SQLite timestamps as UTC

SQLite datetime('now') stores timestamps with no timezone suffix, which new Date() reads as local time.
Revision and dashboard timestamps in the admin UI drifted by the viewer's offset.

Closes #919

* fix(admin): route content list and editor dates through parseTimestamp

* fix(admin): route the scheduled publish date through parseTimestamp

---------

Co-authored-by: Matt Kane <mkane@cloudflare.com>
2026-08-13 15:33:30 +00:00
emdashbot[bot] ced6fcac0b chore: extract locale catalogs [skip ci] 2026-08-13 13:38:22 +00:00
logelog fefb702763 feat(content): support indexed custom field sorting (#2212)
* feat(content): support indexed custom field sorting

* fix(content): remove redundant indexed cursor cast

* fix(content): use generated custom field indexes

* fix(content): keep indexed cursor scans ordered

* test(content): capture qualified list queries

* fix(admin): clear the indexed flag for non-indexable field types

* test(admin): cover clearing the indexed flag on type change

* refactor: drop comments that address the reviewer

Three blocks defend sending `indexed: false` or narrate a Playwright
workaround. The tests already name the contract they cover. The bind-budget
note above the seed batch size stays: it records a limit a reader would
otherwise raise.

* test(core): cover indexed ordering on PostgreSQL

* refactor(schema): simplify indexable field comment

* fix(test): import the indexed migration from its current slot

The file moved to 061 when upstream took 059; this import still named 060,
so the suite could not load the module at all.

* fix(seed): reject unsupported indexed field types

* test(core): allow PostgreSQL ordering setup

* fix(content): preserve indexed field contracts

* fix(test): import indexed migration from slot 066

* fix(core): keep localized custom ordering seekable

---------

Co-authored-by: logelog <194732487+logelog@users.noreply.github.com>
Co-authored-by: Matt Kane <mkane@cloudflare.com>
2026-08-13 14:34:36 +01:00
marcusbellamyshaw-cell 4831f77454 fix: exclude image/svg+xml from default media upload allowlist (#2250)
* fix: exclude image/svg+xml from default media upload allowlist

GLOBAL_UPLOAD_ALLOWLIST used a bare "image/" prefix match, which
unintentionally included image/svg+xml. There is no upload-time content
validation for SVG, so a Contributor+ user could upload an SVG with an
embedded <script>. Enumerate the safe raster types explicitly instead.
Field-specific allowedMimeTypes overrides are unaffected.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

* fix(admin): match Images upload preset to the raster-only allowlist

The Images preset in AllowedTypesEditor still stored a bare "image/"
prefix, which prefix-matches image/svg+xml. Since upload routes widen
the global allowlist to a field's own allowedMimeTypes, picking this
preset re-admits SVG uploads and undermines the new default that
excludes them.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Matt Kane <mkane@cloudflare.com>
2026-08-13 14:26:39 +01:00
emdashbot[bot] f9e44628af chore: extract locale catalogs [skip ci] 2026-08-13 13:25:07 +00:00
logelog 6a7786217a feat(admin): allow trusted plugins to add editor panels (#2187)
* feat(admin): allow trusted plugins to add editor panels

* fix(admin): use Kumo button in panel fallback

* fix(admin): integrate plugin panels with sortable settings

* test(admin): cover plugin panel retry recovery

* fix(admin): reset a failed plugin panel when the entry changes

The boundary kept its error state across entries, so a panel that threw on
one entry stayed replaced by the fallback after navigating to another. Keying
it on collection and entry id rebuilds it with the content it renders.

Retry already recovers: React unmounts the subtree when the boundary catches,
so clearing the flag mounts the panel fresh. The added test asserts that
mount count rather than assuming it.

* fix(admin): localize plugin panel titles

---------

Co-authored-by: logelog <194732487+logelog@users.noreply.github.com>
2026-08-13 14:24:01 +01:00
emdashbot[bot] 21cca95410 chore: extract locale catalogs [skip ci] 2026-08-12 11:23:24 +00:00
Noah (Nguyen Pham) dd17e616d2 feat(admin): refine featured image field (#2217)
* feat(admin): refine featured image field

* feat(admin): inset featured image preview

* feat(admin): stack featured image details

* fix(admin): preserve featured image preview aspect ratio

* chore: use media library project changeset

* fix(media): align image value schemas

* fix(admin): clarify featured image actions

---------

Co-authored-by: Matt Kane <mkane@cloudflare.com>
2026-08-12 11:21:54 +00:00
emdashbot[bot] 2cf71bcccb chore: extract locale catalogs [skip ci] 2026-08-12 11:10:05 +00:00
MA2153 a159b445d4 feat: filter the admin content list by byline (#2312)
* feat: filter the admin content list by byline

Adds byline filtering alongside the existing status, author, and date
filters. Selecting several bylines matches entries credited to any of
them; "No byline assigned" matches entries with no credit.

Credits inferred from an entry's author (rendered when an entry has no
explicit credit) are excluded unless opted into, so the filter matches
assigned bylines by default.

No migration is required. The UNIQUE(collection_slug, content_id,
byline_id) index from migration 031 covers every filter shape: EXPLAIN
QUERY PLAN shows a covering seek for the include, exclude, and no-byline
probes while the outer query keeps its sort-ordered composite index, so
LIMIT still short-circuits without a temp B-tree. Correlating EXISTS from
the content table is what makes that hold — driving from the pivot side
cannot use the index for the byline and forces a temp sort — hence the
note in applyBylineFilter.

Filter values are translation_groups (what the junction has stored since
migration 040), so a selection matches a byline across every locale.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* tmp: bulk-apply bylines from the content list

Temporary tooling for exercising the byline filter by hand. Not intended
to ship with the feature -- revert this commit before the PR goes up.

Adds a "Set byline" picker to the existing bulk-selection toolbar. The
picked bylines replace each selected entry's credit set rather than
merging into it: list items hydrate credits with strict locale matching,
so an entry whose byline has no row in the entry's locale comes back with
an empty `bylines` array, and a client-side merge silently drops those
credits on write.

Requests fan out through runBulkAction like the other bulk actions, so
failed ids stay selected for a retry.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Revert "tmp: bulk-apply bylines from the content list"

This reverts commit 28101afdf5f32b88c5939387e7f5768fdf4aea80.

* fix: resolve inferred bylines at the list's locale

The inferred-byline branch of the content-list filter matched on
`author_id` alone, against a set of users resolved from every locale row
of the selected bylines. Byline hydration is strict per locale, so an
entry whose author owns a byline with no row at the list's locale renders
as uncredited while still matching a filter on that byline -- and matched
"No byline" at the same time, since that branch did correlate on locale.

Resolve the credit the same way the list renders it: a correlated EXISTS
on `_emdash_bylines` scoped to the locale the list is showing, falling
back to each entry's own locale when the list spans locales. This drops
the pre-resolution query the handler ran for `includeInferredBylines`, so
the opt-in no longer costs an extra round-trip.

Also pluralize the selected-byline count through Lingui's `plural` rather
than a bare interpolation, which only reads correctly in languages with a
single plural form.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: trim the byline filter doc comment to its invariants

Drop the rejected-alternative narrative and the migration reference;
keep why the EXISTS correlates from the content table and why "none"
tests the junction rather than primary_byline_id.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: drop the default-justification line from ContentBylineFilter

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* docs: drop the design rationale from the BylineFilter docstring

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* Update packages/admin/src/components/ContentList.tsx

Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com>

* fix: scope the byline filter's explicit credits to the list's locale

The credit EXISTS matched a junction row by translation_group alone,
but a credit only renders where that group has a byline row at the
list's locale. Junction rows are copied to every translation of an
entry, so a group that exists only in `en` still matched
`bylines=[group]` on the `fr` list, where the entry renders
uncredited — and was excluded from "no byline" at the same time.

The credit test now repeats the join `getContentBylinesMany` makes,
with the same list-locale / entry-locale fallback as the inferred
branch. The author fallback keeps a separate, deliberately
locale-agnostic junction probe: hydration suppresses inference on the
presence of any credit, not on one that renders here, so an entry
whose explicit credit fails to resolve stays uncredited rather than
falling through to its author.

Adds a query-plan test covering all four filter shapes: every probe
is still an indexed seek and no shape sorts in a temp b-tree.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* chore: drop the issue reference from the filter-state comment

Comments are for future readers of the code; the issue number is
narrative that belongs in the commit and PR description.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com>
2026-08-12 11:06:19 +00:00
emdashbot[bot] 0970780ef1 chore: extract locale catalogs [skip ci] 2026-08-12 09:40:09 +00:00
Daniel 49bc75efb1 fix(admin): localize the editor word-count footer (#2402)
* fix(admin): localize the editor word-count footer

The word, character, and reading-time metrics under the Portable Text
editor were raw string literals, so they stayed English in every admin
language and used hand-rolled one/other pluralization. Route them
through Lingui so the catalogs pick them up and each language's plural
rules apply.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(admin): reset i18n locale after footer test; drop test-export comment

Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com>

* fix(admin): dedupe EditorFooter test export

Applying the review suggestion anchored to the comment line only, which
left the export statement duplicated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(admin): pluralize the reading-time footer message

English "min" is invariant, but the flat t-string kept translators from
branching on number. Use the same plural() macro as the other two metrics
so locales that spell out "minutes" get ICU plural categories.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: emdashbot[bot] <273199577+emdashbot[bot]@users.noreply.github.com>
2026-08-12 10:39:11 +01:00
emdashbot[bot] 09049fd270 chore: extract locale catalogs [skip ci] 2026-08-11 16:09:08 +00:00
logelog 2d5fb0bccd feat(admin): show configured fields in content lists (#2194)
* feat(admin): show configured fields in content lists

* fix(admin): use the .js specifier on the content list export

* fix(schema): enforce list column limit

---------

Co-authored-by: logelog <194732487+logelog@users.noreply.github.com>
2026-08-11 17:07:48 +01:00
logelog 9e74e59958 fix(content): search searchable custom fields (#2191)
* fix(content): search searchable custom fields

* docs(content): describe what the search filter actually matches

`searchColumns` no longer comes from display fields alone. It also carries
the slug and every field marked searchable, so the docstring was telling
callers the wrong thing about what `q` will match.

---------

Co-authored-by: logelog <194732487+logelog@users.noreply.github.com>
2026-08-11 16:54:59 +01:00
emdashbot[bot] a891d73232 ci: release (#2350)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-08-11 15:49:52 +01:00
emdashbot[bot] e0ce3a9a9b chore: extract locale catalogs [skip ci] 2026-08-11 10:41:52 +00:00
Daniel 604a524343 i18n(de): complete German translations (#2400)
Translate the 373 remaining entries in the German admin catalog.

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-11 11:40:53 +01:00
emdashbot[bot] 513ab24e91 chore: extract locale catalogs [skip ci] 2026-08-10 12:45:29 +00:00
Matt Kane 1cf2811310 fix(admin): paginate redirects list (#2406)
* fix(admin): paginate redirects list

* fix(admin): reset redirect pagination after mutations
2026-08-10 13:41:33 +01:00
Daniel 317eb3ee7f test(admin): pick the role option directly to deflake the allowed-domains dialog test (#2398)
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 09:51:05 +01:00
emdashbot[bot] 671bd4eb60 chore: extract locale catalogs [skip ci] 2026-08-10 06:27:17 +00:00
Daniel 6e47033005 fix(admin): show published content as "Published", not "Publish" (#2401)
* fix(admin): show published content as "Published", not "Publish"

The status badge and the status filter labeled published content with
the action verb. Translated locales made it plainly wrong (German badge
read "Publizieren"). Both sites now use the existing "Published" msgid,
so existing translations apply automatically.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(admin): label the dashboard published count with the state for screen readers

Applies the review suggestion: the published CountBadge sr-only label now
uses the state "Published" like the badge and filter, matching the
adjacent "Drafts" state label.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-08-10 07:26:13 +01:00
emdashbot[bot] 9c699480c0 chore: extract locale catalogs [skip ci] 2026-08-09 11:08:35 +00:00
David Pivert e8048e40b4 feat(admin): explicit collection order in the sidebar (#2267)
* feat(admin): explicit collection order in the sidebar

The sidebar order came straight from `listCollections`, which sorted by
slug. A site's collections then appear in an order nobody chose —
"Certifications, Education, Endorsements, Pages, Positions, Posts,
Projects" — with no way out short of renaming slugs (breaking URLs and
queries) or forking the admin.

Adds `sort_order` on `_emdash_collections`, settable by dragging rows on
the Content Types screen or via `sortOrder` in a seed file. The column is
nullable rather than defaulting to 0: NULL means "no explicit position",
and those collections keep the alphabetical order behind the ordered
ones, so a site that never reorders renders exactly as before. Reads
materialise the fallback with COALESCE instead of relying on NULL
ordering, which SQLite puts first and Postgres last on ASC.

`reorderCollections` takes the full desired order and clears the position
of anything left out, so the stored state stays a faithful picture of
what the admin renders instead of a sparse set the UI has to reconcile.

`reorder` becomes a reserved collection slug: the static
POST /schema/collections/reorder route would otherwise shadow a
collection by that name. Same defence in depth already applied to
byline-fields/reorder.

Closes #474

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(admin): expose sortOrder on the schema export, tighten comments

Pre-empts the review notes already raised on the sibling `hidden` PR.

The full schema export the CLI reads for `emdash types` builds its
collection shape by hand and omitted the new field. The comment changes
apply AGENTS.md: drop justification and narrative, keep the non-obvious
constraints (route ordering, NULL sorting across dialects), and remove
issue references from test titles. The migration docstring also pointed
at a symbol name that never existed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 10:59:31 +00:00
emdashbot[bot] a9aa3716f4 chore: extract locale catalogs [skip ci] 2026-08-09 09:56:19 +00:00