Skip to content

fix: server-side content list search + locale-aware list indexes#1226

Draft
scottbuscemi wants to merge 1 commit into
mainfrom
fix/1219-content-list-search
Draft

fix: server-side content list search + locale-aware list indexes#1226
scottbuscemi wants to merge 1 commit into
mainfrom
fix/1219-content-list-search

Conversation

@scottbuscemi
Copy link
Copy Markdown
Contributor

@scottbuscemi scottbuscemi commented May 29, 2026

Addresses #1219.

Problem

On collections with 1000+ entries:

  • Search only matched rows already loaded on the current page. An entry far back in the collection couldn't be found from the first page — it became searchable only after you navigated near it. (ContentList filtered data.pages.flatMap(...).filter(byTitle) client-side; the API client never sent a search term; the list query/handler/repository had no search filter.)
  • Navigation was slow on large, i18n-enabled collections partly because locale-filtered ordered lists weren't covered by an index.

Fix

Server-side search

  • contentListQuery schema gains an optional q (trimmed, 1–200 chars).
  • ContentRepository.findMany/count apply a case-insensitive substring filter across handler-resolved searchColumns, OR'd. lower() on both sides for SQLite/Postgres parity; LIKE wildcards (% _ \) in the query are escaped with ESCAPE '\'.
  • handleContentList resolves the searchable columns from the collection's fields — always slug, plus title/name when defined (mirrors the admin's title resolution) — so collections without those columns don't error.
  • Admin: fetchContentList sends q; ContentList debounces the search box (300ms) and reports it up via a new onSearchChange prop; the route feeds it into the infinite query key + fetch. Client-side filtering is retained only as legacy behavior when onSearchChange isn't supplied.

Navigation

  • New migration 041_content_locale_list_index adds idx_{table}_deleted_locale_updated_id and …_created_id across all ec_* tables (idempotent, forward-only), mirrored in SchemaRegistry for new collections.

Deliberately out of scope

The research flagged the UI's page-number pagination over forward-only cursors as the bigger driver of deep-navigation latency; that's a larger UX refactor and is deferred. I also did not skip the per-page COUNT — there's an explicit regression guard requiring total on every page, so that optimization would break documented behavior.

Testing

  • New packages/core/tests/integration/content/content-list-search.test.ts (describeEachDialect): finds a deep entry, case-insensitive, slug match, wildcard-escape ("50%" matches only the literal), and unfiltered fallback. Passing on SQLite locally; Postgres in CI.
  • New admin test in ContentList.test.tsx: server mode reports the debounced query and does not client-filter.
  • Full core suite green (3486 tests); pnpm lint:json clean; emdash + @emdash-cms/admin typecheck pass. Admin browser tests run in CI (couldn't launch a local browser here).

Manual verification

  1. Collection with 1000+ entries.
  2. Search a term that only matches a deep entry → it appears (previously didn't until you paged near it).
  3. With i18n enabled, confirm locale-filtered lists still page correctly.

Try this PR

Open a fresh playground →

A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.

Tracks fix/1219-content-list-search. Updated automatically when the playground redeploys.

The admin content list filtered only the rows already loaded on the
current page, so entries far back in a large collection were unfindable
until you navigated near them. Add a `q` query param to the content
list endpoint that performs a case-insensitive substring search across
the collection's title/name/slug columns (LIKE wildcards escaped), and
wire the admin search box to drive it (debounced) instead of filtering
in memory. Add locale-aware composite indexes so i18n-filtered lists stay
index-served on large tables. Closes #1219.
@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented May 29, 2026

🦋 Changeset detected

Latest commit: 7545b03

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 14 packages
Name Type
emdash Patch
@emdash-cms/admin Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions
Copy link
Copy Markdown
Contributor

PR template validation failed

Please fix the following issues by editing your PR description:

  • This PR does not use the required PR template. Please edit the description to use the PR template. Copy it into your PR description and fill out all sections.

See CONTRIBUTING.md for the full contribution policy.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 29, 2026

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@1226

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@1226

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@1226

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@1226

emdash

npm i https://pkg.pr.new/emdash@1226

create-emdash

npm i https://pkg.pr.new/create-emdash@1226

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@1226

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@1226

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@1226

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@1226

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@1226

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@1226

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@1226

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@1226

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@1226

commit: 7545b03

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 7545b03 May 29 2026, 11:03 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs 7545b03 May 29 2026, 11:03 PM

@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 7545b03 May 29 2026, 11:05 PM

@github-actions
Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant